detangling merge of main node for v2 (#293)

This commit is contained in:
Cassandra Heart 2024-10-12 11:55:17 -07:00 committed by GitHub
parent ab065006b4
commit b4051ccbc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
461 changed files with 843087 additions and 244751 deletions

596
Cargo.lock generated
View File

@ -2,6 +2,41 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array 0.14.7",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -130,6 +165,24 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "basic-toml"
version = "0.1.9"
@ -160,6 +213,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
@ -169,7 +234,16 @@ dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
@ -260,6 +334,26 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "channel"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"criterion 0.4.0",
"ed448-goldilocks-plus",
"hex 0.4.3",
"hkdf",
"hmac",
"lazy_static",
"rand",
"serde",
"serde_json",
"sha2 0.10.8",
"thiserror",
"uniffi",
]
[[package]]
name = "ciborium"
version = "0.2.2"
@ -287,6 +381,16 @@ dependencies = [
"half",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "3.2.25"
@ -363,6 +467,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "criterion"
version = "0.4.0"
@ -456,13 +575,108 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array 0.14.7",
"rand_core",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.7",
"rand_core",
"typenum",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "der"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer 0.10.4",
"crypto-common",
"subtle",
]
[[package]]
name = "ed448-goldilocks-plus"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1a0afcce0f2d28faeda0c9fea990ea5832b9a13a4ecff26d2486fcecc5c014"
dependencies = [
"elliptic-curve",
"hex 0.4.3",
"rand_core",
"serde",
"sha3",
"subtle",
"zeroize",
]
[[package]]
@ -471,12 +685,53 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"base64ct",
"crypto-bigint",
"digest 0.10.7",
"ff",
"generic-array 0.14.7",
"group",
"pem-rfc7468",
"pkcs8",
"rand_core",
"sec1",
"serde_json",
"serdect",
"subtle",
"tap",
"zeroize",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "ff"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"bitvec",
"rand_core",
"subtle",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "fs-err"
version = "2.11.0"
@ -486,6 +741,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "generic-array"
version = "0.12.4"
@ -495,6 +756,17 @@ dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
name = "getrandom"
version = "0.2.15"
@ -506,6 +778,16 @@ dependencies = [
"wasi",
]
[[package]]
name = "ghash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug 0.3.1",
"polyval",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -523,6 +805,17 @@ dependencies = [
"scroll",
]
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"subtle",
]
[[package]]
name = "half"
version = "2.4.1"
@ -578,6 +871,24 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@ -588,6 +899,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
@ -629,6 +949,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
dependencies = [
"cpufeatures",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -685,6 +1014,70 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -718,6 +1111,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "os_str_bytes"
version = "6.6.1"
@ -730,6 +1129,25 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "plain"
version = "0.2.3"
@ -764,6 +1182,18 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "polyval"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug 0.3.1",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -788,6 +1218,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@ -867,6 +1303,31 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rpm"
version = "0.1.0"
dependencies = [
"criterion 0.4.0",
"curve25519-dalek",
"hex 0.4.3",
"lazy_static",
"num",
"rand",
"rayon",
"serde_json",
"subtle",
"uniffi",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.18"
@ -902,6 +1363,21 @@ dependencies = [
"syn",
]
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array 0.14.7",
"pkcs8",
"serdect",
"subtle",
"zeroize",
]
[[package]]
name = "semver"
version = "1.0.23"
@ -913,18 +1389,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
@ -942,16 +1418,47 @@ dependencies = [
"serde",
]
[[package]]
name = "serdect"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
dependencies = [
"base16ct",
"serde",
]
[[package]]
name = "sha2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.7",
]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest 0.10.7",
"keccak",
]
[[package]]
@ -960,6 +1467,16 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -972,6 +1489,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.66"
@ -983,6 +1506,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "textwrap"
version = "0.16.1"
@ -991,18 +1520,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
@ -1181,6 +1710,16 @@ dependencies = [
"weedle2",
]
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "utf8parse"
version = "0.2.1"
@ -1196,7 +1735,7 @@ dependencies = [
"criterion 0.5.1",
"hex 0.3.2",
"num-traits",
"sha2",
"sha2 0.8.2",
"uniffi",
]
@ -1398,3 +1937,32 @@ name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -14,8 +14,10 @@
[workspace]
members = [
"crates/vdf",
"crates/channel",
"crates/classgroup",
"crates/bls48581",
"crates/rpm",
]
[profile.release]

9
channel/README.md Normal file
View File

@ -0,0 +1,9 @@
# Channel
Wrapper for the Rust implementation of Channel in [crates/channel](../crates/channel).
## Generate Go bindings
```sh
go generate
```

88
channel/channel.go Normal file
View File

@ -0,0 +1,88 @@
package channel
import (
generated "source.quilibrium.com/quilibrium/monorepo/channel/generated/channel"
)
//go:generate ./generate.sh
func NewDoubleRatchet(
sessionKey []uint8,
sendingHeaderKey []uint8,
nextReceivingHeaderKey []uint8,
isSender bool,
sendingEphemeralPrivateKey []uint8,
receivingEphemeralKey []uint8,
) string {
return generated.NewDoubleRatchet(
sessionKey,
sendingHeaderKey,
nextReceivingHeaderKey,
isSender,
sendingEphemeralPrivateKey,
receivingEphemeralKey,
)
}
func NewTripleRatchet(
peers [][]uint8,
peerKey []uint8,
identityKey []uint8,
signedPreKey []uint8,
threshold uint64,
asyncDkgRatchet bool,
) generated.TripleRatchetStateAndMetadata {
return generated.NewTripleRatchet(
peers,
peerKey,
identityKey,
signedPreKey,
threshold,
asyncDkgRatchet,
)
}
func DoubleRatchetEncrypt(
ratchetStateAndMessage generated.DoubleRatchetStateAndMessage,
) generated.DoubleRatchetStateAndEnvelope {
return generated.DoubleRatchetEncrypt(ratchetStateAndMessage)
}
func DoubleRatchetDecrypt(
ratchetStateAndEnvelope generated.DoubleRatchetStateAndEnvelope,
) generated.DoubleRatchetStateAndMessage {
return generated.DoubleRatchetDecrypt(ratchetStateAndEnvelope)
}
func TripleRatchetInitRound1(
ratchetStateAndMetadata generated.TripleRatchetStateAndMetadata,
) generated.TripleRatchetStateAndMetadata {
return generated.TripleRatchetInitRound1(ratchetStateAndMetadata)
}
func TripleRatchetInitRound2(
ratchetStateAndMetadata generated.TripleRatchetStateAndMetadata,
) generated.TripleRatchetStateAndMetadata {
return generated.TripleRatchetInitRound2(ratchetStateAndMetadata)
}
func TripleRatchetInitRound3(
ratchetStateAndMetadata generated.TripleRatchetStateAndMetadata,
) generated.TripleRatchetStateAndMetadata {
return generated.TripleRatchetInitRound3(ratchetStateAndMetadata)
}
func TripleRatchetInitRound4(
ratchetStateAndMetadata generated.TripleRatchetStateAndMetadata,
) generated.TripleRatchetStateAndMetadata {
return generated.TripleRatchetInitRound4(ratchetStateAndMetadata)
}
func TripleRatchetEncrypt(
ratchetStateAndMessage generated.TripleRatchetStateAndMessage,
) generated.TripleRatchetStateAndEnvelope {
return generated.TripleRatchetEncrypt(ratchetStateAndMessage)
}
func TripleRatchetDecrypt(
ratchetStateAndEnvelope generated.TripleRatchetStateAndEnvelope,
) generated.TripleRatchetStateAndMessage {
return generated.TripleRatchetDecrypt(ratchetStateAndEnvelope)
}

14
channel/generate.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -euxo pipefail
ROOT_DIR="${ROOT_DIR:-$( cd "$(dirname "$(realpath "$( dirname "${BASH_SOURCE[0]}" )")")" >/dev/null 2>&1 && pwd )}"
RUST_CHANNEL_PACKAGE="$ROOT_DIR/crates/channel"
BINDINGS_DIR="$ROOT_DIR/channel"
# Build the Rust Channel package in release mode
cargo build -p channel --release
# Generate Go bindings
pushd "$RUST_CHANNEL_PACKAGE" > /dev/null
uniffi-bindgen-go src/lib.udl -o "$BINDINGS_DIR"/generated

View File

@ -0,0 +1,8 @@
#include <channel.h>
// This file exists beacause of
// https://github.com/golang/go/issues/11263
void cgo_rust_task_callback_bridge_channel(RustTaskCallback cb, const void * taskData, int8_t status) {
cb(taskData, status);
}

View File

@ -0,0 +1,954 @@
package channel
// #include <channel.h>
import "C"
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math"
"unsafe"
)
type RustBuffer = C.RustBuffer
type RustBufferI interface {
AsReader() *bytes.Reader
Free()
ToGoBytes() []byte
Data() unsafe.Pointer
Len() int
Capacity() int
}
func RustBufferFromExternal(b RustBufferI) RustBuffer {
return RustBuffer{
capacity: C.int(b.Capacity()),
len: C.int(b.Len()),
data: (*C.uchar)(b.Data()),
}
}
func (cb RustBuffer) Capacity() int {
return int(cb.capacity)
}
func (cb RustBuffer) Len() int {
return int(cb.len)
}
func (cb RustBuffer) Data() unsafe.Pointer {
return unsafe.Pointer(cb.data)
}
func (cb RustBuffer) AsReader() *bytes.Reader {
b := unsafe.Slice((*byte)(cb.data), C.int(cb.len))
return bytes.NewReader(b)
}
func (cb RustBuffer) Free() {
rustCall(func(status *C.RustCallStatus) bool {
C.ffi_channel_rustbuffer_free(cb, status)
return false
})
}
func (cb RustBuffer) ToGoBytes() []byte {
return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len))
}
func stringToRustBuffer(str string) RustBuffer {
return bytesToRustBuffer([]byte(str))
}
func bytesToRustBuffer(b []byte) RustBuffer {
if len(b) == 0 {
return RustBuffer{}
}
// We can pass the pointer along here, as it is pinned
// for the duration of this call
foreign := C.ForeignBytes{
len: C.int(len(b)),
data: (*C.uchar)(unsafe.Pointer(&b[0])),
}
return rustCall(func(status *C.RustCallStatus) RustBuffer {
return C.ffi_channel_rustbuffer_from_bytes(foreign, status)
})
}
type BufLifter[GoType any] interface {
Lift(value RustBufferI) GoType
}
type BufLowerer[GoType any] interface {
Lower(value GoType) RustBuffer
}
type FfiConverter[GoType any, FfiType any] interface {
Lift(value FfiType) GoType
Lower(value GoType) FfiType
}
type BufReader[GoType any] interface {
Read(reader io.Reader) GoType
}
type BufWriter[GoType any] interface {
Write(writer io.Writer, value GoType)
}
type FfiRustBufConverter[GoType any, FfiType any] interface {
FfiConverter[GoType, FfiType]
BufReader[GoType]
}
func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer {
// This might be not the most efficient way but it does not require knowing allocation size
// beforehand
var buffer bytes.Buffer
bufWriter.Write(&buffer, value)
bytes, err := io.ReadAll(&buffer)
if err != nil {
panic(fmt.Errorf("reading written data: %w", err))
}
return bytesToRustBuffer(bytes)
}
func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType {
defer rbuf.Free()
reader := rbuf.AsReader()
item := bufReader.Read(reader)
if reader.Len() > 0 {
// TODO: Remove this
leftover, _ := io.ReadAll(reader)
panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover)))
}
return item
}
func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) {
var status C.RustCallStatus
returnValue := callback(&status)
err := checkCallStatus(converter, status)
return returnValue, err
}
func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error {
switch status.code {
case 0:
return nil
case 1:
return converter.Lift(status.errorBuf)
case 2:
// when the rust code sees a panic, it tries to construct a rustbuffer
// with the message. but if that code panics, then it just sends back
// an empty buffer.
if status.errorBuf.len > 0 {
panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf)))
} else {
panic(fmt.Errorf("Rust panicked while handling Rust panic"))
}
default:
return fmt.Errorf("unknown status code: %d", status.code)
}
}
func checkCallStatusUnknown(status C.RustCallStatus) error {
switch status.code {
case 0:
return nil
case 1:
panic(fmt.Errorf("function not returning an error returned an error"))
case 2:
// when the rust code sees a panic, it tries to construct a rustbuffer
// with the message. but if that code panics, then it just sends back
// an empty buffer.
if status.errorBuf.len > 0 {
panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf)))
} else {
panic(fmt.Errorf("Rust panicked while handling Rust panic"))
}
default:
return fmt.Errorf("unknown status code: %d", status.code)
}
}
func rustCall[U any](callback func(*C.RustCallStatus) U) U {
returnValue, err := rustCallWithError(nil, callback)
if err != nil {
panic(err)
}
return returnValue
}
func writeInt8(writer io.Writer, value int8) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeUint8(writer io.Writer, value uint8) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeInt16(writer io.Writer, value int16) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeUint16(writer io.Writer, value uint16) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeInt32(writer io.Writer, value int32) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeUint32(writer io.Writer, value uint32) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeInt64(writer io.Writer, value int64) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeUint64(writer io.Writer, value uint64) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeFloat32(writer io.Writer, value float32) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func writeFloat64(writer io.Writer, value float64) {
if err := binary.Write(writer, binary.BigEndian, value); err != nil {
panic(err)
}
}
func readInt8(reader io.Reader) int8 {
var result int8
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readUint8(reader io.Reader) uint8 {
var result uint8
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readInt16(reader io.Reader) int16 {
var result int16
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readUint16(reader io.Reader) uint16 {
var result uint16
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readInt32(reader io.Reader) int32 {
var result int32
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readUint32(reader io.Reader) uint32 {
var result uint32
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readInt64(reader io.Reader) int64 {
var result int64
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readUint64(reader io.Reader) uint64 {
var result uint64
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readFloat32(reader io.Reader) float32 {
var result float32
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func readFloat64(reader io.Reader) float64 {
var result float64
if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
panic(err)
}
return result
}
func init() {
uniffiCheckChecksums()
}
func uniffiCheckChecksums() {
// Get the bindings contract version from our ComponentInterface
bindingsContractVersion := 24
// Get the scaffolding contract version by calling the into the dylib
scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t {
return C.ffi_channel_uniffi_contract_version(uniffiStatus)
})
if bindingsContractVersion != int(scaffoldingContractVersion) {
// If this happens try cleaning and rebuilding your project
panic("channel: UniFFI contract version mismatch")
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_double_ratchet_decrypt(uniffiStatus)
})
if checksum != 57128 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_double_ratchet_decrypt: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_double_ratchet_encrypt(uniffiStatus)
})
if checksum != 10167 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_double_ratchet_encrypt: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_new_double_ratchet(uniffiStatus)
})
if checksum != 21249 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_new_double_ratchet: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_new_triple_ratchet(uniffiStatus)
})
if checksum != 11118 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_new_triple_ratchet: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_decrypt(uniffiStatus)
})
if checksum != 56417 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_decrypt: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_encrypt(uniffiStatus)
})
if checksum != 63768 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_encrypt: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_init_round_1(uniffiStatus)
})
if checksum != 48593 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_init_round_1: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_init_round_2(uniffiStatus)
})
if checksum != 55359 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_init_round_2: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_init_round_3(uniffiStatus)
})
if checksum != 50330 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_init_round_3: UniFFI API checksum mismatch")
}
}
{
checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
return C.uniffi_channel_checksum_func_triple_ratchet_init_round_4(uniffiStatus)
})
if checksum != 58513 {
// If this happens try cleaning and rebuilding your project
panic("channel: uniffi_channel_checksum_func_triple_ratchet_init_round_4: UniFFI API checksum mismatch")
}
}
}
type FfiConverterUint8 struct{}
var FfiConverterUint8INSTANCE = FfiConverterUint8{}
func (FfiConverterUint8) Lower(value uint8) C.uint8_t {
return C.uint8_t(value)
}
func (FfiConverterUint8) Write(writer io.Writer, value uint8) {
writeUint8(writer, value)
}
func (FfiConverterUint8) Lift(value C.uint8_t) uint8 {
return uint8(value)
}
func (FfiConverterUint8) Read(reader io.Reader) uint8 {
return readUint8(reader)
}
type FfiDestroyerUint8 struct{}
func (FfiDestroyerUint8) Destroy(_ uint8) {}
type FfiConverterUint64 struct{}
var FfiConverterUint64INSTANCE = FfiConverterUint64{}
func (FfiConverterUint64) Lower(value uint64) C.uint64_t {
return C.uint64_t(value)
}
func (FfiConverterUint64) Write(writer io.Writer, value uint64) {
writeUint64(writer, value)
}
func (FfiConverterUint64) Lift(value C.uint64_t) uint64 {
return uint64(value)
}
func (FfiConverterUint64) Read(reader io.Reader) uint64 {
return readUint64(reader)
}
type FfiDestroyerUint64 struct{}
func (FfiDestroyerUint64) Destroy(_ uint64) {}
type FfiConverterBool struct{}
var FfiConverterBoolINSTANCE = FfiConverterBool{}
func (FfiConverterBool) Lower(value bool) C.int8_t {
if value {
return C.int8_t(1)
}
return C.int8_t(0)
}
func (FfiConverterBool) Write(writer io.Writer, value bool) {
if value {
writeInt8(writer, 1)
} else {
writeInt8(writer, 0)
}
}
func (FfiConverterBool) Lift(value C.int8_t) bool {
return value != 0
}
func (FfiConverterBool) Read(reader io.Reader) bool {
return readInt8(reader) != 0
}
type FfiDestroyerBool struct{}
func (FfiDestroyerBool) Destroy(_ bool) {}
type FfiConverterString struct{}
var FfiConverterStringINSTANCE = FfiConverterString{}
func (FfiConverterString) Lift(rb RustBufferI) string {
defer rb.Free()
reader := rb.AsReader()
b, err := io.ReadAll(reader)
if err != nil {
panic(fmt.Errorf("reading reader: %w", err))
}
return string(b)
}
func (FfiConverterString) Read(reader io.Reader) string {
length := readInt32(reader)
buffer := make([]byte, length)
read_length, err := reader.Read(buffer)
if err != nil {
panic(err)
}
if read_length != int(length) {
panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length))
}
return string(buffer)
}
func (FfiConverterString) Lower(value string) RustBuffer {
return stringToRustBuffer(value)
}
func (FfiConverterString) Write(writer io.Writer, value string) {
if len(value) > math.MaxInt32 {
panic("String is too large to fit into Int32")
}
writeInt32(writer, int32(len(value)))
write_length, err := io.WriteString(writer, value)
if err != nil {
panic(err)
}
if write_length != len(value) {
panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length))
}
}
type FfiDestroyerString struct{}
func (FfiDestroyerString) Destroy(_ string) {}
type DoubleRatchetStateAndEnvelope struct {
RatchetState string
Envelope string
}
func (r *DoubleRatchetStateAndEnvelope) Destroy() {
FfiDestroyerString{}.Destroy(r.RatchetState)
FfiDestroyerString{}.Destroy(r.Envelope)
}
type FfiConverterTypeDoubleRatchetStateAndEnvelope struct{}
var FfiConverterTypeDoubleRatchetStateAndEnvelopeINSTANCE = FfiConverterTypeDoubleRatchetStateAndEnvelope{}
func (c FfiConverterTypeDoubleRatchetStateAndEnvelope) Lift(rb RustBufferI) DoubleRatchetStateAndEnvelope {
return LiftFromRustBuffer[DoubleRatchetStateAndEnvelope](c, rb)
}
func (c FfiConverterTypeDoubleRatchetStateAndEnvelope) Read(reader io.Reader) DoubleRatchetStateAndEnvelope {
return DoubleRatchetStateAndEnvelope{
FfiConverterStringINSTANCE.Read(reader),
FfiConverterStringINSTANCE.Read(reader),
}
}
func (c FfiConverterTypeDoubleRatchetStateAndEnvelope) Lower(value DoubleRatchetStateAndEnvelope) RustBuffer {
return LowerIntoRustBuffer[DoubleRatchetStateAndEnvelope](c, value)
}
func (c FfiConverterTypeDoubleRatchetStateAndEnvelope) Write(writer io.Writer, value DoubleRatchetStateAndEnvelope) {
FfiConverterStringINSTANCE.Write(writer, value.RatchetState)
FfiConverterStringINSTANCE.Write(writer, value.Envelope)
}
type FfiDestroyerTypeDoubleRatchetStateAndEnvelope struct{}
func (_ FfiDestroyerTypeDoubleRatchetStateAndEnvelope) Destroy(value DoubleRatchetStateAndEnvelope) {
value.Destroy()
}
type DoubleRatchetStateAndMessage struct {
RatchetState string
Message []uint8
}
func (r *DoubleRatchetStateAndMessage) Destroy() {
FfiDestroyerString{}.Destroy(r.RatchetState)
FfiDestroyerSequenceUint8{}.Destroy(r.Message)
}
type FfiConverterTypeDoubleRatchetStateAndMessage struct{}
var FfiConverterTypeDoubleRatchetStateAndMessageINSTANCE = FfiConverterTypeDoubleRatchetStateAndMessage{}
func (c FfiConverterTypeDoubleRatchetStateAndMessage) Lift(rb RustBufferI) DoubleRatchetStateAndMessage {
return LiftFromRustBuffer[DoubleRatchetStateAndMessage](c, rb)
}
func (c FfiConverterTypeDoubleRatchetStateAndMessage) Read(reader io.Reader) DoubleRatchetStateAndMessage {
return DoubleRatchetStateAndMessage{
FfiConverterStringINSTANCE.Read(reader),
FfiConverterSequenceUint8INSTANCE.Read(reader),
}
}
func (c FfiConverterTypeDoubleRatchetStateAndMessage) Lower(value DoubleRatchetStateAndMessage) RustBuffer {
return LowerIntoRustBuffer[DoubleRatchetStateAndMessage](c, value)
}
func (c FfiConverterTypeDoubleRatchetStateAndMessage) Write(writer io.Writer, value DoubleRatchetStateAndMessage) {
FfiConverterStringINSTANCE.Write(writer, value.RatchetState)
FfiConverterSequenceUint8INSTANCE.Write(writer, value.Message)
}
type FfiDestroyerTypeDoubleRatchetStateAndMessage struct{}
func (_ FfiDestroyerTypeDoubleRatchetStateAndMessage) Destroy(value DoubleRatchetStateAndMessage) {
value.Destroy()
}
type TripleRatchetStateAndEnvelope struct {
RatchetState string
Envelope string
}
func (r *TripleRatchetStateAndEnvelope) Destroy() {
FfiDestroyerString{}.Destroy(r.RatchetState)
FfiDestroyerString{}.Destroy(r.Envelope)
}
type FfiConverterTypeTripleRatchetStateAndEnvelope struct{}
var FfiConverterTypeTripleRatchetStateAndEnvelopeINSTANCE = FfiConverterTypeTripleRatchetStateAndEnvelope{}
func (c FfiConverterTypeTripleRatchetStateAndEnvelope) Lift(rb RustBufferI) TripleRatchetStateAndEnvelope {
return LiftFromRustBuffer[TripleRatchetStateAndEnvelope](c, rb)
}
func (c FfiConverterTypeTripleRatchetStateAndEnvelope) Read(reader io.Reader) TripleRatchetStateAndEnvelope {
return TripleRatchetStateAndEnvelope{
FfiConverterStringINSTANCE.Read(reader),
FfiConverterStringINSTANCE.Read(reader),
}
}
func (c FfiConverterTypeTripleRatchetStateAndEnvelope) Lower(value TripleRatchetStateAndEnvelope) RustBuffer {
return LowerIntoRustBuffer[TripleRatchetStateAndEnvelope](c, value)
}
func (c FfiConverterTypeTripleRatchetStateAndEnvelope) Write(writer io.Writer, value TripleRatchetStateAndEnvelope) {
FfiConverterStringINSTANCE.Write(writer, value.RatchetState)
FfiConverterStringINSTANCE.Write(writer, value.Envelope)
}
type FfiDestroyerTypeTripleRatchetStateAndEnvelope struct{}
func (_ FfiDestroyerTypeTripleRatchetStateAndEnvelope) Destroy(value TripleRatchetStateAndEnvelope) {
value.Destroy()
}
type TripleRatchetStateAndMessage struct {
RatchetState string
Message []uint8
}
func (r *TripleRatchetStateAndMessage) Destroy() {
FfiDestroyerString{}.Destroy(r.RatchetState)
FfiDestroyerSequenceUint8{}.Destroy(r.Message)
}
type FfiConverterTypeTripleRatchetStateAndMessage struct{}
var FfiConverterTypeTripleRatchetStateAndMessageINSTANCE = FfiConverterTypeTripleRatchetStateAndMessage{}
func (c FfiConverterTypeTripleRatchetStateAndMessage) Lift(rb RustBufferI) TripleRatchetStateAndMessage {
return LiftFromRustBuffer[TripleRatchetStateAndMessage](c, rb)
}
func (c FfiConverterTypeTripleRatchetStateAndMessage) Read(reader io.Reader) TripleRatchetStateAndMessage {
return TripleRatchetStateAndMessage{
FfiConverterStringINSTANCE.Read(reader),
FfiConverterSequenceUint8INSTANCE.Read(reader),
}
}
func (c FfiConverterTypeTripleRatchetStateAndMessage) Lower(value TripleRatchetStateAndMessage) RustBuffer {
return LowerIntoRustBuffer[TripleRatchetStateAndMessage](c, value)
}
func (c FfiConverterTypeTripleRatchetStateAndMessage) Write(writer io.Writer, value TripleRatchetStateAndMessage) {
FfiConverterStringINSTANCE.Write(writer, value.RatchetState)
FfiConverterSequenceUint8INSTANCE.Write(writer, value.Message)
}
type FfiDestroyerTypeTripleRatchetStateAndMessage struct{}
func (_ FfiDestroyerTypeTripleRatchetStateAndMessage) Destroy(value TripleRatchetStateAndMessage) {
value.Destroy()
}
type TripleRatchetStateAndMetadata struct {
RatchetState string
Metadata map[string]string
}
func (r *TripleRatchetStateAndMetadata) Destroy() {
FfiDestroyerString{}.Destroy(r.RatchetState)
FfiDestroyerMapStringString{}.Destroy(r.Metadata)
}
type FfiConverterTypeTripleRatchetStateAndMetadata struct{}
var FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE = FfiConverterTypeTripleRatchetStateAndMetadata{}
func (c FfiConverterTypeTripleRatchetStateAndMetadata) Lift(rb RustBufferI) TripleRatchetStateAndMetadata {
return LiftFromRustBuffer[TripleRatchetStateAndMetadata](c, rb)
}
func (c FfiConverterTypeTripleRatchetStateAndMetadata) Read(reader io.Reader) TripleRatchetStateAndMetadata {
return TripleRatchetStateAndMetadata{
FfiConverterStringINSTANCE.Read(reader),
FfiConverterMapStringStringINSTANCE.Read(reader),
}
}
func (c FfiConverterTypeTripleRatchetStateAndMetadata) Lower(value TripleRatchetStateAndMetadata) RustBuffer {
return LowerIntoRustBuffer[TripleRatchetStateAndMetadata](c, value)
}
func (c FfiConverterTypeTripleRatchetStateAndMetadata) Write(writer io.Writer, value TripleRatchetStateAndMetadata) {
FfiConverterStringINSTANCE.Write(writer, value.RatchetState)
FfiConverterMapStringStringINSTANCE.Write(writer, value.Metadata)
}
type FfiDestroyerTypeTripleRatchetStateAndMetadata struct{}
func (_ FfiDestroyerTypeTripleRatchetStateAndMetadata) Destroy(value TripleRatchetStateAndMetadata) {
value.Destroy()
}
type FfiConverterSequenceUint8 struct{}
var FfiConverterSequenceUint8INSTANCE = FfiConverterSequenceUint8{}
func (c FfiConverterSequenceUint8) Lift(rb RustBufferI) []uint8 {
return LiftFromRustBuffer[[]uint8](c, rb)
}
func (c FfiConverterSequenceUint8) Read(reader io.Reader) []uint8 {
length := readInt32(reader)
if length == 0 {
return nil
}
result := make([]uint8, 0, length)
for i := int32(0); i < length; i++ {
result = append(result, FfiConverterUint8INSTANCE.Read(reader))
}
return result
}
func (c FfiConverterSequenceUint8) Lower(value []uint8) RustBuffer {
return LowerIntoRustBuffer[[]uint8](c, value)
}
func (c FfiConverterSequenceUint8) Write(writer io.Writer, value []uint8) {
if len(value) > math.MaxInt32 {
panic("[]uint8 is too large to fit into Int32")
}
writeInt32(writer, int32(len(value)))
for _, item := range value {
FfiConverterUint8INSTANCE.Write(writer, item)
}
}
type FfiDestroyerSequenceUint8 struct{}
func (FfiDestroyerSequenceUint8) Destroy(sequence []uint8) {
for _, value := range sequence {
FfiDestroyerUint8{}.Destroy(value)
}
}
type FfiConverterSequenceSequenceUint8 struct{}
var FfiConverterSequenceSequenceUint8INSTANCE = FfiConverterSequenceSequenceUint8{}
func (c FfiConverterSequenceSequenceUint8) Lift(rb RustBufferI) [][]uint8 {
return LiftFromRustBuffer[[][]uint8](c, rb)
}
func (c FfiConverterSequenceSequenceUint8) Read(reader io.Reader) [][]uint8 {
length := readInt32(reader)
if length == 0 {
return nil
}
result := make([][]uint8, 0, length)
for i := int32(0); i < length; i++ {
result = append(result, FfiConverterSequenceUint8INSTANCE.Read(reader))
}
return result
}
func (c FfiConverterSequenceSequenceUint8) Lower(value [][]uint8) RustBuffer {
return LowerIntoRustBuffer[[][]uint8](c, value)
}
func (c FfiConverterSequenceSequenceUint8) Write(writer io.Writer, value [][]uint8) {
if len(value) > math.MaxInt32 {
panic("[][]uint8 is too large to fit into Int32")
}
writeInt32(writer, int32(len(value)))
for _, item := range value {
FfiConverterSequenceUint8INSTANCE.Write(writer, item)
}
}
type FfiDestroyerSequenceSequenceUint8 struct{}
func (FfiDestroyerSequenceSequenceUint8) Destroy(sequence [][]uint8) {
for _, value := range sequence {
FfiDestroyerSequenceUint8{}.Destroy(value)
}
}
type FfiConverterMapStringString struct{}
var FfiConverterMapStringStringINSTANCE = FfiConverterMapStringString{}
func (c FfiConverterMapStringString) Lift(rb RustBufferI) map[string]string {
return LiftFromRustBuffer[map[string]string](c, rb)
}
func (_ FfiConverterMapStringString) Read(reader io.Reader) map[string]string {
result := make(map[string]string)
length := readInt32(reader)
for i := int32(0); i < length; i++ {
key := FfiConverterStringINSTANCE.Read(reader)
value := FfiConverterStringINSTANCE.Read(reader)
result[key] = value
}
return result
}
func (c FfiConverterMapStringString) Lower(value map[string]string) RustBuffer {
return LowerIntoRustBuffer[map[string]string](c, value)
}
func (_ FfiConverterMapStringString) Write(writer io.Writer, mapValue map[string]string) {
if len(mapValue) > math.MaxInt32 {
panic("map[string]string is too large to fit into Int32")
}
writeInt32(writer, int32(len(mapValue)))
for key, value := range mapValue {
FfiConverterStringINSTANCE.Write(writer, key)
FfiConverterStringINSTANCE.Write(writer, value)
}
}
type FfiDestroyerMapStringString struct{}
func (_ FfiDestroyerMapStringString) Destroy(mapValue map[string]string) {
for key, value := range mapValue {
FfiDestroyerString{}.Destroy(key)
FfiDestroyerString{}.Destroy(value)
}
}
func DoubleRatchetDecrypt(ratchetStateAndEnvelope DoubleRatchetStateAndEnvelope) DoubleRatchetStateAndMessage {
return FfiConverterTypeDoubleRatchetStateAndMessageINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_double_ratchet_decrypt(FfiConverterTypeDoubleRatchetStateAndEnvelopeINSTANCE.Lower(ratchetStateAndEnvelope), _uniffiStatus)
}))
}
func DoubleRatchetEncrypt(ratchetStateAndMessage DoubleRatchetStateAndMessage) DoubleRatchetStateAndEnvelope {
return FfiConverterTypeDoubleRatchetStateAndEnvelopeINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_double_ratchet_encrypt(FfiConverterTypeDoubleRatchetStateAndMessageINSTANCE.Lower(ratchetStateAndMessage), _uniffiStatus)
}))
}
func NewDoubleRatchet(sessionKey []uint8, sendingHeaderKey []uint8, nextReceivingHeaderKey []uint8, isSender bool, sendingEphemeralPrivateKey []uint8, receivingEphemeralKey []uint8) string {
return FfiConverterStringINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_new_double_ratchet(FfiConverterSequenceUint8INSTANCE.Lower(sessionKey), FfiConverterSequenceUint8INSTANCE.Lower(sendingHeaderKey), FfiConverterSequenceUint8INSTANCE.Lower(nextReceivingHeaderKey), FfiConverterBoolINSTANCE.Lower(isSender), FfiConverterSequenceUint8INSTANCE.Lower(sendingEphemeralPrivateKey), FfiConverterSequenceUint8INSTANCE.Lower(receivingEphemeralKey), _uniffiStatus)
}))
}
func NewTripleRatchet(peers [][]uint8, peerKey []uint8, identityKey []uint8, signedPreKey []uint8, threshold uint64, asyncDkgRatchet bool) TripleRatchetStateAndMetadata {
return FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_new_triple_ratchet(FfiConverterSequenceSequenceUint8INSTANCE.Lower(peers), FfiConverterSequenceUint8INSTANCE.Lower(peerKey), FfiConverterSequenceUint8INSTANCE.Lower(identityKey), FfiConverterSequenceUint8INSTANCE.Lower(signedPreKey), FfiConverterUint64INSTANCE.Lower(threshold), FfiConverterBoolINSTANCE.Lower(asyncDkgRatchet), _uniffiStatus)
}))
}
func TripleRatchetDecrypt(ratchetStateAndEnvelope TripleRatchetStateAndEnvelope) TripleRatchetStateAndMessage {
return FfiConverterTypeTripleRatchetStateAndMessageINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_decrypt(FfiConverterTypeTripleRatchetStateAndEnvelopeINSTANCE.Lower(ratchetStateAndEnvelope), _uniffiStatus)
}))
}
func TripleRatchetEncrypt(ratchetStateAndMessage TripleRatchetStateAndMessage) TripleRatchetStateAndEnvelope {
return FfiConverterTypeTripleRatchetStateAndEnvelopeINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_encrypt(FfiConverterTypeTripleRatchetStateAndMessageINSTANCE.Lower(ratchetStateAndMessage), _uniffiStatus)
}))
}
func TripleRatchetInitRound1(ratchetStateAndMetadata TripleRatchetStateAndMetadata) TripleRatchetStateAndMetadata {
return FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_init_round_1(FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lower(ratchetStateAndMetadata), _uniffiStatus)
}))
}
func TripleRatchetInitRound2(ratchetStateAndMetadata TripleRatchetStateAndMetadata) TripleRatchetStateAndMetadata {
return FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_init_round_2(FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lower(ratchetStateAndMetadata), _uniffiStatus)
}))
}
func TripleRatchetInitRound3(ratchetStateAndMetadata TripleRatchetStateAndMetadata) TripleRatchetStateAndMetadata {
return FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_init_round_3(FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lower(ratchetStateAndMetadata), _uniffiStatus)
}))
}
func TripleRatchetInitRound4(ratchetStateAndMetadata TripleRatchetStateAndMetadata) TripleRatchetStateAndMetadata {
return FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
return C.uniffi_channel_fn_func_triple_ratchet_init_round_4(FfiConverterTypeTripleRatchetStateAndMetadataINSTANCE.Lower(ratchetStateAndMetadata), _uniffiStatus)
}))
}

View File

@ -0,0 +1,475 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#include <stdbool.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6
#ifndef UNIFFI_SHARED_HEADER_V6
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V6
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V6
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
typedef struct RustBuffer {
int32_t capacity;
int32_t len;
uint8_t *data;
} RustBuffer;
typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *);
// Task defined in Rust that Go executes
typedef void (*RustTaskCallback)(const void *, int8_t);
// Callback to execute Rust tasks using a Go routine
//
// Args:
// executor: ForeignExecutor lowered into a uint64_t value
// delay: Delay in MS
// task: RustTaskCallback to call
// task_data: data to pass the task callback
typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *);
typedef struct ForeignBytes {
int32_t len;
const uint8_t *data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// Continuation callback for UniFFI Futures
typedef void (*RustFutureContinuation)(void * , int8_t);
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
// Needed because we can't execute the callback directly from go.
void cgo_rust_task_callback_bridge_channel(RustTaskCallback, const void *, int8_t);
int8_t uniffiForeignExecutorCallbackchannel(uint64_t, uint32_t, RustTaskCallback, void*);
void uniffiFutureContinuationCallbackchannel(void*, int8_t);
RustBuffer uniffi_channel_fn_func_double_ratchet_decrypt(
RustBuffer ratchet_state_and_envelope,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_double_ratchet_encrypt(
RustBuffer ratchet_state_and_message,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_new_double_ratchet(
RustBuffer session_key,
RustBuffer sending_header_key,
RustBuffer next_receiving_header_key,
int8_t is_sender,
RustBuffer sending_ephemeral_private_key,
RustBuffer receiving_ephemeral_key,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_new_triple_ratchet(
RustBuffer peers,
RustBuffer peer_key,
RustBuffer identity_key,
RustBuffer signed_pre_key,
uint64_t threshold,
int8_t async_dkg_ratchet,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_decrypt(
RustBuffer ratchet_state_and_envelope,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_encrypt(
RustBuffer ratchet_state_and_message,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_init_round_1(
RustBuffer ratchet_state_and_metadata,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_init_round_2(
RustBuffer ratchet_state_and_metadata,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_init_round_3(
RustBuffer ratchet_state_and_metadata,
RustCallStatus* out_status
);
RustBuffer uniffi_channel_fn_func_triple_ratchet_init_round_4(
RustBuffer ratchet_state_and_metadata,
RustCallStatus* out_status
);
RustBuffer ffi_channel_rustbuffer_alloc(
int32_t size,
RustCallStatus* out_status
);
RustBuffer ffi_channel_rustbuffer_from_bytes(
ForeignBytes bytes,
RustCallStatus* out_status
);
void ffi_channel_rustbuffer_free(
RustBuffer buf,
RustCallStatus* out_status
);
RustBuffer ffi_channel_rustbuffer_reserve(
RustBuffer buf,
int32_t additional,
RustCallStatus* out_status
);
void ffi_channel_rust_future_continuation_callback_set(
RustFutureContinuation callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_u8(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_u8(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_u8(
void* handle,
RustCallStatus* out_status
);
uint8_t ffi_channel_rust_future_complete_u8(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_i8(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_i8(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_i8(
void* handle,
RustCallStatus* out_status
);
int8_t ffi_channel_rust_future_complete_i8(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_u16(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_u16(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_u16(
void* handle,
RustCallStatus* out_status
);
uint16_t ffi_channel_rust_future_complete_u16(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_i16(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_i16(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_i16(
void* handle,
RustCallStatus* out_status
);
int16_t ffi_channel_rust_future_complete_i16(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_u32(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_u32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_u32(
void* handle,
RustCallStatus* out_status
);
uint32_t ffi_channel_rust_future_complete_u32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_i32(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_i32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_i32(
void* handle,
RustCallStatus* out_status
);
int32_t ffi_channel_rust_future_complete_i32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_u64(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_u64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_u64(
void* handle,
RustCallStatus* out_status
);
uint64_t ffi_channel_rust_future_complete_u64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_i64(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_i64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_i64(
void* handle,
RustCallStatus* out_status
);
int64_t ffi_channel_rust_future_complete_i64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_f32(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_f32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_f32(
void* handle,
RustCallStatus* out_status
);
float ffi_channel_rust_future_complete_f32(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_f64(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_f64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_f64(
void* handle,
RustCallStatus* out_status
);
double ffi_channel_rust_future_complete_f64(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_pointer(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_pointer(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_pointer(
void* handle,
RustCallStatus* out_status
);
void* ffi_channel_rust_future_complete_pointer(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_rust_buffer(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_rust_buffer(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_rust_buffer(
void* handle,
RustCallStatus* out_status
);
RustBuffer ffi_channel_rust_future_complete_rust_buffer(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_poll_void(
void* handle,
void* uniffi_callback,
RustCallStatus* out_status
);
void ffi_channel_rust_future_cancel_void(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_free_void(
void* handle,
RustCallStatus* out_status
);
void ffi_channel_rust_future_complete_void(
void* handle,
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_double_ratchet_decrypt(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_double_ratchet_encrypt(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_new_double_ratchet(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_new_triple_ratchet(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_decrypt(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_encrypt(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_init_round_1(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_init_round_2(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_init_round_3(
RustCallStatus* out_status
);
uint16_t uniffi_channel_checksum_func_triple_ratchet_init_round_4(
RustCallStatus* out_status
);
uint32_t ffi_channel_uniffi_contract_version(
RustCallStatus* out_status
);

17
channel/go.mod Normal file
View File

@ -0,0 +1,17 @@
module source.quilibrium.com/quilibrium/monorepo/channel
go 1.20
// A necessary hack until source.quilibrium.com is open to all
replace source.quilibrium.com/quilibrium/monorepo/nekryptology => ../nekryptology
require github.com/stretchr/testify v1.9.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/sys v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
source.quilibrium.com/quilibrium/monorepo/nekryptology v0.0.0-00010101000000-000000000000 // indirect
)

13
channel/go.sum Normal file
View File

@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

17
channel/test.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
set -euxo pipefail
# Run tests for the channel package. Takes care of linking the native Channel library.
# Assumes that the Channel library has been built by running the generate.sh script in the same directory.
ROOT_DIR="${ROOT_DIR:-$( cd "$(dirname "$(realpath "$( dirname "${BASH_SOURCE[0]}" )")")" >/dev/null 2>&1 && pwd )}"
NODE_DIR="$ROOT_DIR/channel"
BINARIES_DIR="$ROOT_DIR/target/release"
# Link the native Channel library and execute tests
pushd "$NODE_DIR" > /dev/null
CGO_LDFLAGS="-L$BINARIES_DIR -lchannel -ldl" \
CGO_ENABLED=1 \
GOEXPERIMENT=arenas \
go test "$@"

View File

@ -363,7 +363,7 @@ pub fn prove_raw(
subz = big::BIG::modadd(&subz, &big::BIG::modneg(&z, &big::BIG::new_ints(&rom::CURVE_ORDER)), &big::BIG::new_ints(&rom::CURVE_ORDER));
let mut subzinv = subz.clone();
subzinv.invmodp(&big::BIG::new_ints(&rom::CURVE_ORDER));
let mut o = big::BIG::new_int(1);
let o = big::BIG::new_int(1);
let mut oinv = o.clone();
oinv.invmodp(&big::BIG::new_ints(&rom::CURVE_ORDER));
let divisors: Vec<big::BIG> = vec![
@ -436,7 +436,29 @@ pub fn verify_raw(
let y = big::BIG::frombytes(data);
let c = ecp::ECP::frombytes(commit);
if c.is_infinity() || c.equals(&ecp::ECP::generator()) {
return false;
}
let p = ecp::ECP::frombytes(proof);
if p.is_infinity() || p.equals(&ecp::ECP::generator()) {
return false;
}
if poly_size > 1024 {
let mut xc = c.clone();
xc.sub(&bls::singleton().FFTBLS48581[&poly_size][index as usize].clone().mul(&y));
let mut check = c.clone();
check.neg();
let yp = &bls::singleton().CeremonyBLS48581G2[1].clone().mul(&y);
let mut r = pair8::initmp();
pair8::another(&mut r, &bls::singleton().CeremonyBLS48581G2[1], &check);
pair8::another(&mut r, &yp, &bls::singleton().FFTBLS48581[&poly_size][index as usize]);
pair8::another(&mut r, &bls::singleton().CeremonyBLS48581G2[1], &xc);
let mut v = pair8::miller(&mut r);
v = pair8::fexp(&v);
return v.isunity();
}
return verify(
&c,

7
crates/channel/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "channel"
version = "0.1.0"

34
crates/channel/Cargo.toml Normal file
View File

@ -0,0 +1,34 @@
[package]
name = "channel"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib", "staticlib"]
name = "channel"
[dependencies]
base64 = "0.22.1"
hex = "0.4.3"
serde_json = "1.0.117"
ed448-goldilocks-plus = "0.11.2"
rand = "0.8.5"
sha2 = "0.10.8"
hkdf = "0.12.4"
aes-gcm = "0.10.3"
thiserror = "1.0.63"
hmac = "0.12.1"
serde = "1.0.208"
lazy_static = "1.5.0"
uniffi = { version= "0.25", features = ["cli"]}
[dev-dependencies]
criterion = { version = "0.4", features = ["html_reports"] }
rand = "0.8.5"
[build-dependencies]
uniffi = { version = "0.25", features = [ "build" ] }
[[bench]]
name = "bench_channel"
harness = false

View File

@ -0,0 +1,9 @@
use rand::Rng;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn criterion_benchmark(c: &mut Criterion) {
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

5
crates/channel/build.rs Normal file
View File

@ -0,0 +1,5 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
uniffi::generate_scaffolding("src/lib.udl").expect("uniffi generation failed");
}

991
crates/channel/src/lib.rs Normal file
View File

@ -0,0 +1,991 @@
use base64::prelude::*;
use std::collections::HashMap;
use ed448_goldilocks_plus::{elliptic_curve::group::GroupEncoding, EdwardsPoint, Scalar};
use protocols::{doubleratchet::{DoubleRatchetParticipant, P2PChannelEnvelope}, tripleratchet::{PeerInfo, TripleRatchetParticipant}};
pub(crate) mod protocols;
pub struct DoubleRatchetStateAndEnvelope {
pub ratchet_state: String,
pub envelope: String,
}
pub struct DoubleRatchetStateAndMessage {
pub ratchet_state: String,
pub message: Vec<u8>,
}
pub struct TripleRatchetStateAndMetadata {
pub ratchet_state: String,
pub metadata: HashMap<String, String>,
}
pub struct TripleRatchetStateAndEnvelope {
pub ratchet_state: String,
pub envelope: String,
}
pub struct TripleRatchetStateAndMessage {
pub ratchet_state: String,
pub message: Vec<u8>,
}
pub fn new_double_ratchet(session_key: &Vec<u8>, sending_header_key: &Vec<u8>, next_receiving_header_key: &Vec<u8>, is_sender: bool, sending_ephemeral_private_key: &Vec<u8>, receiving_ephemeral_key: &Vec<u8>) -> String {
if sending_ephemeral_private_key.len() != 56 {
return "".to_string();
}
if receiving_ephemeral_key.len() != 57 {
return "".to_string();
}
let mut sending_ephemeral_private_key_bytes = [0u8; 56];
sending_ephemeral_private_key_bytes.copy_from_slice(&sending_ephemeral_private_key);
let mut receiving_ephemeral_key_bytes = [0u8; 57];
receiving_ephemeral_key_bytes.copy_from_slice(&receiving_ephemeral_key);
let sending_key = Scalar::from_bytes(&sending_ephemeral_private_key_bytes.into());
let receiving_key = EdwardsPoint::from_bytes(&receiving_ephemeral_key_bytes.into()).into_option();
if receiving_key.is_none() {
return "".to_string();
}
let participant = DoubleRatchetParticipant::new(
&session_key,
&sending_header_key,
&next_receiving_header_key,
true,
sending_key,
receiving_key.unwrap(),
);
if participant.is_err() {
return "".to_string();
}
let json = participant.unwrap().to_json();
if json.is_err() {
return "".to_string();
}
return json.unwrap();
}
pub fn double_ratchet_encrypt(ratchet_state_and_message: DoubleRatchetStateAndMessage) -> DoubleRatchetStateAndEnvelope {
let ratchet_state = ratchet_state_and_message.ratchet_state.clone();
let participant = DoubleRatchetParticipant::from_json(ratchet_state.clone());
if participant.is_err() {
return DoubleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let mut dr = participant.unwrap();
let envelope = dr.ratchet_encrypt(&ratchet_state_and_message.message);
if envelope.is_err() {
return DoubleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let participant_json = dr.to_json();
if participant_json.is_err() {
return DoubleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let envelope_json = envelope.unwrap().to_json();
if envelope_json.is_err() {
return DoubleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
return DoubleRatchetStateAndEnvelope{
ratchet_state: participant_json.unwrap(),
envelope: envelope_json.unwrap(),
};
}
pub fn double_ratchet_decrypt(ratchet_state_and_envelope: DoubleRatchetStateAndEnvelope) -> DoubleRatchetStateAndMessage {
let ratchet_state = ratchet_state_and_envelope.ratchet_state.clone();
let participant = DoubleRatchetParticipant::from_json(ratchet_state.clone());
let envelope = P2PChannelEnvelope::from_json(ratchet_state_and_envelope.envelope);
if participant.is_err() || envelope.is_err() {
return DoubleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
let mut dr = participant.unwrap();
let message = dr.ratchet_decrypt(&envelope.unwrap());
if message.is_err() {
return DoubleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
let participant_json = dr.to_json();
if participant_json.is_err() {
return DoubleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
return DoubleRatchetStateAndMessage{
ratchet_state: participant_json.unwrap(),
message: message.unwrap(),
};
}
pub fn new_triple_ratchet(peers: &Vec<Vec<u8>>, peer_key: &Vec<u8>, identity_key: &Vec<u8>, signed_pre_key: &Vec<u8>, threshold: u64, async_dkg_ratchet: bool) -> TripleRatchetStateAndMetadata {
if peer_key.len() != 56 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
if identity_key.len() != 56 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
if signed_pre_key.len() != 56 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
if peers.len() < 3 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
if threshold > peers.len() as u64 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
let mut peer_key_bytes = [0u8; 56];
peer_key_bytes.copy_from_slice(&peer_key);
let mut identity_key_bytes = [0u8; 56];
identity_key_bytes.copy_from_slice(&identity_key);
let mut signed_pre_key_bytes = [0u8; 56];
signed_pre_key_bytes.copy_from_slice(&signed_pre_key);
let peer_key_scalar = Scalar::from_bytes(&peer_key_bytes.into());
let identity_key_scalar = Scalar::from_bytes(&identity_key_bytes.into());
let signed_pre_key_scalar = Scalar::from_bytes(&signed_pre_key_bytes.into());
let mut peerinfos = Vec::<PeerInfo>::new();
for pk in peers.iter() {
if pk.len() != 171 {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
peerinfos.push(PeerInfo{
public_key: pk[..57].into(),
identity_public_key: pk[57..114].into(),
signed_pre_public_key: pk[114..].into(),
});
}
let participant = TripleRatchetParticipant::new(
&peerinfos,
peer_key_scalar,
identity_key_scalar,
signed_pre_key_scalar,
threshold as usize,
async_dkg_ratchet,
);
if participant.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
let (tr, metadata) = participant.unwrap();
let participant_json = tr.to_json();
if participant_json.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: "".to_string(),
metadata: HashMap::new(),
};
}
let metadata_json = match metadata_to_json(&String::from(""), metadata) {
Ok(value) => value,
Err(value) => return value,
};
return TripleRatchetStateAndMetadata{
ratchet_state: participant_json.unwrap(),
metadata: metadata_json,
};
}
fn metadata_to_json(ratchet_state: &String, metadata: HashMap<Vec<u8>, P2PChannelEnvelope>) -> Result<HashMap<String, String>, TripleRatchetStateAndMetadata> {
let mut metadata_json = HashMap::<String, String>::new();
for (k,v) in metadata {
let env = v.to_json();
if env.is_err() {
return Err(TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state.to_string(),
metadata: HashMap::new(),
});
}
metadata_json.insert(BASE64_STANDARD.encode(k), env.unwrap());
}
Ok(metadata_json)
}
fn json_to_metadata(ratchet_state_and_metadata: TripleRatchetStateAndMetadata, ratchet_state: &String) -> Result<HashMap<Vec<u8>, P2PChannelEnvelope>, TripleRatchetStateAndMetadata> {
let mut metadata = HashMap::<Vec<u8>, P2PChannelEnvelope>::new();
for (k,v) in ratchet_state_and_metadata.metadata {
let env = P2PChannelEnvelope::from_json(v);
let kb = BASE64_STANDARD.decode(k);
if env.is_err() || kb.is_err() {
return Err(TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state.clone(),
metadata: HashMap::new(),
});
}
metadata.insert(kb.unwrap(), env.unwrap());
}
Ok(metadata)
}
pub fn triple_ratchet_init_round_1(ratchet_state_and_metadata: TripleRatchetStateAndMetadata) -> TripleRatchetStateAndMetadata {
let ratchet_state = ratchet_state_and_metadata.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let metadata = match json_to_metadata(ratchet_state_and_metadata, &ratchet_state) {
Ok(value) => value,
Err(value) => return value,
};
let mut trp = tr.unwrap();
let result = trp.initialize(&metadata);
if result.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let metadata = result.unwrap();
let metadata_json = match metadata_to_json(&ratchet_state, metadata) {
Ok(value) => value,
Err(value) => return value,
};
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
return TripleRatchetStateAndMetadata{
ratchet_state: json.unwrap(),
metadata: metadata_json,
};
}
pub fn triple_ratchet_init_round_2(ratchet_state_and_metadata: TripleRatchetStateAndMetadata) -> TripleRatchetStateAndMetadata {
let ratchet_state = ratchet_state_and_metadata.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let metadata = match json_to_metadata(ratchet_state_and_metadata, &ratchet_state) {
Ok(value) => value,
Err(value) => return value,
};
let mut trp = tr.unwrap();
let mut result = HashMap::<Vec<u8>, P2PChannelEnvelope>::new();
for (k, v) in metadata {
let r = trp.receive_poly_frag(&k, &v);
if r.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let opt = r.unwrap();
if opt.is_some() {
result = opt.unwrap();
}
}
let metadata_json = match metadata_to_json(&ratchet_state, result) {
Ok(value) => value,
Err(value) => return value,
};
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
return TripleRatchetStateAndMetadata{
ratchet_state: json.unwrap(),
metadata: metadata_json,
};
}
pub fn triple_ratchet_init_round_3(ratchet_state_and_metadata: TripleRatchetStateAndMetadata) -> TripleRatchetStateAndMetadata {
let ratchet_state = ratchet_state_and_metadata.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let metadata = match json_to_metadata(ratchet_state_and_metadata, &ratchet_state) {
Ok(value) => value,
Err(value) => return value,
};
let mut trp = tr.unwrap();
let mut result = HashMap::<Vec<u8>, P2PChannelEnvelope>::new();
for (k, v) in metadata {
let r = trp.receive_commitment(&k, &v);
if r.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let opt = r.unwrap();
if opt.is_some() {
result = opt.unwrap();
}
}
let metadata_json = match metadata_to_json(&ratchet_state, result) {
Ok(value) => value,
Err(value) => return value,
};
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
return TripleRatchetStateAndMetadata{
ratchet_state: json.unwrap(),
metadata: metadata_json,
};
}
pub fn triple_ratchet_init_round_4(ratchet_state_and_metadata: TripleRatchetStateAndMetadata) -> TripleRatchetStateAndMetadata {
let ratchet_state = ratchet_state_and_metadata.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
let metadata = match json_to_metadata(ratchet_state_and_metadata, &ratchet_state) {
Ok(value) => value,
Err(value) => return value,
};
let mut trp = tr.unwrap();
let mut result = HashMap::<Vec<u8>, P2PChannelEnvelope>::new();
for (k, v) in metadata {
let r = trp.recombine(&k, &v);
if r.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
}
let metadata_json = match metadata_to_json(&ratchet_state, result) {
Ok(value) => value,
Err(value) => return value,
};
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndMetadata{
ratchet_state: ratchet_state,
metadata: HashMap::new(),
};
}
return TripleRatchetStateAndMetadata{
ratchet_state: json.unwrap(),
metadata: metadata_json,
};
}
pub fn triple_ratchet_encrypt(ratchet_state_and_message: TripleRatchetStateAndMessage) -> TripleRatchetStateAndEnvelope {
let ratchet_state = ratchet_state_and_message.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let mut trp = tr.unwrap();
let result = trp.ratchet_encrypt(&ratchet_state_and_message.message);
if result.is_err() {
return TripleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let envelope = result.unwrap();
let envelope_json = envelope.to_json();
if envelope_json.is_err() {
return TripleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndEnvelope{
ratchet_state: ratchet_state,
envelope: "".to_string(),
};
}
return TripleRatchetStateAndEnvelope{
ratchet_state: json.unwrap(),
envelope: envelope_json.unwrap(),
};
}
pub fn triple_ratchet_decrypt(ratchet_state_and_envelope: TripleRatchetStateAndEnvelope) -> TripleRatchetStateAndMessage {
let ratchet_state = ratchet_state_and_envelope.ratchet_state.clone();
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return TripleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
let mut trp = tr.unwrap();
let env = P2PChannelEnvelope::from_json(ratchet_state_and_envelope.envelope);
if env.is_err() {
return TripleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
let result = trp.ratchet_decrypt(&env.unwrap());
if result.is_err() {
return TripleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
let message = result.unwrap().0;
let json = trp.to_json();
if json.is_err() {
return TripleRatchetStateAndMessage{
ratchet_state: ratchet_state,
message: vec![],
};
}
return TripleRatchetStateAndMessage{
ratchet_state: json.unwrap(),
message: message,
};
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use ed448_goldilocks_plus::{Scalar, elliptic_curve::Group, EdwardsPoint};
use protocols::{doubleratchet::P2PChannelEnvelope, tripleratchet::{PeerInfo, TripleRatchetParticipant}};
#[test]
fn test_four_party_triple_ratchet_communication() {
let mut rng = rand::thread_rng();
let mut keys: Vec<(Scalar, Scalar, Scalar)> = (0..4)
.map(|_| (Scalar::random(&mut rng), Scalar::random(&mut rng), Scalar::random(&mut rng)))
.collect();
keys.sort_by(|a, b| (a.0 * EdwardsPoint::generator()).compress().to_bytes().cmp(&(b.0 * EdwardsPoint::generator()).compress().to_bytes()));
let mut peer_infos: Vec<PeerInfo> = keys
.iter()
.map(|(peer_key, identity_key, signed_pre_key)| PeerInfo {
public_key: (peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
identity_public_key: (identity_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
signed_pre_public_key: (signed_pre_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
})
.collect();
// mirror the internal order so we can use by index:
peer_infos.sort_by(|a, b| a.public_key.cmp(&b.public_key));
let mut participants: Vec<TripleRatchetParticipant> = Vec::new();
let mut init_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut frag_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut commitment_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut reveal_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
for i in 0..4 {
init_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
frag_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
commitment_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
reveal_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
}
for i in 0..4 {
let other_peers: Vec<PeerInfo> = peer_infos.iter().enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, peer)| peer.clone())
.collect();
let (participant, init_msg) = TripleRatchetParticipant::new(
&other_peers,
keys[i].0.clone(),
keys[i].1.clone(),
keys[i].2.clone(),
3,
false,
).unwrap();
participants.push(participant);
for (j, env) in init_msg.iter() {
init_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
// Exchange initial messages and get frags:
for i in 0..4 {
let result = participants[i].initialize(&init_messages[&peer_infos[i].public_key.clone()]).unwrap();
for (j, env) in result.iter() {
frag_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
// Exchange frags and receive commitments once all frags have been distributed:
for i in 0..4 {
for (p, envelope) in frag_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(out) = participants[i].receive_poly_frag(&p, envelope).unwrap() {
for (j, env) in out.iter() {
commitment_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
}
// Exchange commitments and produce reveals:
for i in 0..4 {
for (p, envelope) in commitment_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(reveal_msg) = participants[i].receive_commitment(&p, envelope).unwrap() {
for (j, env) in reveal_msg.iter() {
reveal_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
}
// Collect reveals and confirm zkpoks are valid, produce group key:
for i in 0..4 {
for (j, env) in reveal_messages[&peer_infos[i].public_key.clone()].iter() {
participants[i].recombine(j, &env.clone()).unwrap();
}
}
// Test sending and receiving messages
let test_messages = [
"hello there",
"general kenobi",
"you are a bold one",
"*mechanical laughter*",
];
for (i, message) in test_messages.iter().enumerate() {
let encrypted = participants[i].ratchet_encrypt(message.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted = participants[j].ratchet_decrypt(&encrypted).unwrap();
assert_eq!(message.as_bytes(), decrypted.0.as_slice(), "Message decryption failed for Participant {}", j);
}
}
}
for _ in 0..5 {
for i in 0..4 {
let message1 = format!("test 1 {}", i + 1);
let message2 = format!("test 2 {}", i + 1);
let encrypted1 = participants[i].ratchet_encrypt(message1.as_bytes()).unwrap();
let encrypted2 = participants[i].ratchet_encrypt(message2.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted1 = participants[j].ratchet_decrypt(&encrypted1).unwrap();
assert_eq!(message1.as_bytes(), decrypted1.0.as_slice(), "Round message decryption failed for Participant {}", j);
let decrypted2 = participants[j].ratchet_decrypt(&encrypted2).unwrap();
assert_eq!(message2.as_bytes(), decrypted2.0.as_slice(), "Round message decryption failed for Participant {}", j);
}
}
}
}
}
#[test]
fn test_four_party_triple_ratchet_communication_with_serialization_each_step() {
let mut rng = rand::thread_rng();
let mut keys: Vec<(Scalar, Scalar, Scalar)> = (0..4)
.map(|_| (Scalar::random(&mut rng), Scalar::random(&mut rng), Scalar::random(&mut rng)))
.collect();
keys.sort_by(|a, b| (a.0 * EdwardsPoint::generator()).compress().to_bytes().cmp(&(b.0 * EdwardsPoint::generator()).compress().to_bytes()));
let mut peer_infos: Vec<PeerInfo> = keys
.iter()
.map(|(peer_key, identity_key, signed_pre_key)| PeerInfo {
public_key: (peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
identity_public_key: (identity_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
signed_pre_public_key: (signed_pre_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
})
.collect();
// mirror the internal order so we can use by index:
peer_infos.sort_by(|a, b| a.public_key.cmp(&b.public_key));
let mut participants: Vec<TripleRatchetParticipant> = Vec::new();
let mut init_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut frag_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut commitment_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut reveal_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
for i in 0..4 {
init_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
frag_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
commitment_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
reveal_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
}
for i in 0..4 {
let other_peers: Vec<PeerInfo> = peer_infos.iter().enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, peer)| peer.clone())
.collect();
let (participant, init_msg) = TripleRatchetParticipant::new(
&other_peers,
keys[i].0.clone(),
keys[i].1.clone(),
keys[i].2.clone(),
3,
false,
).unwrap();
for (j, env) in init_msg.iter() {
init_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
let participant_json = participant.to_json();
if participant_json.is_err() {
panic!("bad json");
}
participants.push(TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap());
}
// Exchange initial messages and get frags:
for i in 0..4 {
let result = participants[i].initialize(&init_messages[&peer_infos[i].public_key.clone()]).unwrap();
for (j, env) in result.iter() {
frag_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
// Exchange frags and receive commitments once all frags have been distributed:
for i in 0..4 {
for (p, envelope) in frag_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(out) = participants[i].receive_poly_frag(&p, envelope).unwrap() {
for (j, env) in out.iter() {
commitment_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
// Exchange commitments and produce reveals:
for i in 0..4 {
for (p, envelope) in commitment_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(reveal_msg) = participants[i].receive_commitment(&p, envelope).unwrap() {
for (j, env) in reveal_msg.iter() {
reveal_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
// Collect reveals and confirm zkpoks are valid, produce group key:
for i in 0..4 {
for (j, env) in reveal_messages[&peer_infos[i].public_key.clone()].iter() {
participants[i].recombine(j, &env.clone()).unwrap();
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
}
// Test sending and receiving messages
let test_messages = [
"hello there",
"general kenobi",
"you are a bold one",
"*mechanical laughter*",
];
for (i, message) in test_messages.iter().enumerate() {
let encrypted = participants[i].ratchet_encrypt(message.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted = participants[j].ratchet_decrypt(&encrypted).unwrap();
assert_eq!(message.as_bytes(), decrypted.0.as_slice(), "Message decryption failed for Participant {}", j);
}
}
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
for _ in 0..5 {
for i in 0..4 {
let message1 = format!("test 1 {}", i + 1);
let message2 = format!("test 2 {}", i + 1);
let encrypted1 = participants[i].ratchet_encrypt(message1.as_bytes()).unwrap();
let encrypted2 = participants[i].ratchet_encrypt(message2.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted1 = participants[j].ratchet_decrypt(&encrypted1).unwrap();
assert_eq!(message1.as_bytes(), decrypted1.0.as_slice(), "Round message decryption failed for Participant {}", j);
let decrypted2 = participants[j].ratchet_decrypt(&encrypted2).unwrap();
assert_eq!(message2.as_bytes(), decrypted2.0.as_slice(), "Round message decryption failed for Participant {}", j);
}
}
let participant_json = participants[i].to_json();
participants[i] = TripleRatchetParticipant::from_json(&participant_json.unwrap()).unwrap();
}
}
}
#[test]
fn test_four_party_async_triple_ratchet_communication() {
let mut rng = rand::thread_rng();
let mut keys: Vec<(Scalar, Scalar, Scalar)> = (0..4)
.map(|_| (Scalar::random(&mut rng), Scalar::random(&mut rng), Scalar::random(&mut rng)))
.collect();
keys.sort_by(|a, b| (a.0 * EdwardsPoint::generator()).compress().to_bytes().cmp(&(b.0 * EdwardsPoint::generator()).compress().to_bytes()));
let mut peer_infos: Vec<PeerInfo> = keys
.iter()
.map(|(peer_key, identity_key, signed_pre_key)| PeerInfo {
public_key: (peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
identity_public_key: (identity_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
signed_pre_public_key: (signed_pre_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
})
.collect();
// mirror the internal order so we can use by index:
peer_infos.sort_by(|a, b| a.public_key.cmp(&b.public_key));
let mut participants: Vec<TripleRatchetParticipant> = Vec::new();
let mut init_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut frag_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut commitment_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
let mut reveal_messages: HashMap<Vec<u8>, HashMap<Vec<u8>, P2PChannelEnvelope>> = HashMap::new();
for i in 0..4 {
init_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
frag_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
commitment_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
reveal_messages.insert(peer_infos[i].public_key.clone(), HashMap::new());
}
for i in 0..4 {
let other_peers: Vec<PeerInfo> = peer_infos.iter().enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, peer)| peer.clone())
.collect();
let (participant, init_msg) = TripleRatchetParticipant::new(
&other_peers,
keys[i].0.clone(),
keys[i].1.clone(),
keys[i].2.clone(),
2,
true,
).unwrap();
participants.push(participant);
for (j, env) in init_msg.iter() {
init_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
// Exchange initial messages and get frags:
for i in 0..4 {
let result = participants[i].initialize(&init_messages[&peer_infos[i].public_key.clone()]).unwrap();
for (j, env) in result.iter() {
frag_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
// Exchange frags and receive commitments once all frags have been distributed:
for i in 0..4 {
for (p, envelope) in frag_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(out) = participants[i].receive_poly_frag(&p, envelope).unwrap() {
for (j, env) in out.iter() {
commitment_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
}
// Exchange commitments and produce reveals:
for i in 0..4 {
for (p, envelope) in commitment_messages[&peer_infos[i].public_key.clone()].iter() {
if let Some(reveal_msg) = participants[i].receive_commitment(&p, envelope).unwrap() {
for (j, env) in reveal_msg.iter() {
reveal_messages.get_mut(j).unwrap().insert(peer_infos[i].public_key.clone(), env.clone());
}
}
}
}
// Collect reveals and confirm zkpoks are valid, produce group key:
for i in 0..4 {
for (j, env) in reveal_messages[&peer_infos[i].public_key.clone()].iter() {
participants[i].recombine(j, &env.clone()).unwrap();
}
}
// Test sending and receiving messages
let test_messages = [
"hello there",
"general kenobi",
"you are a bold one",
"*mechanical laughter*",
];
for (i, message) in test_messages.iter().enumerate() {
let encrypted = participants[i].ratchet_encrypt(message.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted = participants[j].ratchet_decrypt(&encrypted).unwrap();
assert_eq!(message.as_bytes(), decrypted.0.as_slice(), "Message decryption failed for Participant {}", j);
}
}
}
for _ in 0..5 {
for i in 0..4 {
let message1 = format!("test 1 {}", i + 1);
let message2 = format!("test 2 {}", i + 1);
let encrypted1 = participants[i].ratchet_encrypt(message1.as_bytes()).unwrap();
let encrypted2 = participants[i].ratchet_encrypt(message2.as_bytes()).unwrap();
for j in 0..4 {
if i != j {
let decrypted1 = participants[j].ratchet_decrypt(&encrypted1).unwrap();
assert_eq!(message1.as_bytes(), decrypted1.0.as_slice(), "Round message decryption failed for Participant {}", j);
let decrypted2 = participants[j].ratchet_decrypt(&encrypted2).unwrap();
assert_eq!(message2.as_bytes(), decrypted2.0.as_slice(), "Round message decryption failed for Participant {}", j);
}
}
}
}
}
}

View File

@ -0,0 +1,38 @@
namespace channel {
string new_double_ratchet([ByRef] sequence<u8> session_key, [ByRef] sequence<u8> sending_header_key, [ByRef] sequence<u8> next_receiving_header_key, boolean is_sender, [ByRef] sequence<u8> sending_ephemeral_private_key, [ByRef] sequence<u8> receiving_ephemeral_key);
DoubleRatchetStateAndEnvelope double_ratchet_encrypt(DoubleRatchetStateAndMessage ratchet_state_and_message);
DoubleRatchetStateAndMessage double_ratchet_decrypt(DoubleRatchetStateAndEnvelope ratchet_state_and_envelope);
TripleRatchetStateAndMetadata new_triple_ratchet([ByRef] sequence<sequence<u8>> peers, [ByRef] sequence<u8> peer_key, [ByRef] sequence<u8> identity_key, [ByRef] sequence<u8> signed_pre_key, u64 threshold, boolean async_dkg_ratchet);
TripleRatchetStateAndMetadata triple_ratchet_init_round_1(TripleRatchetStateAndMetadata ratchet_state_and_metadata);
TripleRatchetStateAndMetadata triple_ratchet_init_round_2(TripleRatchetStateAndMetadata ratchet_state_and_metadata);
TripleRatchetStateAndMetadata triple_ratchet_init_round_3(TripleRatchetStateAndMetadata ratchet_state_and_metadata);
TripleRatchetStateAndMetadata triple_ratchet_init_round_4(TripleRatchetStateAndMetadata ratchet_state_and_metadata);
TripleRatchetStateAndEnvelope triple_ratchet_encrypt(TripleRatchetStateAndMessage ratchet_state_and_message);
TripleRatchetStateAndMessage triple_ratchet_decrypt(TripleRatchetStateAndEnvelope ratchet_state_and_envelope);
};
dictionary DoubleRatchetStateAndEnvelope {
string ratchet_state;
string envelope;
};
dictionary DoubleRatchetStateAndMessage {
string ratchet_state;
sequence<u8> message;
};
dictionary TripleRatchetStateAndMetadata {
string ratchet_state;
record<string, string> metadata;
};
dictionary TripleRatchetStateAndEnvelope {
string ratchet_state;
string envelope;
};
dictionary TripleRatchetStateAndMessage {
string ratchet_state;
sequence<u8> message;
};

View File

@ -0,0 +1,622 @@
use base64::prelude::*;
use ed448_goldilocks_plus::elliptic_curve::group::GroupEncoding;
use ed448_goldilocks_plus::elliptic_curve::ops::MulByGenerator;
use ed448_goldilocks_plus::{subtle, CompressedEdwardsY, EdwardsPoint, Scalar};
use rand::rngs::OsRng;
use rand::RngCore;
use sha2::Sha512;
use hkdf::Hkdf;
use aes_gcm::{Aes256Gcm, Nonce};
use aes_gcm::aead::{Aead, Payload};
use std::collections::HashMap;
use std::error;
use subtle::ConstantTimeEq;
use serde::{Serialize, Deserialize};
const DOUBLE_RATCHET_PROTOCOL_VERSION: u16 = 1;
const DOUBLE_RATCHET_PROTOCOL: u16 = 1 << 8 + DOUBLE_RATCHET_PROTOCOL_VERSION;
const CHAIN_KEY: u8 = 0x01;
const MESSAGE_KEY: u8 = 0x02;
const AEAD_KEY: u8 = 0x03;
pub struct DoubleRatchetParticipant {
sending_ephemeral_private_key: Scalar,
receiving_ephemeral_key: EdwardsPoint,
root_key: Vec<u8>,
sending_chain_key: Vec<u8>,
current_sending_header_key: Vec<u8>,
current_receiving_header_key: Vec<u8>,
next_sending_header_key: Vec<u8>,
next_receiving_header_key: Vec<u8>,
receiving_chain_key: Vec<u8>,
current_sending_chain_length: u32,
previous_sending_chain_length: u32,
current_receiving_chain_length: u32,
previous_receiving_chain_length: u32,
skipped_keys_map: HashMap<Vec<u8>, HashMap<u32, Vec<u8>>>,
}
#[derive(Serialize, Deserialize)]
pub struct DoubleRatchetParticipantJson {
pub sending_ephemeral_private_key: String,
pub receiving_ephemeral_key: String,
pub root_key: String,
pub sending_chain_key: String,
pub current_sending_header_key: String,
pub current_receiving_header_key: String,
pub next_sending_header_key: String,
pub next_receiving_header_key: String,
pub receiving_chain_key: String,
pub current_sending_chain_length: u32,
pub previous_sending_chain_length: u32,
pub current_receiving_chain_length: u32,
pub previous_receiving_chain_length: u32,
pub skipped_keys_map: HashMap<String, HashMap<u32, String>>,
}
#[derive(Clone, Debug)]
pub struct MessageCiphertext {
pub ciphertext: Vec<u8>,
pub initialization_vector: Vec<u8>,
pub associated_data: Option<Vec<u8>>,
}
#[derive(Clone, Debug)]
pub struct P2PChannelEnvelope {
pub protocol_identifier: u16,
pub message_header: MessageCiphertext,
pub message_body: MessageCiphertext,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct P2PChannelEnvelopeJson {
pub protocol_identifier: u16,
pub message_header: MessageCiphertextJson,
pub message_body: MessageCiphertextJson,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageCiphertextJson {
pub ciphertext: String,
pub initialization_vector: String,
pub associated_data: Option<String>,
}
impl P2PChannelEnvelope {
pub fn to_json(&self) -> Result<String, serde_json::Error> {
let envelope = P2PChannelEnvelopeJson{
protocol_identifier: self.protocol_identifier,
message_header: MessageCiphertextJson{
ciphertext: BASE64_STANDARD.encode(&self.message_header.ciphertext),
initialization_vector: BASE64_STANDARD.encode(&self.message_header.initialization_vector),
associated_data: self.message_header.associated_data.clone().map(|a| BASE64_STANDARD.encode(a)),
},
message_body: MessageCiphertextJson{
ciphertext: BASE64_STANDARD.encode(&self.message_body.ciphertext),
initialization_vector: BASE64_STANDARD.encode(&self.message_body.initialization_vector),
associated_data: self.message_body.associated_data.clone().map(|a| BASE64_STANDARD.encode(a)),
},
};
serde_json::to_string(&envelope)
}
pub fn from_json(envelope_json: String) -> Result<P2PChannelEnvelope, Box<dyn std::error::Error>> {
let envelope: Result<P2PChannelEnvelopeJson, serde_json::Error> = serde_json::from_str(&envelope_json);
if envelope.is_err() {
return Err(Box::new(envelope.unwrap_err()));
}
let e = envelope.unwrap();
let header_ciphertext = BASE64_STANDARD.decode(e.message_header.ciphertext)?;
let header_initialization_vector = BASE64_STANDARD.decode(e.message_header.initialization_vector)?;
let header_associated_data = e.message_header.associated_data.map(|a| BASE64_STANDARD.decode(a)).transpose()?;
let ciphertext = BASE64_STANDARD.decode(e.message_body.ciphertext)?;
let initialization_vector = BASE64_STANDARD.decode(e.message_body.initialization_vector)?;
let associated_data = e.message_body.associated_data.map(|a| BASE64_STANDARD.decode(a)).transpose()?;
Ok(P2PChannelEnvelope{
protocol_identifier: e.protocol_identifier,
message_header: MessageCiphertext{
ciphertext: header_ciphertext,
initialization_vector: header_initialization_vector,
associated_data: header_associated_data,
},
message_body: MessageCiphertext{
ciphertext: ciphertext,
initialization_vector: initialization_vector,
associated_data: associated_data,
},
})
}
}
impl DoubleRatchetParticipant {
pub fn new(
session_key: &[u8],
sending_header_key: &[u8],
next_receiving_header_key: &[u8],
is_sender: bool,
sending_ephemeral_private_key: Scalar,
receiving_ephemeral_key: EdwardsPoint,
) -> Result<Self, Box<dyn std::error::Error>> {
let mut participant = DoubleRatchetParticipant {
sending_ephemeral_private_key,
receiving_ephemeral_key,
root_key: vec![],
sending_chain_key: vec![],
current_sending_header_key: sending_header_key.to_vec(),
current_receiving_header_key: vec![],
next_sending_header_key: vec![],
next_receiving_header_key: next_receiving_header_key.to_vec(),
receiving_chain_key: vec![],
current_sending_chain_length: 0,
previous_sending_chain_length: 0,
current_receiving_chain_length: 0,
previous_receiving_chain_length: 0,
skipped_keys_map: HashMap::new(),
};
if is_sender {
let dh_output = receiving_ephemeral_key * sending_ephemeral_private_key;
let hkdf = Hkdf::<Sha512>::new(Some(session_key), &dh_output.compress().to_bytes());
let mut rkck = [0u8; 96];
let err = hkdf.expand(b"quilibrium-double-ratchet", &mut rkck);
if err.is_err() {
return Err("invalid length".into());
}
participant.root_key = rkck[..32].to_vec();
participant.sending_chain_key = rkck[32..64].to_vec();
participant.next_sending_header_key = rkck[64..].to_vec();
} else {
participant.root_key = session_key.to_vec();
participant.next_sending_header_key = next_receiving_header_key.to_vec();
participant.next_receiving_header_key = sending_header_key.to_vec();
}
Ok(participant)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
let mut skipped_keys_map = HashMap::<String, HashMap<u32, String>>::new();
for (k, v) in &self.skipped_keys_map {
let kb = BASE64_STANDARD.encode(k);
let mut val = HashMap::<u32, String>::new();
for (kk, vv) in v {
let vvb = BASE64_STANDARD.encode(vv);
val.insert(*kk, vvb);
}
skipped_keys_map.insert(kb, val);
}
let participant = DoubleRatchetParticipantJson{
sending_ephemeral_private_key: BASE64_STANDARD.encode(self.sending_ephemeral_private_key.to_bytes()),
receiving_ephemeral_key: BASE64_STANDARD.encode(self.receiving_ephemeral_key.compress().to_bytes()),
root_key: BASE64_STANDARD.encode(&self.root_key),
sending_chain_key: BASE64_STANDARD.encode(&self.sending_chain_key),
current_sending_header_key: BASE64_STANDARD.encode(&self.current_sending_header_key),
current_receiving_header_key: BASE64_STANDARD.encode(&self.current_receiving_header_key),
next_sending_header_key: BASE64_STANDARD.encode(&self.next_sending_header_key),
next_receiving_header_key: BASE64_STANDARD.encode(&self.next_receiving_header_key),
receiving_chain_key: BASE64_STANDARD.encode(&self.receiving_chain_key),
current_sending_chain_length: self.current_sending_chain_length,
previous_sending_chain_length: self.previous_sending_chain_length,
current_receiving_chain_length: self.current_receiving_chain_length,
previous_receiving_chain_length: self.previous_receiving_chain_length,
skipped_keys_map: skipped_keys_map,
};
serde_json::to_string(&participant)
}
pub fn from_json(participant_json: String) -> Result<DoubleRatchetParticipant, Box<dyn std::error::Error>> {
let json: Result<DoubleRatchetParticipantJson, serde_json::Error> = serde_json::from_str(&participant_json);
match json {
Ok(participant) => {
let sending_ephemeral_private_key_bytes = BASE64_STANDARD.decode(participant.sending_ephemeral_private_key)?;
let receiving_ephemeral_key_bytes = BASE64_STANDARD.decode(participant.receiving_ephemeral_key)?;
let root_key = BASE64_STANDARD.decode(participant.root_key)?;
let sending_chain_key = BASE64_STANDARD.decode(participant.sending_chain_key)?;
let current_sending_header_key = BASE64_STANDARD.decode(participant.current_sending_header_key)?;
let current_receiving_header_key = BASE64_STANDARD.decode(participant.current_receiving_header_key)?;
let next_sending_header_key = BASE64_STANDARD.decode(participant.next_sending_header_key)?;
let next_receiving_header_key = BASE64_STANDARD.decode(participant.next_receiving_header_key)?;
let receiving_chain_key = BASE64_STANDARD.decode(participant.receiving_chain_key)?;
let current_sending_chain_length = participant.current_sending_chain_length;
let previous_sending_chain_length = participant.previous_sending_chain_length;
let current_receiving_chain_length = participant.current_receiving_chain_length;
let previous_receiving_chain_length = participant.previous_receiving_chain_length;
let mut skipped_keys_map = HashMap::<Vec<u8>, HashMap<u32, Vec<u8>>>::new();
for (k, v) in participant.skipped_keys_map {
let kb = BASE64_STANDARD.decode(k)?;
let mut val = HashMap::<u32, Vec<u8>>::new();
for (kk, vv) in v {
let vvb = BASE64_STANDARD.decode(vv)?;
val.insert(kk, vvb);
}
skipped_keys_map.insert(kb, val);
}
if sending_ephemeral_private_key_bytes.len() != 56 || receiving_ephemeral_key_bytes.len() != 57 {
Err("invalid data".into())
} else {
let mut sending_ephemeral_private_key = [0u8; 56];
sending_ephemeral_private_key.copy_from_slice(&sending_ephemeral_private_key_bytes);
let mut receiving_ephemeral_key = [0u8; 57];
receiving_ephemeral_key.copy_from_slice(&receiving_ephemeral_key_bytes);
let receiving_ephemeral_ct = EdwardsPoint::from_bytes(&receiving_ephemeral_key.into());
if receiving_ephemeral_ct.is_none().into() {
Err("invalid data".into())
} else {
Ok(DoubleRatchetParticipant{
sending_ephemeral_private_key: Scalar::from_bytes(&sending_ephemeral_private_key),
receiving_ephemeral_key: receiving_ephemeral_ct.unwrap(),
root_key: root_key,
sending_chain_key: sending_chain_key,
current_sending_header_key: current_sending_header_key,
current_receiving_header_key: current_receiving_header_key,
next_sending_header_key: next_sending_header_key,
next_receiving_header_key: next_receiving_header_key,
receiving_chain_key: receiving_chain_key,
current_sending_chain_length: current_sending_chain_length,
previous_sending_chain_length: previous_sending_chain_length,
current_receiving_chain_length: current_receiving_chain_length,
previous_receiving_chain_length: previous_receiving_chain_length,
skipped_keys_map: skipped_keys_map,
})
}
}
}
Err(e) => {
Err(Box::new(e))
}
}
}
pub fn ratchet_encrypt(&mut self, message: &[u8]) -> Result<P2PChannelEnvelope, Box<dyn std::error::Error>> {
let mut envelope = P2PChannelEnvelope {
protocol_identifier: DOUBLE_RATCHET_PROTOCOL,
message_header: MessageCiphertext::default(),
message_body: MessageCiphertext::default(),
};
let (new_chain_key, message_key, aead_key) = ratchet_keys(&self.sending_chain_key);
self.sending_chain_key = new_chain_key;
let header = self.encode_header();
envelope.message_header = self.encrypt(&header, &self.current_sending_header_key, None)?;
envelope.message_body = self.encrypt(
message,
&message_key,
Some(&[&aead_key[..], &envelope.message_header.ciphertext[..]].concat()),
)?;
self.current_sending_chain_length += 1;
Ok(envelope)
}
pub fn ratchet_decrypt(&mut self, envelope: &P2PChannelEnvelope) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if let Some(plaintext) = self.try_skipped_message_keys(envelope)? {
return Ok(plaintext);
}
let (header, should_ratchet) = self.decrypt_header(&envelope.message_header, &self.current_receiving_header_key)?;
let (receiving_ephemeral_key, previous_receiving_chain_length, current_receiving_chain_length) =
self.decode_header(&header)?;
if should_ratchet {
self.skip_message_keys(previous_receiving_chain_length)?;
self.ratchet_ephemeral_keys(&receiving_ephemeral_key)?;
}
self.skip_message_keys(current_receiving_chain_length)?;
let (new_chain_key, message_key, aead_key) = ratchet_keys(&self.receiving_chain_key);
let plaintext = self.decrypt(
&envelope.message_body,
&message_key,
Some(&[&aead_key[..], &envelope.message_header.ciphertext[..]].concat()),
)?;
self.receiving_chain_key = new_chain_key;
self.current_receiving_chain_length += 1;
Ok(plaintext)
}
fn ratchet_ephemeral_keys(&mut self, new_receiving_ephemeral_key: &EdwardsPoint) -> Result<(), Box<dyn std::error::Error>> {
self.previous_sending_chain_length = self.current_sending_chain_length;
self.current_sending_chain_length = 0;
self.current_receiving_chain_length = 0;
self.current_sending_header_key = self.next_sending_header_key.clone();
self.current_receiving_header_key = self.next_receiving_header_key.clone();
self.receiving_ephemeral_key = *new_receiving_ephemeral_key;
// Perform DH and KDF to get new root key and receiving chain key
let dh_output = new_receiving_ephemeral_key * self.sending_ephemeral_private_key;
let hkdf = Hkdf::<Sha512>::new(Some(&self.root_key), &dh_output.compress().to_bytes());
let mut rkck = [0u8; 96];
hkdf.expand(b"quilibrium-double-ratchet", &mut rkck);
self.root_key = rkck[..32].to_vec();
self.receiving_chain_key = rkck[32..64].to_vec();
self.next_receiving_header_key = rkck[64..].to_vec();
// Generate new sending ephemeral key
self.sending_ephemeral_private_key = Scalar::random(&mut OsRng);
// Perform DH and KDF to get new root key and sending chain key
let dh_output = new_receiving_ephemeral_key * self.sending_ephemeral_private_key;
let hkdf = Hkdf::<Sha512>::new(Some(&self.root_key), &dh_output.compress().to_bytes());
let mut rkck2 = [0u8; 96];
hkdf.expand(b"quilibrium-double-ratchet", &mut rkck2);
self.root_key = rkck2[..32].to_vec();
self.sending_chain_key = rkck2[32..64].to_vec();
self.next_sending_header_key = rkck2[64..].to_vec();
Ok(())
}
fn try_skipped_message_keys(&self, envelope: &P2PChannelEnvelope) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
for (receiving_header_key, skipped_keys) in &self.skipped_keys_map {
if let Ok((header, _)) = self.decrypt_header(&envelope.message_header, receiving_header_key) {
let (_, _, current) = self.decode_header(&header)?;
if let Some(key_pair) = skipped_keys.get(&current) {
let message_key = &key_pair[..32];
let aead_key = &key_pair[32..];
return self.decrypt(
&envelope.message_body,
message_key,
Some(&[aead_key, &envelope.message_header.ciphertext[..]].concat()),
).map(Some);
}
}
}
Ok(None)
}
fn skip_message_keys(&mut self, until: u32) -> Result<(), Box<dyn std::error::Error>> {
if self.current_receiving_chain_length + 100 < until {
return Err("Skip limit exceeded".into());
}
if !self.receiving_chain_key.is_empty() {
while self.current_receiving_chain_length < until {
let (new_chain_key, message_key, aead_key) = ratchet_keys(&self.receiving_chain_key);
self.skipped_keys_map
.entry(self.current_receiving_header_key.clone())
.or_insert_with(HashMap::new)
.insert(self.current_receiving_chain_length, [&message_key[..], &aead_key[..]].concat());
self.receiving_chain_key = new_chain_key;
self.current_receiving_chain_length += 1;
}
}
Ok(())
}
fn encode_header(&self) -> Vec<u8> {
let mut header = Vec::new();
header.extend_from_slice(&EdwardsPoint::mul_by_generator(&self.sending_ephemeral_private_key).compress().to_bytes());
header.extend_from_slice(&self.previous_sending_chain_length.to_be_bytes());
header.extend_from_slice(&self.current_sending_chain_length.to_be_bytes());
header
}
fn decrypt_header(&self, ciphertext: &MessageCiphertext, receiving_header_key: &[u8])
-> Result<(Vec<u8>, bool), Box<dyn std::error::Error>> {
match self.decrypt(ciphertext, receiving_header_key, None) {
Ok(header) => Ok((header, false)),
Err(_) if receiving_header_key.ct_eq(self.current_receiving_header_key.as_slice()).into() => {
self.decrypt(ciphertext, &self.next_receiving_header_key, None)
.map(|header| (header, true))
},
Err(e) => Err(e),
}
}
fn decode_header(&self, header: &[u8]) -> Result<(EdwardsPoint, u32, u32), Box<dyn std::error::Error>> {
if header.len() < 57 { // 57 bytes for EdwardsPoint + 8 bytes for two u32
return Err("Malformed header".into());
}
let receiving_ephemeral_key = CompressedEdwardsY(header[..57].try_into().unwrap()).decompress();
if receiving_ephemeral_key.is_none().into() {
return Err("Malformed point".into());
}
let previous_receiving_chain_length = u32::from_be_bytes(header[57..61].try_into()?);
let current_receiving_chain_length = u32::from_be_bytes(header[61..65].try_into()?);
Ok((receiving_ephemeral_key.unwrap(), previous_receiving_chain_length, current_receiving_chain_length))
}
fn encrypt(&self, plaintext: &[u8], key: &[u8], associated_data: Option<&[u8]>)
-> Result<MessageCiphertext, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
let mut iv = [0u8; 12];
OsRng.fill_bytes(&mut iv);
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let nonce = Nonce::from_slice(&iv);
let mut associated_data = associated_data.unwrap_or(&[]);
let mut aad = [0u8; 32];
if associated_data.len() == 0 {
OsRng.fill_bytes(&mut aad);
associated_data = &aad
}
let ciphertext = cipher.encrypt(nonce, Payload{
msg: plaintext,
aad: associated_data,
}).map_err(|e| format!("Encryption failed: {}", e))?;
Ok(MessageCiphertext {
ciphertext,
initialization_vector: iv.to_vec(),
associated_data: Some(associated_data.to_vec()),
})
}
fn decrypt(&self, ciphertext: &MessageCiphertext, key: &[u8], associated_data: Option<&[u8]>)
-> Result<Vec<u8>, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
if key.len() != 32 {
return Err(format!("Invalid key length").into());
}
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let nonce = Nonce::from_slice(&ciphertext.initialization_vector);
let associated_data = associated_data.unwrap_or_else(|| ciphertext.associated_data.as_ref().unwrap());
cipher.decrypt(nonce, Payload{
msg: ciphertext.ciphertext.as_slice(),
aad: associated_data,
}).map_err(|e| format!("Decryption failed: {}", e).into())
}
pub fn rotate_sending_key(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.sending_ephemeral_private_key = Scalar::random(&mut OsRng);
self.ratchet_ephemeral_keys(&self.receiving_ephemeral_key.clone())
}
pub fn get_public_key(&self) -> EdwardsPoint {
EdwardsPoint::mul_by_generator(&self.sending_ephemeral_private_key)
}
}
fn ratchet_keys(input_key: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
use hmac::Mac;
let mut aead_key = [0u8; 64];
let mut message_key = [0u8; 64];
let mut chain_key = [0u8; 64];
let mut hmac_aead = hmac::Hmac::<Sha512>::new_from_slice(input_key).unwrap();
hmac_aead.update(&[AEAD_KEY]);
aead_key.copy_from_slice(&hmac_aead.finalize().into_bytes());
let mut hmac_message = hmac::Hmac::<Sha512>::new_from_slice(input_key).unwrap();
hmac_message.update(&[MESSAGE_KEY]);
message_key.copy_from_slice(&hmac_message.finalize().into_bytes());
let mut hmac_chain = hmac::Hmac::<Sha512>::new_from_slice(input_key).unwrap();
hmac_chain.update(&[CHAIN_KEY]);
chain_key.copy_from_slice(&hmac_chain.finalize().into_bytes());
(chain_key[..32].to_vec(), message_key[..32].to_vec(), aead_key[..32].to_vec())
}
// Implementation for MessageCiphertext
impl Default for MessageCiphertext {
fn default() -> Self {
MessageCiphertext {
ciphertext: Vec::new(),
initialization_vector: Vec::new(),
associated_data: None,
}
}
}
// Implementation for P2PChannelEnvelope
impl P2PChannelEnvelope {
pub fn new(protocol_identifier: u16) -> Self {
P2PChannelEnvelope {
protocol_identifier,
message_header: MessageCiphertext::default(),
message_body: MessageCiphertext::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed448_goldilocks_plus::Scalar;
#[test]
fn test_double_ratchet_communication() {
let session_key = [0u8; 32];
let sending_header_key = [1u8; 32];
let next_receiving_header_key = [2u8; 32];
let alice_ephemeral = Scalar::random(&mut OsRng);
let bob_ephemeral = Scalar::random(&mut OsRng);
let alice_public = EdwardsPoint::mul_by_generator(&alice_ephemeral);
let bob_public = EdwardsPoint::mul_by_generator(&bob_ephemeral);
let mut alice = DoubleRatchetParticipant::new(
&session_key,
&sending_header_key,
&next_receiving_header_key,
true,
alice_ephemeral,
bob_public,
).unwrap();
let mut bob = DoubleRatchetParticipant::new(
&session_key,
&sending_header_key,
&next_receiving_header_key,
false,
bob_ephemeral,
alice_public,
).unwrap();
// Test message exchange
let message = b"Hello, Bob!";
let envelope = alice.ratchet_encrypt(message).unwrap();
let decrypted = bob.ratchet_decrypt(&envelope).unwrap();
assert_eq!(message, decrypted.as_slice());
let response = b"Hello, Alice!";
let envelope = bob.ratchet_encrypt(response).unwrap();
let delayed = alice.ratchet_encrypt(b"force another step").unwrap();
let decrypted = alice.ratchet_decrypt(&envelope).unwrap();
assert_eq!(response, decrypted.as_slice());
// Test multiple messages
for _ in 0..5 {
let message = b"Secure communication test";
let envelope = alice.ratchet_encrypt(message).unwrap();
let decrypted = bob.ratchet_decrypt(&envelope).unwrap();
assert_eq!(message, decrypted.as_slice());
let response = b"Acknowledged";
let envelope = bob.ratchet_encrypt(response).unwrap();
let decrypted = alice.ratchet_decrypt(&envelope).unwrap();
assert_eq!(response, decrypted.as_slice());
}
let alice_json = alice.to_json().unwrap();
let bob_json = bob.to_json().unwrap();
let mut new_alice = DoubleRatchetParticipant::from_json(alice_json).unwrap();
let mut new_bob = DoubleRatchetParticipant::from_json(bob_json).unwrap();
// Test multiple messages
for _ in 0..5 {
let message = b"Secure communication test";
let envelope = new_alice.ratchet_encrypt(message).unwrap();
let decrypted = new_bob.ratchet_decrypt(&envelope).unwrap();
assert_eq!(message, decrypted.as_slice());
let response = b"Acknowledged";
let envelope = new_bob.ratchet_encrypt(response).unwrap();
let decrypted = new_alice.ratchet_decrypt(&envelope).unwrap();
assert_eq!(response, decrypted.as_slice());
}
let decrypted = new_bob.ratchet_decrypt(&delayed).unwrap();
assert_eq!(b"force another step", decrypted.as_slice());
}
}

View File

@ -0,0 +1,485 @@
use base64::prelude::*;
use std::{collections::HashMap, io::Read};
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha512};
use ed448_goldilocks_plus::{elliptic_curve::{group::GroupEncoding, Field, Group}, subtle::ConstantTimeEq, EdwardsPoint, Scalar};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FeldmanError {
#[error("Wrong round for Feldman operation")]
WrongRound,
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Crypto error: {0}")]
CryptoError(String),
}
#[derive(Clone, Copy, PartialEq)]
enum FeldmanRound {
Uninitialized,
Initialized,
Committed,
Revealed,
Reconstructed,
}
pub struct Feldman {
threshold: usize,
total: usize,
id: usize,
frags_for_counterparties: HashMap<usize, Vec<u8>>,
frags_from_counterparties: HashMap<usize, Scalar>,
zkpok: Option<Scalar>,
secret: Scalar,
scalar: Option<Scalar>,
generator: EdwardsPoint,
public_key: EdwardsPoint,
point: EdwardsPoint,
random_commitment_point: Option<EdwardsPoint>,
round: FeldmanRound,
zkcommits_from_counterparties: HashMap<usize, Vec<u8>>,
points_from_counterparties: HashMap<usize, EdwardsPoint>,
}
#[derive(Serialize, Deserialize)]
pub struct FeldmanJson {
threshold: usize,
total: usize,
id: usize,
frags_for_counterparties: HashMap<usize, String>,
frags_from_counterparties: HashMap<usize, String>,
zkpok: Option<String>,
secret: String,
scalar: Option<String>,
generator: String,
public_key: String,
point: String,
random_commitment_point: Option<String>,
round: usize,
zkcommits_from_counterparties: HashMap<usize, String>,
points_from_counterparties: HashMap<usize, String>,
}
#[derive(Serialize, Deserialize)]
pub struct FeldmanReveal {
point: Vec<u8>,
random_commitment_point: Vec<u8>,
zk_pok: Vec<u8>,
}
pub fn vec_to_array<const N: usize>(v: Vec<u8>) -> Result<[u8; N], Box<dyn std::error::Error>> {
if v.len() != N {
return Err(format!("Invalid length: expected {}, got {}", N, v.len()).into());
}
let mut arr: [u8; N] = [0u8; N];
arr.copy_from_slice(&v);
Ok(arr)
}
impl Feldman {
pub fn new(
threshold: usize,
total: usize,
id: usize,
secret: Scalar,
generator: EdwardsPoint,
) -> Self {
Feldman {
threshold,
total,
id,
frags_for_counterparties: HashMap::new(),
frags_from_counterparties: HashMap::new(),
zkpok: None,
secret,
scalar: None,
generator,
public_key: EdwardsPoint::generator(),
point: EdwardsPoint::generator(),
random_commitment_point: None,
round: FeldmanRound::Uninitialized,
zkcommits_from_counterparties: HashMap::new(),
points_from_counterparties: HashMap::new(),
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
let feldman_json = FeldmanJson {
threshold: self.threshold,
total: self.total,
id: self.id,
frags_for_counterparties: self.frags_for_counterparties.iter()
.map(|(&k, v)| (k, BASE64_STANDARD.encode(v)))
.collect(),
frags_from_counterparties: self.frags_from_counterparties.iter()
.map(|(&k, v)| (k, BASE64_STANDARD.encode(v.to_bytes())))
.collect(),
zkpok: self.zkpok.as_ref().map(|s| BASE64_STANDARD.encode(s.to_bytes())),
secret: BASE64_STANDARD.encode(self.secret.to_bytes()),
scalar: self.scalar.as_ref().map(|s| BASE64_STANDARD.encode(s.to_bytes())),
generator: BASE64_STANDARD.encode(self.generator.compress().to_bytes()),
public_key: BASE64_STANDARD.encode(self.public_key.compress().to_bytes()),
point: BASE64_STANDARD.encode(self.point.compress().to_bytes()),
random_commitment_point: self.random_commitment_point.as_ref()
.map(|p| BASE64_STANDARD.encode(p.compress().to_bytes())),
round: self.round as usize,
zkcommits_from_counterparties: self.zkcommits_from_counterparties.iter()
.map(|(&k, v)| (k, BASE64_STANDARD.encode(v)))
.collect(),
points_from_counterparties: self.points_from_counterparties.iter()
.map(|(&k, v)| (k, BASE64_STANDARD.encode(v.compress().to_bytes())))
.collect(),
};
serde_json::to_string(&feldman_json)
}
pub fn from_json(json: &str) -> Result<Self, Box<dyn std::error::Error>> {
let feldman_json: FeldmanJson = serde_json::from_str(json)?;
let frags_for_counterparties = feldman_json.frags_for_counterparties.into_iter()
.map(|(k, v)| Ok((k, BASE64_STANDARD.decode(v)?)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let frags_from_counterparties = feldman_json.frags_from_counterparties.into_iter()
.map(|(k, v)| {
let bytes = BASE64_STANDARD.decode(v)?;
Ok((k, Scalar::from_bytes(&vec_to_array::<56>(bytes)?)))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let mut zkpok: Option<Scalar> = None;
if feldman_json.zkpok.is_some() {
let bytes = BASE64_STANDARD.decode(feldman_json.zkpok.unwrap())?;
zkpok = Some(Scalar::from_bytes(&vec_to_array::<56>(bytes)?));
}
let secret_bytes = BASE64_STANDARD.decode(feldman_json.secret)?;
let secret = Scalar::from_bytes(&vec_to_array::<56>(secret_bytes)?);
let mut scalar: Option<Scalar> = None;
if feldman_json.scalar.is_some() {
let bytes = BASE64_STANDARD.decode(feldman_json.scalar.unwrap())?;
scalar = Some(Scalar::from_bytes(&vec_to_array::<56>(bytes)?));
}
let generator_bytes = BASE64_STANDARD.decode(feldman_json.generator)?;
let generator = EdwardsPoint::from_bytes(&vec_to_array::<57>(generator_bytes)?.into()).into_option().ok_or_else(|| FeldmanError::InvalidData("invalid data".into()))?;
let public_key_bytes = BASE64_STANDARD.decode(feldman_json.public_key)?;
let public_key = EdwardsPoint::from_bytes(&vec_to_array::<57>(public_key_bytes)?.into()).into_option().ok_or_else(|| FeldmanError::InvalidData("invalid data".into()))?;
let point_bytes = BASE64_STANDARD.decode(feldman_json.point)?;
let point = EdwardsPoint::from_bytes(&vec_to_array::<57>(point_bytes)?.into()).into_option().ok_or_else(|| FeldmanError::InvalidData("invalid data".into()))?;
let mut random_commitment_point: Option<EdwardsPoint> = None;
if feldman_json.random_commitment_point.is_some() {
let bytes = BASE64_STANDARD.decode(feldman_json.random_commitment_point.unwrap())?;
random_commitment_point = Some(EdwardsPoint::from_bytes(&vec_to_array::<57>(bytes)?.into()).into_option().ok_or_else(|| FeldmanError::InvalidData("invalid data".into()))?);
}
let zkcommits_from_counterparties = feldman_json.zkcommits_from_counterparties.into_iter()
.map(|(k, v)| Ok((k, BASE64_STANDARD.decode(v)?)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let points_from_counterparties = feldman_json.points_from_counterparties.into_iter()
.map(|(k, v)| {
Ok((k, EdwardsPoint::from_bytes(&vec_to_array::<57>(BASE64_STANDARD.decode(v)?)?.into()).into_option().ok_or_else(|| FeldmanError::InvalidData("invalid data".into()))?))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
Ok(Feldman {
threshold: feldman_json.threshold,
total: feldman_json.total,
id: feldman_json.id,
frags_for_counterparties,
frags_from_counterparties,
zkpok,
secret,
scalar,
generator,
public_key,
point,
random_commitment_point,
round: match feldman_json.round {
0 => FeldmanRound::Uninitialized,
1 => FeldmanRound::Initialized,
2 => FeldmanRound::Committed,
3 => FeldmanRound::Revealed,
4 => FeldmanRound::Reconstructed,
_ => FeldmanRound::Uninitialized,
},
zkcommits_from_counterparties,
points_from_counterparties,
})
}
pub fn set_id(&mut self, id: usize) {
self.id = id;
}
pub fn sample_polynomial<R: RngCore + CryptoRng>(&mut self, rng: &mut R) -> Result<(), FeldmanError> {
if self.round != FeldmanRound::Uninitialized {
return Err(FeldmanError::WrongRound);
}
let mut coeffs = vec![self.secret];
for _ in 1..self.threshold {
coeffs.push(Scalar::random(rng));
}
for i in 1..=self.total {
let mut result = coeffs[0];
let x = Scalar::from(i as u32);
for j in 1..self.threshold {
let term = coeffs[j] * Scalar::from(i.pow(j as u32) as u32);
result += term;
}
if i == self.id {
self.scalar = Some(result);
} else {
self.frags_for_counterparties.insert(i, result.to_bytes().to_vec());
}
}
self.round = FeldmanRound::Initialized;
Ok(())
}
pub fn scalar(&self) -> Option<&Scalar> {
self.scalar.as_ref()
}
pub fn get_poly_frags(&self) -> Result<&HashMap<usize, Vec<u8>>, FeldmanError> {
if self.round != FeldmanRound::Initialized {
return Err(FeldmanError::WrongRound);
}
Ok(&self.frags_for_counterparties)
}
pub fn set_poly_frag_for_party(&mut self, id: usize, frag: &[u8]) -> Result<Option<Vec<u8>>, FeldmanError> {
if self.round != FeldmanRound::Initialized {
return Err(FeldmanError::WrongRound);
}
let scalar = Scalar::from_bytes(frag.try_into().unwrap());
self.frags_from_counterparties.insert(id, scalar);
if self.frags_from_counterparties.len() == self.total - 1 {
let mut combined_scalar = self.scalar.unwrap_or_else(|| Scalar::ZERO);
for scalar in self.frags_from_counterparties.values() {
combined_scalar += *scalar;
}
self.scalar = Some(combined_scalar);
self.point = self.generator * combined_scalar;
let rand_commitment = Scalar::random(&mut rand::thread_rng());
self.random_commitment_point = Some(self.generator * rand_commitment);
let random_commitment_point_bytes = self.random_commitment_point.unwrap().compress().to_bytes();
let public_point_bytes = self.point.compress().to_bytes();
let mut hasher = Sha512::new();
hasher.update(&public_point_bytes);
hasher.update(&random_commitment_point_bytes);
let challenge = hasher.finalize();
let challenge_scalar = Scalar::from_bytes(challenge[..56].try_into().unwrap());
self.zkpok = Some(combined_scalar * challenge_scalar + rand_commitment);
let zkpok_bytes = self.zkpok.unwrap().to_bytes();
let mut hasher = Sha512::new();
hasher.update(&random_commitment_point_bytes);
hasher.update(&zkpok_bytes);
let zkcommit = hasher.finalize();
self.round = FeldmanRound::Committed;
return Ok(Some(zkcommit[..56].to_vec()));
}
Ok(None)
}
pub fn receive_commitments(&mut self, id: usize, zkcommit: &[u8]) -> Result<Option<FeldmanReveal>, FeldmanError> {
if self.round != FeldmanRound::Committed {
return Err(FeldmanError::WrongRound);
}
self.zkcommits_from_counterparties.insert(id, zkcommit.to_vec());
if self.zkcommits_from_counterparties.len() == self.total - 1 {
let public_point_bytes = self.point.compress().to_bytes();
let random_commitment_point_bytes = self.random_commitment_point.unwrap().compress().to_bytes();
self.round = FeldmanRound::Revealed;
let zkpok_bytes = self.zkpok.unwrap().to_bytes();
return Ok(Some(FeldmanReveal {
point: public_point_bytes.to_vec(),
random_commitment_point: random_commitment_point_bytes.to_vec(),
zk_pok: zkpok_bytes.to_vec(),
}));
}
Ok(None)
}
pub fn recombine(&mut self, id: usize, reveal: &FeldmanReveal) -> Result<bool, FeldmanError> {
if self.round != FeldmanRound::Revealed {
return Err(FeldmanError::WrongRound);
}
let counterparty_point = EdwardsPoint::from_bytes(reveal.point.as_slice().into()).unwrap();
if counterparty_point.eq(&EdwardsPoint::generator()).into() || counterparty_point == self.generator {
return Err(FeldmanError::InvalidData("Counterparty sent generator".into()));
}
let counterparty_random_commitment_point = EdwardsPoint::from_bytes(reveal.random_commitment_point.as_slice().into()).unwrap();
if counterparty_random_commitment_point.eq(&EdwardsPoint::generator()).into() || counterparty_random_commitment_point == self.generator {
return Err(FeldmanError::InvalidData("Counterparty sent generator".into()));
}
let counterparty_zkpok = Scalar::from_bytes(reveal.zk_pok.as_slice().try_into().unwrap());
let counterparty_zkcommit = self.zkcommits_from_counterparties.get(&id)
.ok_or_else(|| FeldmanError::InvalidData("Missing ZK commit for counterparty".into()))?;
let mut hasher = Sha512::new();
hasher.update(&reveal.point);
hasher.update(&reveal.random_commitment_point);
let challenge = hasher.finalize();
let challenge_scalar = Scalar::from_bytes(challenge[..56].try_into().unwrap());
let proof = self.generator * counterparty_zkpok;
let expected_proof = counterparty_random_commitment_point + (counterparty_point * challenge_scalar);
if proof != expected_proof {
return Err(FeldmanError::InvalidData(format!("Invalid proof from {}", id)));
}
let mut hasher = Sha512::new();
hasher.update(&reveal.random_commitment_point);
hasher.update(&reveal.zk_pok);
let verifier = hasher.finalize();
if &verifier[..56] != counterparty_zkcommit {
return Err(FeldmanError::InvalidData(format!("{} changed zkpok after commit", id)));
}
self.points_from_counterparties.insert(id, counterparty_point);
if self.points_from_counterparties.len() == self.total - 1 {
self.points_from_counterparties.insert(self.id, self.point);
for i in 1..=self.total - self.threshold + 1 {
let mut reconstructed_sum = EdwardsPoint::generator();
for j in i..self.threshold + i {
let mut num = Scalar::ONE;
let mut den = Scalar::ONE;
for k in i..self.threshold + i {
if j != k {
let j_scalar = Scalar::from(j as u32);
let k_scalar = Scalar::from(k as u32);
num *= k_scalar;
den *= k_scalar - j_scalar;
}
}
let den_inv = den.invert();
let reconstructed_fragment = self.points_from_counterparties[&j] * (num * den_inv);
reconstructed_sum += reconstructed_fragment;
}
if self.public_key == EdwardsPoint::generator() || self.public_key == self.generator {
self.public_key = reconstructed_sum;
} else if self.public_key != reconstructed_sum {
return Err(FeldmanError::InvalidData("Recombination mismatch".into()));
}
}
self.round = FeldmanRound::Reconstructed;
}
Ok(self.round == FeldmanRound::Reconstructed)
}
pub fn mul_share(&self, pubkey: &[u8]) -> Result<Vec<u8>, FeldmanError> {
if self.scalar.is_none() {
return Err(FeldmanError::WrongRound);
}
let point = EdwardsPoint::from_bytes(pubkey.into());
if point.is_none().into() {
return Err(FeldmanError::InvalidData("invalid pubkey".to_string()));
}
let result = self.scalar.unwrap() * point.unwrap();
if result.is_identity().into() {
return Err(FeldmanError::InvalidData("invalid pubkey".to_string()));
}
return Ok(result.compress().to_bytes().to_vec());
}
pub fn combine_mul_share(&mut self, shares: Vec<&[u8]>, ids: &[usize]) -> Result<Vec<u8>, FeldmanError> {
if shares.len() != ids.len() {
return Err(FeldmanError::InvalidData("mismatch of shares and ids len".to_string()));
}
let mut points = HashMap::<usize, EdwardsPoint>::new();
for (i, share) in shares.iter().enumerate() {
let point = EdwardsPoint::from_bytes((*share).into());
if point.is_none().into() {
return Err(FeldmanError::InvalidData(format!("invalid pubkey for {}", ids[i]).to_string()));
}
points.insert(ids[i], point.unwrap());
}
let mut reconstructed_sum = EdwardsPoint::generator();
for j in ids {
let mut num = Scalar::ONE;
let mut den = Scalar::ONE;
for k in ids {
if j != k {
let j_scalar = Scalar::from(*j as u32);
let k_scalar = Scalar::from(*k as u32);
num *= k_scalar;
den *= k_scalar - j_scalar;
}
}
let den_inv = den.invert();
let reconstructed_fragment = points[&j] * (num * den_inv);
reconstructed_sum += reconstructed_fragment;
}
self.public_key = reconstructed_sum;
return Ok(reconstructed_sum.compress().to_bytes().to_vec());
}
pub fn public_key(&self) -> &EdwardsPoint {
&self.public_key
}
pub fn public_key_bytes(&self) -> Vec<u8> {
self.public_key.to_bytes().to_vec()
}
}

View File

@ -0,0 +1,4 @@
pub(crate) mod doubleratchet;
pub(crate) mod tripleratchet;
pub(crate) mod feldman;
pub(crate) mod x3dh;

View File

@ -0,0 +1,883 @@
use base64::prelude::*;
use std::collections::HashMap;
use ed448_goldilocks_plus::elliptic_curve::group::GroupEncoding;
use ed448_goldilocks_plus::elliptic_curve::Group;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use sha2::{Sha512, Digest};
use hkdf::Hkdf;
use aes_gcm::{Aes256Gcm, Nonce};
use aes_gcm::aead::{Aead, Payload};
use ed448_goldilocks_plus::{subtle, EdwardsPoint, Scalar};
use serde::{Serialize, Deserialize};
use thiserror::Error;
use subtle::ConstantTimeEq;
use super::doubleratchet::{DoubleRatchetParticipant, DoubleRatchetParticipantJson, MessageCiphertext, P2PChannelEnvelope};
use super::feldman::{Feldman, vec_to_array, FeldmanReveal};
use super::x3dh::{receiver_x3dh, sender_x3dh};
const TRIPLE_RATCHET_PROTOCOL_VERSION: u16 = 1;
const TRIPLE_RATCHET_PROTOCOL: u16 = 2 << 8 + TRIPLE_RATCHET_PROTOCOL_VERSION;
#[derive(Clone, Copy, PartialEq)]
enum TripleRatchetRound {
Uninitialized,
Initialized,
Committed,
Revealed,
Reconstructed,
}
#[derive(Error, Debug)]
pub enum TripleRatchetError {
#[error("Crypto error: {0}")]
CryptoError(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Skip limit exceeded")]
SkipLimitExceeded,
#[error("Malformed header")]
MalformedHeader,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct PeerInfo {
pub(crate) public_key: Vec<u8>,
pub(crate) identity_public_key: Vec<u8>,
pub(crate) signed_pre_public_key: Vec<u8>,
}
pub struct TripleRatchetParticipant {
peer_key: Scalar,
sending_ephemeral_private_key: Scalar,
receiving_ephemeral_keys: HashMap<Vec<u8>, Scalar>,
receiving_group_key: Option<Vec<u8>>,
root_key: Vec<u8>,
sending_chain_key: Vec<u8>,
current_header_key: Vec<u8>,
next_header_key: Vec<u8>,
receiving_chain_key: HashMap<Vec<u8>, Vec<u8>>,
current_sending_chain_length: u32,
previous_sending_chain_length: u32,
current_receiving_chain_length: HashMap<Vec<u8>, u32>,
previous_receiving_chain_length: HashMap<Vec<u8>, u32>,
peer_id_map: HashMap<Vec<u8>, usize>,
id_peer_map: HashMap<usize, PeerInfo>,
skipped_keys_map: HashMap<Vec<u8>, HashMap<Vec<u8>, HashMap<u32, Vec<u8>>>>,
peer_channels: HashMap<Vec<u8>, DoubleRatchetParticipant>,
dkg_ratchet: Feldman,
next_dkg_ratchet: Feldman,
async_dkg_ratchet: bool,
should_ratchet: bool,
should_dkg_ratchet: HashMap<Vec<u8>, bool>,
async_dkg_pubkey: Option<EdwardsPoint>,
threshold: usize,
}
#[derive(Serialize, Deserialize)]
pub struct PeerInfoJson {
public_key: String,
identity_public_key: String,
signed_pre_public_key: String,
}
#[derive(Serialize, Deserialize)]
pub struct TripleRatchetParticipantJson {
peer_key: String,
sending_ephemeral_private_key: String,
receiving_ephemeral_keys: HashMap<String, String>,
receiving_group_key: Option<String>,
root_key: String,
sending_chain_key: String,
current_header_key: String,
next_header_key: String,
receiving_chain_key: HashMap<String, String>,
current_sending_chain_length: u32,
previous_sending_chain_length: u32,
current_receiving_chain_length: HashMap<String, u32>,
previous_receiving_chain_length: HashMap<String, u32>,
peer_id_map: HashMap<String, usize>,
id_peer_map: HashMap<usize, PeerInfoJson>,
skipped_keys_map: HashMap<String, HashMap<String, HashMap<u32, String>>>,
peer_channels: HashMap<String, String>,
dkg_ratchet: String,
next_dkg_ratchet: String,
async_dkg_ratchet: bool,
should_ratchet: bool,
should_dkg_ratchet: HashMap<String, bool>,
async_dkg_pubkey: Option<String>,
threshold: usize,
}
impl TripleRatchetParticipant {
pub fn new(
peers: &[PeerInfo],
peer_key: Scalar,
identity_key: Scalar,
signed_pre_key: Scalar,
threshold: usize,
async_dkg_ratchet: bool,
) -> Result<(Self, HashMap<Vec<u8>, P2PChannelEnvelope>), TripleRatchetError> {
let mut participant = TripleRatchetParticipant {
peer_key,
sending_ephemeral_private_key: Scalar::random(&mut OsRng),
receiving_ephemeral_keys: HashMap::new(),
receiving_group_key: None,
root_key: vec![],
sending_chain_key: vec![],
current_header_key: vec![],
next_header_key: vec![],
receiving_chain_key: HashMap::new(),
current_sending_chain_length: 0,
previous_sending_chain_length: 0,
current_receiving_chain_length: HashMap::new(),
previous_receiving_chain_length: HashMap::new(),
peer_id_map: HashMap::new(),
id_peer_map: HashMap::new(),
skipped_keys_map: HashMap::new(),
peer_channels: HashMap::new(),
dkg_ratchet: Feldman::new(
threshold,
peers.len() + 1,
0, // This will be set later
Scalar::random(&mut OsRng),
EdwardsPoint::generator(),
),
next_dkg_ratchet: Feldman::new(
threshold,
peers.len() + 1,
0, // This will be set later
Scalar::random(&mut OsRng),
EdwardsPoint::generator(),
),
async_dkg_ratchet: async_dkg_ratchet,
should_ratchet: true,
should_dkg_ratchet: HashMap::new(),
async_dkg_pubkey: None,
threshold: threshold,
};
let mut peer_basis = peers.to_vec();
peer_basis.push(PeerInfo {
public_key: (peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
identity_public_key: (identity_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
signed_pre_public_key: (signed_pre_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(),
});
peer_basis.sort_by(|a, b| a.public_key.cmp(&b.public_key));
let mut init_messages = HashMap::new();
let mut sender = false;
for (i, peer) in peer_basis.iter().enumerate() {
participant.peer_id_map.insert(peer.public_key.clone(), i + 1);
participant.id_peer_map.insert(i + 1, peer.clone());
if peer.public_key.ct_eq(&(peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec()).into() {
sender = true;
participant.dkg_ratchet.set_id(i + 1);
participant.next_dkg_ratchet.set_id(i + 1);
} else {
participant.skipped_keys_map.insert(peer.public_key.clone(), HashMap::new());
participant.current_receiving_chain_length.insert(peer.public_key.clone(), 0);
participant.previous_receiving_chain_length.insert(peer.public_key.clone(), 0);
let session_key = if sender {
sender_x3dh(
&identity_key,
&signed_pre_key,
&EdwardsPoint::from_bytes(peer.identity_public_key.as_slice().into()).unwrap(),
&EdwardsPoint::from_bytes(peer.signed_pre_public_key.as_slice().into()).unwrap(),
96,
)
} else {
receiver_x3dh(
&identity_key,
&signed_pre_key,
&EdwardsPoint::from_bytes(peer.identity_public_key.as_slice().into()).unwrap(),
&EdwardsPoint::from_bytes(peer.signed_pre_public_key.as_slice().into()).unwrap(),
96,
)
}.unwrap();
let peer_channel = DoubleRatchetParticipant::new(
&session_key[..32],
&session_key[32..64],
&session_key[64..],
sender,
signed_pre_key.clone(),
EdwardsPoint::from_bytes(peer.signed_pre_public_key.as_slice().into()).unwrap(),
).unwrap();
participant.peer_channels.insert(peer.public_key.clone(), peer_channel);
if sender {
let init_message = participant.peer_channels
.get_mut(&peer.public_key)
.unwrap()
.ratchet_encrypt(b"init").unwrap();
init_messages.insert(peer.public_key.clone(), init_message);
}
}
}
Ok((participant, init_messages))
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
let triple_ratchet_json = TripleRatchetParticipantJson {
peer_key: BASE64_STANDARD.encode(self.peer_key.to_bytes()),
sending_ephemeral_private_key: BASE64_STANDARD.encode(self.sending_ephemeral_private_key.to_bytes()),
receiving_ephemeral_keys: self.receiving_ephemeral_keys.iter()
.map(|(k, v)| (BASE64_STANDARD.encode(k), BASE64_STANDARD.encode(v.to_bytes())))
.collect(),
receiving_group_key: self.receiving_group_key.as_ref().map(|k| BASE64_STANDARD.encode(k)),
root_key: BASE64_STANDARD.encode(&self.root_key),
sending_chain_key: BASE64_STANDARD.encode(&self.sending_chain_key),
current_header_key: BASE64_STANDARD.encode(&self.current_header_key),
next_header_key: BASE64_STANDARD.encode(&self.next_header_key),
receiving_chain_key: self.receiving_chain_key.iter()
.map(|(k, v)| (BASE64_STANDARD.encode(k), BASE64_STANDARD.encode(v)))
.collect(),
current_sending_chain_length: self.current_sending_chain_length,
previous_sending_chain_length: self.previous_sending_chain_length,
current_receiving_chain_length: self.current_receiving_chain_length.iter()
.map(|(k, &v)| (BASE64_STANDARD.encode(k), v))
.collect(),
previous_receiving_chain_length: self.previous_receiving_chain_length.iter()
.map(|(k, &v)| (BASE64_STANDARD.encode(k), v))
.collect(),
peer_id_map: self.peer_id_map.iter()
.map(|(k, &v)| (BASE64_STANDARD.encode(k), v))
.collect(),
id_peer_map: self.id_peer_map.iter()
.map(|(&k, v)| (k, PeerInfoJson {
public_key: BASE64_STANDARD.encode(&v.public_key),
identity_public_key: BASE64_STANDARD.encode(&v.identity_public_key),
signed_pre_public_key: BASE64_STANDARD.encode(&v.signed_pre_public_key),
}))
.collect(),
skipped_keys_map: self.skipped_keys_map.iter()
.map(|(k1, v1)| (BASE64_STANDARD.encode(k1),
v1.iter().map(|(k2, v2)| (BASE64_STANDARD.encode(k2),
v2.iter().map(|(&k3, v3)| (k3, BASE64_STANDARD.encode(v3)))
.collect()))
.collect()))
.collect(),
peer_channels: self.peer_channels.iter()
.map(|(k, v)| Ok((BASE64_STANDARD.encode(k), v.to_json()?)))
.collect::<Result<HashMap<_, _>, serde_json::Error>>()?,
dkg_ratchet: self.dkg_ratchet.to_json()?,
next_dkg_ratchet: self.next_dkg_ratchet.to_json()?,
async_dkg_ratchet: self.async_dkg_ratchet,
should_ratchet: self.should_ratchet,
should_dkg_ratchet: self.should_dkg_ratchet.iter()
.map(|(k, &v)| (BASE64_STANDARD.encode(k), v))
.collect(),
async_dkg_pubkey: self.async_dkg_pubkey.as_ref()
.map(|p| BASE64_STANDARD.encode(p.compress().to_bytes())),
threshold: self.threshold,
};
serde_json::to_string(&triple_ratchet_json)
}
pub fn from_json(json: &str) -> Result<Self, Box<dyn std::error::Error>> {
let triple_ratchet_json: TripleRatchetParticipantJson = serde_json::from_str(json)?;
let peer_key_bytes = BASE64_STANDARD.decode(&triple_ratchet_json.peer_key)?;
let peer_key = Scalar::from_bytes(&vec_to_array::<56>(peer_key_bytes)?);
let sending_ephemeral_private_key_bytes = BASE64_STANDARD.decode(&triple_ratchet_json.sending_ephemeral_private_key)?;
let sending_ephemeral_private_key = Scalar::from_bytes(&vec_to_array::<56>(sending_ephemeral_private_key_bytes)?);
let receiving_ephemeral_keys = triple_ratchet_json.receiving_ephemeral_keys.into_iter()
.map(|(k, v)| {
let key = BASE64_STANDARD.decode(k)?;
let value_bytes = BASE64_STANDARD.decode(v)?;
let value = Scalar::from_bytes(&vec_to_array::<56>(value_bytes)?);
Ok((key, value))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let receiving_group_key = triple_ratchet_json.receiving_group_key
.map(|k| BASE64_STANDARD.decode(k))
.transpose()?;
let root_key = BASE64_STANDARD.decode(&triple_ratchet_json.root_key)?;
let sending_chain_key = BASE64_STANDARD.decode(&triple_ratchet_json.sending_chain_key)?;
let current_header_key = BASE64_STANDARD.decode(&triple_ratchet_json.current_header_key)?;
let next_header_key = BASE64_STANDARD.decode(&triple_ratchet_json.next_header_key)?;
let receiving_chain_key = triple_ratchet_json.receiving_chain_key.into_iter()
.map(|(k, v)| {
let key = BASE64_STANDARD.decode(k)?;
let value = BASE64_STANDARD.decode(v)?;
Ok((key, value))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let current_receiving_chain_length = triple_ratchet_json.current_receiving_chain_length.into_iter()
.map(|(k, v)| Ok((BASE64_STANDARD.decode(k)?, v)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let previous_receiving_chain_length = triple_ratchet_json.previous_receiving_chain_length.into_iter()
.map(|(k, v)| Ok((BASE64_STANDARD.decode(k)?, v)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let peer_id_map = triple_ratchet_json.peer_id_map.into_iter()
.map(|(k, v)| Ok((BASE64_STANDARD.decode(k)?, v)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let id_peer_map = triple_ratchet_json.id_peer_map.into_iter()
.map(|(k, v)| Ok((k, PeerInfo {
public_key: BASE64_STANDARD.decode(&v.public_key)?,
identity_public_key: BASE64_STANDARD.decode(&v.identity_public_key)?,
signed_pre_public_key: BASE64_STANDARD.decode(&v.signed_pre_public_key)?,
})))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let skipped_keys_map = triple_ratchet_json.skipped_keys_map.into_iter()
.map(|(k1, v1)| {
let key1 = BASE64_STANDARD.decode(k1)?;
let value1 = v1.into_iter()
.map(|(k2, v2)| {
let key2 = BASE64_STANDARD.decode(k2)?;
let value2 = v2.into_iter()
.map(|(k3, v3)| Ok((k3, BASE64_STANDARD.decode(v3)?)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
Ok((key2, value2))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
Ok((key1, value1))
})
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let peer_channels = triple_ratchet_json.peer_channels.into_iter()
.map(|(k, v)| Ok((BASE64_STANDARD.decode(k)?, DoubleRatchetParticipant::from_json(v)?)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let dkg_ratchet = Feldman::from_json(&triple_ratchet_json.dkg_ratchet)?;
let next_dkg_ratchet = Feldman::from_json(&triple_ratchet_json.next_dkg_ratchet)?;
let should_dkg_ratchet = triple_ratchet_json.should_dkg_ratchet.into_iter()
.map(|(k, v)| Ok((BASE64_STANDARD.decode(k)?, v)))
.collect::<Result<HashMap<_, _>, Box<dyn std::error::Error>>>()?;
let mut async_dkg_pubkey: Option<EdwardsPoint> = None;
if triple_ratchet_json.async_dkg_pubkey.is_some() {
let bytes = BASE64_STANDARD.decode(triple_ratchet_json.async_dkg_pubkey.unwrap())?;
let point = EdwardsPoint::from_bytes(&vec_to_array::<57>(bytes)?.into());
async_dkg_pubkey = point.into_option();
}
Ok(TripleRatchetParticipant {
peer_key,
sending_ephemeral_private_key,
receiving_ephemeral_keys,
receiving_group_key,
root_key,
sending_chain_key,
current_header_key,
next_header_key,
receiving_chain_key,
current_sending_chain_length: triple_ratchet_json.current_sending_chain_length,
previous_sending_chain_length: triple_ratchet_json.previous_sending_chain_length,
current_receiving_chain_length,
previous_receiving_chain_length,
peer_id_map,
id_peer_map,
skipped_keys_map,
peer_channels,
dkg_ratchet,
next_dkg_ratchet,
async_dkg_ratchet: triple_ratchet_json.async_dkg_ratchet,
should_ratchet: triple_ratchet_json.should_ratchet,
should_dkg_ratchet,
async_dkg_pubkey,
threshold: triple_ratchet_json.threshold,
})
}
pub fn get_peer_id_map(&self) -> HashMap<Vec<u8>, usize> {
return self.peer_id_map.clone();
}
pub fn initialize(&mut self, init_messages: &HashMap<Vec<u8>, P2PChannelEnvelope>)
-> Result<HashMap<Vec<u8>, P2PChannelEnvelope>, TripleRatchetError> {
for (k, m) in init_messages {
let msg = self.peer_channels.get_mut(k).unwrap().ratchet_decrypt(m).unwrap();
if msg != b"init" {
return Err(TripleRatchetError::InvalidData("Invalid init message".into()));
}
}
self.dkg_ratchet.sample_polynomial(&mut OsRng);
let result = self.dkg_ratchet.get_poly_frags().unwrap();
let mut result_map = HashMap::new();
for (k, v) in result {
let test: bool = self.id_peer_map[&k].public_key.ct_eq(&(self.peer_key * EdwardsPoint::generator()).compress().to_bytes()).into();
if !test {
let envelope = self.peer_channels
.get_mut(&self.id_peer_map[&k].public_key)
.unwrap()
.ratchet_encrypt(&v);
result_map.insert(self.id_peer_map[&k].public_key.clone(), envelope.unwrap());
}
}
Ok(result_map)
}
pub fn receive_poly_frag(&mut self, peer_id: &[u8], frag: &P2PChannelEnvelope)
-> Result<Option<HashMap<Vec<u8>, P2PChannelEnvelope>>, TripleRatchetError> {
let b = self.peer_channels.get_mut(peer_id).unwrap().ratchet_decrypt(frag).unwrap();
let result = self.dkg_ratchet.set_poly_frag_for_party(
*self.peer_id_map.get(peer_id).unwrap(),
&b,
).unwrap();
if result.is_some() {
let mut envelopes = HashMap::new();
for (k, c) in &mut self.peer_channels {
let envelope = c.ratchet_encrypt(&result.clone().unwrap()).unwrap();
envelopes.insert(k.clone(), envelope);
}
Ok(Some(envelopes))
} else {
Ok(None)
}
}
pub fn receive_commitment(&mut self, peer_id: &[u8], zkcommit: &P2PChannelEnvelope)
-> Result<Option<HashMap<Vec<u8>, P2PChannelEnvelope>>, TripleRatchetError> {
let b = self.peer_channels.get_mut(peer_id).unwrap().ratchet_decrypt(zkcommit).unwrap();
let result = self.dkg_ratchet.receive_commitments(
*self.peer_id_map.get(peer_id).unwrap(),
&b,
).unwrap();
if let Some(reveal) = result {
let d = serde_json::to_vec(&reveal).unwrap();
let mut envelopes = HashMap::new();
for (k, c) in &mut self.peer_channels {
let envelope = c.ratchet_encrypt(&d).unwrap();
envelopes.insert(k.clone(), envelope);
}
Ok(Some(envelopes))
} else {
Ok(None)
}
}
pub fn recombine(&mut self, peer_id: &[u8], reveal: &P2PChannelEnvelope) -> Result<(), Box<dyn std::error::Error>> {
let b = self.peer_channels.get_mut(peer_id).unwrap().ratchet_decrypt(reveal).unwrap();
let rev: FeldmanReveal = serde_json::from_slice(&b).unwrap();
let done = self.dkg_ratchet.recombine(
*self.peer_id_map.get(peer_id).unwrap(),
&rev,
).unwrap();
if !done {
return Ok(());
}
let sess = Sha512::digest(&self.dkg_ratchet.public_key_bytes());
let hkdf = Hkdf::<Sha512>::new(
Some(&sess),
&self.dkg_ratchet.public_key_bytes(),
);
let mut rkck = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
self.root_key = rkck[..32].to_vec();
self.current_header_key = rkck[32..64].to_vec();
self.next_header_key = rkck[64..].to_vec();
self.receiving_group_key = Some(self.dkg_ratchet.public_key().to_bytes().to_vec());
Ok(())
}
pub fn ratchet_encrypt(&mut self, message: &[u8]) -> Result<P2PChannelEnvelope, Box<dyn std::error::Error>> {
if self.should_ratchet {
self.ratchet_sender_ephemeral_keys()?;
}
if self.async_dkg_ratchet && self.async_dkg_pubkey.is_some() && self.should_dkg_ratchet.len() == self.threshold {
self.receiving_group_key = Some(self.dkg_ratchet.public_key().to_bytes().to_vec());
let sess = Sha512::digest(&self.dkg_ratchet.public_key_bytes());
let hkdf = Hkdf::<Sha512>::new(
Some(&sess),
&self.dkg_ratchet.public_key_bytes(),
);
let mut rkck = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
self.root_key = rkck[..32].to_vec();
self.current_header_key = rkck[32..64].to_vec();
self.next_header_key = rkck[64..].to_vec();
self.async_dkg_pubkey = None;
}
let dkg_pub: Option<(Vec<u8>, Vec<u8>)> = if (self.async_dkg_ratchet && self.should_dkg_ratchet.len() > 0) || self.async_dkg_pubkey.is_some() {
if self.async_dkg_pubkey.is_none() {
self.should_dkg_ratchet = HashMap::new();
self.async_dkg_pubkey = Some(Scalar::random(&mut OsRng) * EdwardsPoint::generator());
}
let pubkey = self.async_dkg_pubkey.unwrap();
let invpub = self.dkg_ratchet.mul_share(&pubkey.compress().to_bytes());
if invpub.is_err() {
return Err(Box::new(invpub.unwrap_err()));
}
self.should_dkg_ratchet.insert((self.peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec(), true);
Some((invpub.unwrap(), pubkey.compress().to_bytes().to_vec()))
} else { None };
let mut envelope = P2PChannelEnvelope {
protocol_identifier: TRIPLE_RATCHET_PROTOCOL,
message_header: MessageCiphertext::default(),
message_body: MessageCiphertext::default(),
};
let (new_chain_key, message_key, aead_key) = ratchet_keys(&self.sending_chain_key);
self.sending_chain_key = new_chain_key;
let header = self.encode_header(dkg_pub);
envelope.message_header = self.encrypt(&header, &self.current_header_key, None)?;
envelope.message_body = self.encrypt(
message,
&message_key,
Some(&[&aead_key[..], &envelope.message_header.ciphertext[..]].concat()),
)?;
self.current_sending_chain_length += 1;
Ok(envelope)
}
pub fn ratchet_decrypt(&mut self, envelope: &P2PChannelEnvelope) -> Result<(Vec<u8>, bool), Box<dyn std::error::Error>> {
if let Some(plaintext) = self.try_skipped_message_keys(envelope)? {
return Ok((plaintext, false));
}
let header_key = self.current_header_key.clone();
let (header, mut should_dkg_ratchet, should_advance_dkg_ratchet) = self.decrypt_header(&envelope.message_header, &header_key)?;
let (sender_key, receiving_ephemeral_key, previous_receiving_chain_length, current_receiving_chain_length, dkg_pub) =
self.decode_header(&header)?;
let should_ratchet = self.receiving_ephemeral_keys.get(&sender_key.compress().to_bytes().to_vec()).map(|k| !k.eq(&receiving_ephemeral_key)).unwrap_or(true);
if should_ratchet {
self.skip_message_keys(&sender_key, previous_receiving_chain_length)?;
self.ratchet_receiver_ephemeral_keys(&sender_key, &receiving_ephemeral_key)?;
}
self.skip_message_keys(&sender_key, current_receiving_chain_length)?;
let (new_chain_key, message_key, aead_key) = ratchet_keys(
&self.receiving_chain_key[&sender_key.compress().to_bytes().to_vec()],
);
self.receiving_chain_key.insert(sender_key.compress().to_bytes().to_vec(), new_chain_key);
*self.current_receiving_chain_length.entry(sender_key.compress().to_bytes().to_vec()).or_insert(0) += 1;
if should_advance_dkg_ratchet {
self.receiving_group_key = Some(self.dkg_ratchet.public_key().to_bytes().to_vec());
let sess = Sha512::digest(&self.dkg_ratchet.public_key_bytes());
let hkdf = Hkdf::<Sha512>::new(
Some(&sess),
&self.dkg_ratchet.public_key_bytes(),
);
let mut rkck = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
self.root_key = rkck[..32].to_vec();
self.current_header_key = rkck[32..64].to_vec();
self.next_header_key = rkck[64..].to_vec();
}
let plaintext = self.decrypt(
&envelope.message_body,
&message_key,
Some(&[&aead_key[..], &envelope.message_header.ciphertext[..]].concat()),
)?;
if dkg_pub.is_some() {
should_dkg_ratchet = false;
let (other_invpub, pubkey) = dkg_pub.unwrap();
self.async_dkg_pubkey = Some(pubkey);
let invpub = self.dkg_ratchet.mul_share(&pubkey.compress().to_bytes());
if invpub.is_err() {
return Err(Box::new(invpub.unwrap_err()));
}
let invvec = invpub.unwrap();
let inv = invvec.as_slice();
let other = &other_invpub.compress().to_bytes();
let other_id = self.peer_id_map.get(&sender_key.compress().to_bytes().to_vec());
if other_id.is_none() {
return Err(Box::new(TripleRatchetError::MalformedHeader))
}
let our_id = self.peer_id_map.get(&(self.peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec());
let (shares, ids) = if our_id.unwrap() < other_id.unwrap() {
(vec![inv, other], [*our_id.unwrap(), *other_id.unwrap()])
} else {
(vec![other, inv], [*other_id.unwrap(), *our_id.unwrap()])
};
let next_pub = self.dkg_ratchet.combine_mul_share(shares, &ids);
if next_pub.is_err() {
return Err(Box::new(next_pub.unwrap_err()));
}
let next = EdwardsPoint::from_bytes(next_pub.unwrap().as_slice().into());
if next.is_none().into() {
return Err(Box::new(TripleRatchetError::MalformedHeader));
}
}
Ok((plaintext, should_dkg_ratchet))
}
fn ratchet_sender_ephemeral_keys(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let receiving_group_key = self.receiving_group_key.as_ref().ok_or_else(|| TripleRatchetError::CryptoError("Receiving group key not set".into()))?;
self.sending_ephemeral_private_key = Scalar::random(&mut OsRng);
let dh_output = (self.sending_ephemeral_private_key * EdwardsPoint::from_bytes(receiving_group_key.as_slice().into()).unwrap()).compress().to_bytes();
let hkdf = Hkdf::<Sha512>::new(Some(&self.root_key), &dh_output);
let mut rkck2 = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck2);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
self.root_key = rkck2[..32].to_vec();
self.sending_chain_key = rkck2[32..64].to_vec();
self.should_ratchet = false;
Ok(())
}
fn ratchet_receiver_ephemeral_keys(&mut self, peer_key: &EdwardsPoint, new_ephemeral_key: &Scalar) -> Result<(), Box<dyn std::error::Error>> {
self.previous_sending_chain_length = self.current_sending_chain_length;
self.current_sending_chain_length = 0;
*self.current_receiving_chain_length.entry(peer_key.compress().to_bytes().to_vec()).or_insert(0) = 0;
self.receiving_ephemeral_keys.insert(peer_key.compress().to_bytes().to_vec(), new_ephemeral_key.clone());
let receiving_group_key = self.receiving_group_key.as_ref().ok_or_else(|| TripleRatchetError::CryptoError("Receiving group key not set".into()))?;
let dh_output = (new_ephemeral_key * EdwardsPoint::from_bytes(receiving_group_key.as_slice().into()).unwrap()).compress().to_bytes();
let hkdf = Hkdf::<Sha512>::new(Some(&self.root_key), &dh_output);
let mut rkck = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
self.root_key = rkck[..32].to_vec();
self.receiving_chain_key.insert(peer_key.compress().to_bytes().to_vec(), rkck[32..64].to_vec());
self.should_ratchet = true;
if self.async_dkg_ratchet {
self.should_dkg_ratchet = HashMap::new();
self.should_dkg_ratchet.insert(peer_key.compress().to_bytes().to_vec(), true);
self.async_dkg_pubkey = None;
}
Ok(())
}
fn try_skipped_message_keys(&mut self, envelope: &P2PChannelEnvelope) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
for (receiving_header_key, skipped_keys) in &self.skipped_keys_map.clone() {
if let Ok((header, _, _)) = self.decrypt_header(&envelope.message_header, receiving_header_key) {
let (peer_key, _, _, current, _) = self.decode_header(&header)?;
if let Some(peer_skipped_keys) = skipped_keys.get(&peer_key.compress().to_bytes().to_vec()) {
if let Some(key_pair) = peer_skipped_keys.get(&current) {
let message_key = &key_pair[..32];
let aead_key = &key_pair[32..];
let plaintext = self.decrypt(
&envelope.message_body,
message_key,
Some(&[aead_key, &envelope.message_header.ciphertext].concat()),
)?;
return Ok(Some(plaintext));
}
}
}
}
Ok(None)
}
fn skip_message_keys(&mut self, sender_key: &EdwardsPoint, until: u32) -> Result<(), Box<dyn std::error::Error>> {
let mut current = *self.current_receiving_chain_length.entry(sender_key.compress().to_bytes().to_vec()).or_insert(0);
if current + 100 < until {
return Err(Box::new(TripleRatchetError::SkipLimitExceeded));
}
if let Some(chain_key) = self.receiving_chain_key.get_mut(&sender_key.compress().to_bytes().to_vec()) {
while current < until {
let (new_chain_key, message_key, aead_key) = ratchet_keys(chain_key);
self.skipped_keys_map
.entry(self.current_header_key.clone())
.or_insert_with(HashMap::new)
.entry(sender_key.compress().to_bytes().to_vec())
.or_insert_with(HashMap::new)
.insert(current, [message_key, aead_key].concat());
*chain_key = new_chain_key;
current += 1;
*self.current_receiving_chain_length.entry(sender_key.compress().to_bytes().to_vec()).or_insert(0) += 1;
}
}
Ok(())
}
fn encode_header(&self, dkg_pub: Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
let mut header = Vec::new();
header.extend_from_slice(&(self.peer_key * EdwardsPoint::generator()).compress().to_bytes().to_vec());
header.extend_from_slice(&self.sending_ephemeral_private_key.to_bytes());
header.extend_from_slice(&self.previous_sending_chain_length.to_be_bytes());
header.extend_from_slice(&self.current_sending_chain_length.to_be_bytes());
if dkg_pub.is_some() {
let (invpub, pubkey) = dkg_pub.unwrap();
header.extend(invpub);
header.extend(pubkey);
}
header
}
fn decrypt_header(&mut self, ciphertext: &MessageCiphertext, receiving_header_key: &[u8]) -> Result<(Vec<u8>, bool, bool), Box<dyn std::error::Error>> {
match self.decrypt(ciphertext, receiving_header_key, None) {
Ok(header) => {
Ok((header, self.async_dkg_ratchet, false))
},
Err(_) if receiving_header_key == self.current_header_key => {
if self.async_dkg_ratchet && self.async_dkg_pubkey.is_some() {
let receiving_group_key = Some(self.dkg_ratchet.public_key().to_bytes().to_vec());
let sess = Sha512::digest(&self.dkg_ratchet.public_key_bytes());
let hkdf = Hkdf::<Sha512>::new(
Some(&sess),
&self.dkg_ratchet.public_key_bytes(),
);
let mut rkck = [0u8; 96];
let result = hkdf.expand(b"quilibrium-triple-ratchet", &mut rkck);
if result.is_err() {
return Err(Box::new(TripleRatchetError::CryptoError("invalid length".to_owned())));
}
let current_header_key = rkck[32..64].to_vec();
match self.decrypt(ciphertext, &current_header_key, None) {
Ok(header) => Ok((header, true, true)),
Err(e) => Err(Box::new(e)),
}
} else {
match self.decrypt(ciphertext, &self.next_header_key, None) {
Ok(header) => Ok((header, true, false)),
Err(e) => Err(Box::new(e)),
}
}
},
Err(e) => Err(Box::new(e)),
}
}
fn decode_header(&self, header: &[u8]) -> Result<(EdwardsPoint, Scalar, u32, u32, Option<(EdwardsPoint, EdwardsPoint)>), TripleRatchetError> {
if header.len() < 121 {
return Err(TripleRatchetError::MalformedHeader);
}
let sender_key = EdwardsPoint::from_bytes(header[..57].into()).unwrap();
let receiving_ephemeral_key = Scalar::from_bytes(header[57..113].try_into().unwrap());
let previous_receiving_chain_length = u32::from_be_bytes(header[113..117].try_into().unwrap());
let current_receiving_chain_length = u32::from_be_bytes(header[117..121].try_into().unwrap());
let dkg_pub = if header.len() == 235 {
let invpub = EdwardsPoint::from_bytes(header[121..178].into()).into_option();
let pubkey = EdwardsPoint::from_bytes(header[178..].into()).into_option();
if invpub.is_none() || pubkey.is_none() {
None
} else {
Some((invpub.unwrap(), pubkey.unwrap()))
}
} else {
None
};
Ok((sender_key, receiving_ephemeral_key, previous_receiving_chain_length, current_receiving_chain_length, dkg_pub))
}
fn encrypt(&self, plaintext: &[u8], key: &[u8], associated_data: Option<&[u8]>)
-> Result<MessageCiphertext, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
let mut iv = [0u8; 12];
OsRng.fill_bytes(&mut iv);
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let nonce = Nonce::from_slice(&iv);
let mut associated_data = associated_data.unwrap_or(&[]);
let mut aad = [0u8; 32];
if associated_data.len() == 0 {
OsRng.fill_bytes(&mut aad);
associated_data = &aad
}
let ciphertext = cipher.encrypt(nonce, Payload{
msg: plaintext,
aad: associated_data,
}).map_err(|e| format!("Encryption failed: {}", e))?;
Ok(MessageCiphertext {
ciphertext,
initialization_vector: iv.to_vec(),
associated_data: Some(associated_data.to_vec()),
})
}
fn decrypt(&self, ciphertext: &MessageCiphertext, key: &[u8], associated_data: Option<&[u8]>) -> Result<Vec<u8>, TripleRatchetError> {
use aes_gcm::KeyInit;
if key.len() != 32 {
return Err(TripleRatchetError::InvalidData("Invalid key length".to_string()));
}
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let nonce = Nonce::from_slice(&ciphertext.initialization_vector);
let associated_data = associated_data.unwrap_or_else(|| ciphertext.associated_data.as_ref().unwrap());
cipher.decrypt(nonce, Payload{
msg: &ciphertext.ciphertext.as_ref(),
aad: associated_data.as_ref(),
}).map_err(|e| TripleRatchetError::CryptoError(e.to_string()))
}
}
fn ratchet_keys(input: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let mut output = [0u8; 96];
let hkdf = Hkdf::<Sha512>::new(None, input);
hkdf.expand(b"quilibrium-triple-ratchet-keys", &mut output).unwrap();
(output[..32].to_vec(), output[32..64].to_vec(), output[64..].to_vec())
}

View File

@ -0,0 +1,82 @@
use std::collections::HashMap;
use sha2::Sha512;
use hkdf::Hkdf;
use ed448_goldilocks_plus::{subtle, CompressedEdwardsY, EdwardsPoint, Scalar};
use lazy_static::lazy_static;
lazy_static! {
static ref DOMAIN_SEPARATORS: HashMap<&'static str, Vec<u8>> = {
let mut m = HashMap::new();
m.insert("ed448", vec![
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF,
]);
m
};
}
pub fn sender_x3dh(
sending_identity_private_key: &Scalar,
sending_ephemeral_private_key: &Scalar,
receiving_identity_key: &EdwardsPoint,
receiving_signed_pre_key: &EdwardsPoint,
session_key_length: usize,
) -> Option<Vec<u8>> {
let xdh1 = (receiving_signed_pre_key * sending_identity_private_key).compress().to_bytes().to_vec();
let xdh2 = (receiving_identity_key * sending_ephemeral_private_key).compress().to_bytes().to_vec();
let xdh3 = (receiving_signed_pre_key * sending_ephemeral_private_key).compress().to_bytes().to_vec();
let salt = vec![0u8; session_key_length];
let info = b"quilibrium-x3dh";
let domain_separator = DOMAIN_SEPARATORS.get("ed448")
.expect("Unsupported curve");
let mut ikm = Vec::<u8>::new();
ikm.extend(domain_separator);
ikm.extend(xdh1);
ikm.extend(xdh2);
ikm.extend(xdh3);
let hk = Hkdf::<Sha512>::new(Some(&salt), &ikm);
let mut session_key = vec![0u8; session_key_length];
hk.expand(info, &mut session_key).ok()?;
Some(session_key)
}
pub fn receiver_x3dh(
sending_identity_private_key: &Scalar,
sending_signed_pre_private_key: &Scalar,
receiving_identity_key: &EdwardsPoint,
receiving_ephemeral_key: &EdwardsPoint,
session_key_length: usize,
) -> Option<Vec<u8>> {
let xdh1 = (receiving_identity_key * sending_signed_pre_private_key).compress().to_bytes().to_vec();
let xdh2 = (receiving_ephemeral_key * sending_identity_private_key).compress().to_bytes().to_vec();
let xdh3 = (receiving_ephemeral_key * sending_signed_pre_private_key).compress().to_bytes().to_vec();
let salt = vec![0u8; session_key_length];
let info = b"quilibrium-x3dh";
let domain_separator = DOMAIN_SEPARATORS.get("ed448")
.expect("Unsupported curve");
let mut ikm = Vec::<u8>::new();
ikm.extend(domain_separator);
ikm.extend(xdh1);
ikm.extend(xdh2);
ikm.extend(xdh3);
let hk = Hkdf::<Sha512>::new(Some(&salt), &ikm);
let mut session_key = vec![0u8; session_key_length];
hk.expand(info, &mut session_key).ok()?;
Some(session_key)
}

7
crates/rpm/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "rpm"
version = "0.1.0"

30
crates/rpm/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "rpm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib", "staticlib"]
name = "rpm"
[dependencies]
hex = "0.4.3"
serde_json = "1.0.117"
uniffi = { version= "0.25", features = ["cli"]}
curve25519-dalek = "4.1.3"
rand = "0.8.5"
num = "0.4.3"
lazy_static = "1.5.0"
subtle = "2.6.1"
rayon = "1.10"
[dev-dependencies]
criterion = { version = "0.4", features = ["html_reports"] }
rand = "0.8.5"
[build-dependencies]
uniffi = { version = "0.25", features = [ "build" ] }
[[bench]]
name = "bench_rpm"
harness = false

32
crates/rpm/README.md Normal file
View File

@ -0,0 +1,32 @@
# RPM
Based on the paper [RPM: Robust Anonymity at Scale](https://eprint.iacr.org/2022/1037). Built to be used as a primitive all sharp edges are exposed, using this without properly checking the boundaries of the input will cut!
Includes UniFFI UDL file for FFI integration.
## How to use safely
There are six public functions of interest for builders to use if they want to have a purpose built mixnet:
- rpm_generate_initial_shares
- rpm_combine_shares_and_mask
- rpm_sketch_propose
- rpm_sketch_verify
- rpm_permute
- rpm_finalize
### rpm_generate_initial_shares
Performs the first of four steps of the "offline" phase of RPM Variant 3 (Subvariant 2). Each active dealer in a given offline batch generates initial shares of the permutation matrices and masks. Assumes player identifiers are in monotonically increasing order starting from 1, player count must be greater than or equal to the square of dealers. For malicious security, a ZKPoK should be used for each share set and broadcasted by all dealers prior to the next step.
### rpm_combine_shares_and_mask
Performs the second of four steps of the "offline" phase. Each player should have received their respective shares and organized them according to sequence of players for inputs. For malicious security, the ZKPoK should be verified after invoking this. The splits of the permutation matrices and masks should be passed into the next step.
### rpm_sketch_propose
Performs the third of four steps of the "offline" phase. Each player should take the splits of the permutation matrices and masks and pass them into this function. All players should broadcast the sketch proposals.
### rpm_sketch_verify
Performs the fourth step of the "offline" phase. Each player should take the broadcasted sketch proposals and pass them into the function. If any dealer or player had cheated, it would be revealed by the intersection of failures.
### rpm_permute
Performs the first step of the "online" phase. Before invoking this, each message sender should obtain a vector of the first depth's mask shares, at the same index from each, from as many players as needed to match the dealer count. The sender should combine these shares to produce the mask for their message, and add it to their message (chunked by field element size if need be), and secret share it with the same parameters (t = dealers, n = players). The players will collect their respective shares of inputs into an input vector following the order of the mask shares that were applied. The players will then pass in the input vector shares, matrix shares, mask shares, and combined matrix/mask shares from `rpm_combine_shares_and_mask`, along with the depth and player identifiers in player order. This function should be invoked in as many rounds as there is depth to the mixnet, using the previous invocation's output shares given to each respective player. This must be repeated for each chunked vector of field elements if applicable.
### rpm_finalize
A convenience funciton which recombines the shares of the final invocation of `rpm_permute`'s output into the finalized vector of the mixnet.

View File

@ -0,0 +1,71 @@
use curve25519_dalek::Scalar;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn criterion_benchmark(c: &mut Criterion) {
let smsize = 100;
let msize = smsize*smsize;
let depth = 4;
let players = 9;
let dealers = 3;
//todo parties should be + 1
let is1 = rpm::rpm_generate_initial_shares(msize, depth, dealers, players);
let is2 = rpm::rpm_generate_initial_shares(msize, depth, dealers, players);
let is3 = rpm::rpm_generate_initial_shares(msize, depth, dealers, players);
let (m1, r1) = (is1.ms, is1.rs);
let (m2, r2) = (is2.ms, is2.rs);
let (m3, r3) = (is3.ms, is3.rs);
let mut ms = vec![vec![vec![vec![vec![vec![[0u8; 32]; smsize]; smsize]; smsize]; depth]; dealers]; players];
let mut rs = vec![vec![vec![vec![[0u8; 32]; msize]; depth]; dealers]; players];
let mut mc = Vec::<Vec<Vec<Vec<Vec<[u8; 32]>>>>>::with_capacity(players);
let mut rc = Vec::<Vec<Vec<[u8; 32]>>>::with_capacity(players);
let mut mrmc = Vec::<Vec<Vec<Vec<Vec<[u8; 32]>>>>>::with_capacity(players);
let mut mccs = Vec::<Vec<Vec<Vec<[u8; 32]>>>>::with_capacity(players);
let mut rccs = Vec::<Vec<[u8; 32]>>::with_capacity(players);
for i in 0..players {
for j in 0..depth {
for k in 0..smsize {
ms[i][0][j][k] = m1[j][k][i].clone();
ms[i][1][j][k] = m2[j][k][i].clone();
ms[i][2][j][k] = m3[j][k][i].clone();
}
rs[i][0][j] = r1[j][i].clone();
rs[i][1][j] = r2[j][i].clone();
rs[i][2][j] = r3[j][i].clone();
}
let cs = rpm::rpm_combine_shares_and_mask(ms[i].clone(), rs[i].clone(), msize, depth, dealers);
let (m, r, mrm) = (cs.ms, cs.rs, cs.mrms);
let sp = rpm::rpm_sketch_propose(m.clone(), r.clone());
let (mcc, rcc) = (sp.mp, sp.rp);
mc.push(m);
rc.push(r);
mrmc.push(mrm);
mccs.push(mcc);
rccs.push(rcc);
}
let mut xs = vec![vec![[0u8; 32]; msize]; players];
for i in 0..msize {
let xsi = rpm::gen_poly_frags(&Scalar::from(i as u64), 9, 3);
for j in 0..9 {
xs[j][i] = (Scalar::from_bytes_mod_order(xsi[j]) + Scalar::from_bytes_mod_order(rc[j][0][i])).to_bytes();
}
}
let mut group = c.benchmark_group("rpm");
group.sample_size(10);
group.bench_function(format!("rpm init {}", msize), |b| b.iter(|| black_box(rpm::rpm_generate_initial_shares(msize, depth, 3, 9))));
group.bench_function(format!("rpm combine {}", msize), |b| b.iter(|| black_box(rpm::rpm_combine_shares_and_mask(ms[0].clone(), rs[0].clone(), msize, depth, dealers))));
group.bench_function(format!("rpm sketch propose {}", msize), |b| b.iter(|| black_box(rpm::rpm_sketch_propose(mc[0].clone(), rc[0].clone()))));
group.bench_function(format!("rpm sketch verify {}", msize), |b| b.iter(|| black_box(rpm::rpm_sketch_verify(mccs.clone(), rccs.clone(), dealers))));
group.bench_function(format!("rpm permute {}", msize), |b| b.iter(|| black_box(rpm::rpm_permute(xs.clone(), mc[0].clone(), rc[0].clone(), mrmc[0].clone(), 0, vec![1,2,3,4,5,6,7,8,9]))));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

5
crates/rpm/build.rs Normal file
View File

@ -0,0 +1,5 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
uniffi::generate_scaffolding("src/lib.udl").expect("uniffi generation failed");
}

1274
crates/rpm/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

24
crates/rpm/src/lib.udl Normal file
View File

@ -0,0 +1,24 @@
namespace rpm {
InitialShares rpm_generate_initial_shares(u64 size, u64 depth, u64 dealers, u64 players);
CombinedSharesAndMask rpm_combine_shares_and_mask([ByRef] sequence<sequence<sequence<sequence<sequence<sequence<u8>>>>>> ms, [ByRef] sequence<sequence<sequence<sequence<u8>>>> rs, u64 size, u64 depth, u64 dealers);
SketchProposal rpm_sketch_propose([ByRef] sequence<sequence<sequence<sequence<sequence<u8>>>>> m, [ByRef] sequence<sequence<sequence<u8>>> r);
boolean rpm_sketch_verify([ByRef] sequence<sequence<sequence<sequence<sequence<u8>>>>> mcs, [ByRef] sequence<sequence<sequence<u8>>> rcs, u64 dealers);
sequence<sequence<sequence<u8>>> rpm_permute([ByRef] sequence<sequence<sequence<u8>>> masked_input_shares, [ByRef] sequence<sequence<sequence<sequence<sequence<u8>>>>> mb, [ByRef] sequence<sequence<sequence<u8>>> rb, [ByRef] sequence<sequence<sequence<sequence<sequence<u8>>>>> mrmb, u64 depth_index, [ByRef] sequence<u64> parties);
sequence<sequence<u8>> rpm_finalize([ByRef] sequence<sequence<sequence<u8>>> input, [ByRef] sequence<u64> parties);
};
dictionary InitialShares {
sequence<sequence<sequence<sequence<sequence<sequence<u8>>>>>> ms;
sequence<sequence<sequence<sequence<sequence<u8>>>>> rs;
};
dictionary CombinedSharesAndMask {
sequence<sequence<sequence<sequence<sequence<u8>>>>> ms;
sequence<sequence<sequence<u8>>> rs;
sequence<sequence<sequence<sequence<sequence<u8>>>>> mrms;
};
dictionary SketchProposal {
sequence<sequence<sequence<sequence<u8>>>> mp;
sequence<sequence<u8>> rp;
};

View File

@ -175,6 +175,7 @@ mod test {
.unwrap()
);
}
#[test]
fn check_random_bytes() {
assert_eq!(

View File

@ -1,11 +1,123 @@
# go-libp2p-blossomsub
First-pass of blossomsub, rudimentary fork of gossipsub  it does not merge subscriptions, bloom filtering needs to
happen at the publish level. This will be updated post-ceremony with the full bloom filter version.
<p align="left">
<a href="https://quilibrium.com"><img src="https://img.shields.io/badge/made%20by-Quilibrium%20Inc-orange.svg?style=flat-square" /></a>
<a href="https://github.com/quilibriumnetwork"><img src="https://img.shields.io/badge/project-Quilibrium-orange.svg?style=flat-square" /></a>
<a href="https://discourse.quilibrium.com/"><img src="https://img.shields.io/discourse/posts.svg?server=https%3A%2F%2Fquilibrium.discourse.group&style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/golang-%3E%3D1.22.0-orange.svg?style=flat-square" /></a>
</p>
This repo contains the canonical blossomsub implementation for Quilibrium. It has historical origins in [Gossipsub](https://github.com/libp2p/go-libp2p-pubsub), but has diverged significantly. Floodsub and Randomsub are not included in this fork.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Overview](#overview)
- [Tracing](#tracing)
- [Contribute](#contribute)
- [License](#license)
## Install
```
go get source.quilibrium.com/quilibrium/monorepo/go-libp2p-pubsub
```
## Usage
To be used for messaging in high scale, high throughput p2p instrastructure such as Quilibrium.
### Overview
```
.
├── LICENSE
├── README.md
# Regular Golang repo set up
├── codecov.yml
├── pb
├── go.mod
├── go.sum
├── doc.go
# PubSub base
├── backoff.go
├── bitmask.go
├── blacklist.go
├── comm.go
├── discovery.go
├── gossip_tracer.go
├── midgen.go
├── peer_gater.go
├── peer_notify.go
├── pubsub.go
├── sign.go
├── subscription.go
├── tag_tracer.go
├── trace.go
├── tracer.go
├── validation.go
# Blossomsub router
├── blossomsub_feat.go
├── blossomsub.go
├── mcache.go
├── score.go
└── score_params.go
```
### Tracing
The pubsub system supports _tracing_, which collects all events pertaining to the internals of the system. This allows you to recreate the complete message flow and state of the system for analysis purposes.
To enable tracing, instantiate the pubsub system using the `WithEventTracer` option; the option accepts a tracer with three available implementations in-package (trace to json, pb, or a remote peer).
If you want to trace using a remote peer in the same way gossipsub tracing worked, you would need to do so by forking the `traced` daemon from [go-libp2p-pubsub-tracer](https://github.com/libp2p/go-libp2p-pubsub-tracer).
For instance, to capture the trace as a json file, you can use the following option:
```go
tracer, err := pubsub.NewJSONTracer("/path/to/trace.json")
if err != nil {
panic(err)
}
pubsub.NewBlossomSub(..., pubsub.WithEventTracer(tracer))
```
To capture the trace as a protobuf, you can use the following option:
```go
tracer, err := pubsub.NewPBTracer("/path/to/trace.pb")
if err != nil {
panic(err)
}
pubsub.NewBlossomSub(..., pubsub.WithEventTracer(tracer))
```
Finally, to use the remote tracer, you can use the following incantations:
```go
// assuming that your tracer runs in x.x.x.x and has a peer ID of QmTracer
pi, err := peer.AddrInfoFromP2pAddr(ma.StringCast("/ip4/x.x.x.x/tcp/4001/p2p/QmTracer"))
if err != nil {
panic(err)
}
tracer, err := pubsub.NewRemoteTracer(ctx, host, pi)
if err != nil {
panic(err)
}
ps, err := pubsub.NewBlossomSub(..., pubsub.WithEventTracer(tracer))
```
## Contribute
Contributions welcome. Please check out [the issues](https://source.quilibrium.com/quilibrium/monorepo/-/issues).
Quilibrium does not have a code of conduct for contributions  contributions are accepted on merit and benefit to the protocol.
## License
The go-libp2p-blossomsub project being forked from pubsub inherits the dual-license under Apache 2.0 and MIT terms:
The go-libp2p-blossomsub project being forked from go-libp2p-pubsub inherits the dual-license under Apache 2.0 and MIT terms:
- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](./LICENSE-MIT) or http://opensource.org/licenses/MIT)

View File

@ -51,7 +51,6 @@ func newBackoff(ctx context.Context, sizeThreshold int, cleanupInterval time.Dur
func (b *backoff) updateAndGet(id peer.ID) (time.Duration, error) {
b.mu.Lock()
defer b.mu.Unlock()
h, ok := b.info[id]
switch {
@ -62,6 +61,7 @@ func (b *backoff) updateAndGet(id peer.ID) (time.Duration, error) {
attempts: 0,
}
case h.attempts >= b.maxAttempts:
b.mu.Unlock()
return 0, fmt.Errorf("peer %s has reached its maximum backoff attempts", id)
case h.duration < MinBackoffDelay:
@ -78,27 +78,29 @@ func (b *backoff) updateAndGet(id peer.ID) (time.Duration, error) {
h.attempts += 1
h.lastTried = time.Now()
b.info[id] = h
b.mu.Unlock()
return h.duration, nil
}
func (b *backoff) cleanup() {
b.mu.Lock()
defer b.mu.Unlock()
for id, h := range b.info {
if time.Since(h.lastTried) > TimeToLive {
delete(b.info, id)
}
}
b.mu.Unlock()
}
func (b *backoff) cleanupLoop(ctx context.Context) {
ticker := time.NewTicker(b.ci)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
ticker.Stop()
return // pubsub shutting down
case <-ticker.C:
b.cleanup()

View File

@ -23,9 +23,11 @@ func TestBackoff_Update(t *testing.T) {
b := newBackoff(ctx, size, cleanupInterval, maxBackoffAttempts)
b.mu.Lock()
if len(b.info) > 0 {
t.Fatal("non-empty info map for backoff")
}
b.mu.Unlock()
if d, err := b.updateAndGet(id1); d != time.Duration(0) || err != nil {
t.Fatalf("invalid initialization: %v, \t, %s", d, err)
@ -64,9 +66,11 @@ func TestBackoff_Update(t *testing.T) {
t.Fatalf("invalid backoff result, expected: %v, got: %v", MinBackoffDelay, got)
}
b.mu.Lock()
// sets last tried of id2 to long ago that it resets back upon next try.
// update attempts on id2 are below threshold, hence peer should never go beyond backoff attempt threshold.
b.info[id2].lastTried = time.Now().Add(-TimeToLive)
b.mu.Unlock()
got, err = b.updateAndGet(id2)
if err != nil {
t.Fatalf("unexpected error post update: %s", err)
@ -75,10 +79,11 @@ func TestBackoff_Update(t *testing.T) {
t.Fatalf("invalid ttl expiration, expected: %v, got: %v", time.Duration(0), got)
}
b.mu.Lock()
if len(b.info) != 2 {
t.Fatalf("pre-invalidation attempt, info map size mismatch, expected: %d, got: %d", 2, len(b.info))
}
b.mu.Unlock()
}
func TestBackoff_Clean(t *testing.T) {
@ -96,12 +101,16 @@ func TestBackoff_Clean(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error post update: %s", err)
}
b.mu.Lock()
b.info[id].lastTried = time.Now().Add(-TimeToLive) // enforces expiry
b.mu.Unlock()
}
b.mu.Lock()
if len(b.info) != size {
t.Fatalf("info map size mismatch, expected: %d, got: %d", size, len(b.info))
}
b.mu.Unlock()
// waits for a cleanup loop to kick-in
time.Sleep(2 * cleanupInterval)
@ -115,8 +124,10 @@ func TestBackoff_Clean(t *testing.T) {
t.Fatalf("invalid backoff result, expected: %v, got: %v", time.Duration(0), got)
}
b.mu.Lock()
// except "some-new-peer" every other records must be cleaned up
if len(b.info) != 1 {
t.Fatalf("info map size mismatch, expected: %d, got: %d", 1, len(b.info))
}
b.mu.Unlock()
}

View File

@ -48,9 +48,9 @@ func (t *Bitmask) SetScoreParams(p *BitmaskScoreParams) error {
}
t.mux.Lock()
defer t.mux.Unlock()
if t.closed {
t.mux.Unlock()
return ErrBitmaskClosed
}
@ -74,9 +74,11 @@ func (t *Bitmask) SetScoreParams(p *BitmaskScoreParams) error {
select {
case t.p.eval <- update:
err = <-result
t.mux.Unlock()
return err
case <-t.p.ctx.Done():
t.mux.Unlock()
return t.p.ctx.Err()
}
}
@ -85,8 +87,8 @@ func (t *Bitmask) SetScoreParams(p *BitmaskScoreParams) error {
// Multiple event handlers may be created and will operate independently of each other
func (t *Bitmask) EventHandler(opts ...BitmaskEventHandlerOpt) (*BitmaskEventHandler, error) {
t.mux.RLock()
defer t.mux.RUnlock()
if t.closed {
t.mux.RUnlock()
return nil, ErrBitmaskClosed
}
@ -101,6 +103,7 @@ func (t *Bitmask) EventHandler(opts ...BitmaskEventHandlerOpt) (*BitmaskEventHan
for _, opt := range opts {
err := opt(h)
if err != nil {
t.mux.RUnlock()
return nil, err
}
}
@ -120,21 +123,23 @@ func (t *Bitmask) EventHandler(opts ...BitmaskEventHandlerOpt) (*BitmaskEventHan
done <- struct{}{}
}:
case <-t.p.ctx.Done():
t.mux.RUnlock()
return nil, t.p.ctx.Err()
}
<-done
t.mux.RUnlock()
return h, nil
}
func (t *Bitmask) sendNotification(evt PeerEvent) {
t.evtHandlerMux.RLock()
defer t.evtHandlerMux.RUnlock()
for h := range t.evtHandlers {
h.sendNotification(evt)
}
t.evtHandlerMux.RUnlock()
}
// Subscribe returns a new Subscription for the bitmask.
@ -142,8 +147,9 @@ func (t *Bitmask) sendNotification(evt PeerEvent) {
// before the subscription is processed by the pubsub main loop and propagated to our peers.
func (t *Bitmask) Subscribe(opts ...SubOpt) (*Subscription, error) {
t.mux.RLock()
defer t.mux.RUnlock()
if t.closed {
t.mux.RUnlock()
return nil, ErrBitmaskClosed
}
@ -155,12 +161,13 @@ func (t *Bitmask) Subscribe(opts ...SubOpt) (*Subscription, error) {
for _, opt := range opts {
err := opt(sub)
if err != nil {
t.mux.RUnlock()
return nil, err
}
}
if sub.ch == nil {
sub.ch = make(chan *Message, 128)
sub.ch = make(chan *Message, 32)
}
out := make(chan *Subscription, 1)
@ -173,10 +180,13 @@ func (t *Bitmask) Subscribe(opts ...SubOpt) (*Subscription, error) {
resp: out,
}:
case <-t.p.ctx.Done():
t.mux.RUnlock()
return nil, t.p.ctx.Err()
}
return <-out, nil
subOut := <-out
t.mux.RUnlock()
return subOut, nil
}
// Relay enables message relaying for the bitmask and returns a reference
@ -184,8 +194,9 @@ func (t *Bitmask) Subscribe(opts ...SubOpt) (*Subscription, error) {
// To completely disable the relay, all references must be cancelled.
func (t *Bitmask) Relay() (RelayCancelFunc, error) {
t.mux.RLock()
defer t.mux.RUnlock()
if t.closed {
t.mux.RUnlock()
return nil, ErrBitmaskClosed
}
@ -199,10 +210,13 @@ func (t *Bitmask) Relay() (RelayCancelFunc, error) {
resp: out,
}:
case <-t.p.ctx.Done():
t.mux.RUnlock()
return nil, t.p.ctx.Err()
}
return <-out, nil
cancelFunc := <-out
t.mux.RUnlock()
return cancelFunc, nil
}
// RouterReady is a function that decides if a router is ready to publish
@ -220,10 +234,11 @@ type PublishOptions struct {
type PubOpt func(pub *PublishOptions) error
// Publish publishes data to bitmask.
func (t *Bitmask) Publish(ctx context.Context, data []byte, opts ...PubOpt) error {
func (t *Bitmask) Publish(ctx context.Context, bitmask []byte, data []byte, opts ...PubOpt) error {
t.mux.RLock()
defer t.mux.RUnlock()
if t.closed {
t.mux.RUnlock()
return ErrBitmaskClosed
}
@ -234,6 +249,7 @@ func (t *Bitmask) Publish(ctx context.Context, data []byte, opts ...PubOpt) erro
for _, opt := range opts {
err := opt(pub)
if err != nil {
t.mux.RUnlock()
return err
}
}
@ -241,16 +257,18 @@ func (t *Bitmask) Publish(ctx context.Context, data []byte, opts ...PubOpt) erro
if pub.customKey != nil && !pub.local {
key, pid = pub.customKey()
if key == nil {
t.mux.RUnlock()
return ErrNilSignKey
}
if len(pid) == 0 {
t.mux.RUnlock()
return ErrEmptyPeerID
}
}
m := &pb.Message{
Data: data,
Bitmask: t.bitmask,
Bitmask: bitmask,
From: nil,
Seqno: nil,
}
@ -262,6 +280,7 @@ func (t *Bitmask) Publish(ctx context.Context, data []byte, opts ...PubOpt) erro
m.From = []byte(pid)
err := signMessage(pid, key, m)
if err != nil {
t.mux.RUnlock()
return err
}
}
@ -286,28 +305,43 @@ func (t *Bitmask) Publish(ctx context.Context, data []byte, opts ...PubOpt) erro
res <- done
}:
if <-res {
if ticker != nil {
ticker.Stop()
}
break readyLoop
}
case <-t.p.ctx.Done():
if ticker != nil {
ticker.Stop()
}
t.mux.RUnlock()
return t.p.ctx.Err()
case <-ctx.Done():
if ticker != nil {
ticker.Stop()
}
t.mux.RUnlock()
return ctx.Err()
}
if ticker == nil {
ticker = time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
}
select {
case <-ticker.C:
case <-ctx.Done():
ticker.Stop()
t.mux.RUnlock()
return fmt.Errorf("router is not ready: %w", ctx.Err())
}
}
}
}
return t.p.val.PushLocal(&Message{m, "", t.p.host.ID(), nil, pub.local})
err := t.p.val.PushLocal(&Message{m, nil, t.p.host.ID(), nil, pub.local})
t.mux.RUnlock()
return err
}
// WithReadiness returns a publishing option for only publishing when the router is ready.
@ -347,8 +381,9 @@ func WithSecretKeyAndPeerId(key crypto.PrivKey, pid peer.ID) PubOpt {
// Does not error if the bitmask is already closed.
func (t *Bitmask) Close() error {
t.mux.Lock()
defer t.mux.Unlock()
if t.closed {
t.mux.Unlock()
return nil
}
@ -357,6 +392,7 @@ func (t *Bitmask) Close() error {
select {
case t.p.rmBitmask <- req:
case <-t.p.ctx.Done():
t.mux.Unlock()
return t.p.ctx.Err()
}
@ -366,18 +402,22 @@ func (t *Bitmask) Close() error {
t.closed = true
}
t.mux.Unlock()
return err
}
// ListPeers returns a list of peers we are connected to in the given bitmask.
func (t *Bitmask) ListPeers() []peer.ID {
t.mux.RLock()
defer t.mux.RUnlock()
if t.closed {
t.mux.RUnlock()
return []peer.ID{}
}
return t.p.ListPeers(t.bitmask)
l := t.p.ListPeers(t.bitmask)
t.mux.RUnlock()
return l
}
type EventType int

View File

@ -25,7 +25,12 @@ func getBitmasks(psubs []*PubSub, bitmask []byte, opts ...BitmaskOpt) []*Bitmask
if err != nil {
panic(err)
}
bitmasks[i] = t
if len(t) != 1 {
panic("multi bit bitmasks not supported for tests using getBitmasks")
}
bitmasks[i] = t[0]
}
return bitmasks
@ -98,9 +103,9 @@ func testBitmaskCloseWithOpenResource(t *testing.T, openResource func(bitmask *B
defer cancel()
const numHosts = 1
bitmaskID := []byte{0xf0, 0x0b, 0xa1, 0x20}
hosts := getNetHosts(t, ctx, numHosts)
ps := getPubsub(ctx, hosts[0])
bitmaskID := []byte{0x00, 0x01}
hosts := getDefaultHosts(t, numHosts)
ps := getBlossomSub(ctx, hosts[0])
// Try create and cancel bitmask
bitmask, err := ps.Join(bitmaskID)
@ -108,7 +113,7 @@ func testBitmaskCloseWithOpenResource(t *testing.T, openResource func(bitmask *B
t.Fatal(err)
}
if err := bitmask.Close(); err != nil {
if err := bitmask[0].Close(); err != nil {
t.Fatal(err)
}
@ -118,9 +123,9 @@ func testBitmaskCloseWithOpenResource(t *testing.T, openResource func(bitmask *B
t.Fatal(err)
}
openResource(bitmask)
openResource(bitmask[0])
if err := bitmask.Close(); err == nil {
if err := bitmask[0].Close(); err == nil {
t.Fatal("expected an error closing a bitmask with an open resource")
}
@ -128,7 +133,7 @@ func testBitmaskCloseWithOpenResource(t *testing.T, openResource func(bitmask *B
closeResource()
time.Sleep(time.Millisecond * 100)
if err := bitmask.Close(); err != nil {
if err := bitmask[0].Close(); err != nil {
t.Fatal(err)
}
}
@ -138,11 +143,11 @@ func TestBitmaskReuse(t *testing.T) {
defer cancel()
const numHosts = 2
bitmaskID := []byte{0xf0, 0x0b, 0xa1, 0x20}
hosts := getNetHosts(t, ctx, numHosts)
bitmaskID := []byte{0x00, 0x01}
hosts := getDefaultHosts(t, numHosts)
sender := getPubsub(ctx, hosts[0], WithDiscovery(&dummyDiscovery{}))
receiver := getPubsub(ctx, hosts[1])
sender := getBlossomSub(ctx, hosts[0])
receiver := getBlossomSub(ctx, hosts[1])
connectAll(t, hosts)
@ -158,13 +163,18 @@ func TestBitmaskReuse(t *testing.T) {
t.Fatal(err)
}
sub, err := receiveBitmask.Subscribe()
_, err = sendBitmask[0].Subscribe()
if err != nil {
t.Fatal(err)
}
sub, err := receiveBitmask[0].Subscribe()
if err != nil {
t.Fatal(err)
}
firstMsg := []byte("1")
if err := sendBitmask.Publish(ctx, firstMsg, WithReadiness(MinBitmaskSize(1))); err != nil {
if err := sendBitmask[0].Publish(ctx, bitmaskID, firstMsg, WithReadiness(MinBitmaskSize(1))); err != nil {
t.Fatal(err)
}
@ -176,54 +186,10 @@ func TestBitmaskReuse(t *testing.T) {
t.Fatal("received incorrect message")
}
if err := sendBitmask.Close(); err != nil {
t.Fatal(err)
}
// Recreate the same bitmask
newSendBitmask, err := sender.Join(bitmaskID)
if err != nil {
t.Fatal(err)
}
// Try sending data with original bitmask
illegalSend := []byte("illegal")
if err := sendBitmask.Publish(ctx, illegalSend); err != ErrBitmaskClosed {
t.Fatal(err)
}
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, time.Second*2)
defer timeoutCancel()
msg, err = sub.Next(timeoutCtx)
if err != context.DeadlineExceeded {
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(msg.GetData(), illegalSend) {
t.Fatal("received incorrect message from illegal bitmask")
}
t.Fatal("received message sent by illegal bitmask")
}
timeoutCancel()
// Try cancelling the new bitmask by using the original bitmask
if err := sendBitmask.Close(); err != nil {
t.Fatal(err)
}
secondMsg := []byte("2")
if err := newSendBitmask.Publish(ctx, secondMsg); err != nil {
t.Fatal(err)
}
timeoutCtx, timeoutCancel = context.WithTimeout(ctx, time.Second*2)
defer timeoutCancel()
msg, err = sub.Next(timeoutCtx)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(msg.GetData(), secondMsg) {
t.Fatal("received incorrect message")
_, err = sender.Join(bitmaskID)
if err == nil {
t.Fatal("did not error on reuse of bitmask")
}
}
@ -232,9 +198,9 @@ func TestBitmaskEventHandlerCancel(t *testing.T) {
defer cancel()
const numHosts = 5
bitmaskID := []byte{0xf0, 0x0b, 0xa1, 0x20}
hosts := getNetHosts(t, ctx, numHosts)
ps := getPubsub(ctx, hosts[0])
bitmaskID := []byte{0x00, 0x01}
hosts := getDefaultHosts(t, numHosts)
ps := getBlossomSub(ctx, hosts[0])
// Try create and cancel bitmask
bitmask, err := ps.Join(bitmaskID)
@ -242,7 +208,7 @@ func TestBitmaskEventHandlerCancel(t *testing.T) {
t.Fatal(err)
}
evts, err := bitmask.EventHandler()
evts, err := bitmask[0].EventHandler()
if err != nil {
t.Fatal(err)
}
@ -265,8 +231,8 @@ func TestSubscriptionJoinNotification(t *testing.T) {
const numLateSubscribers = 10
const numHosts = 20
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), []byte{0xf0, 0x0b, 0xa1, 0x20})
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), []byte{0x00, 0x01})
evts := getBitmaskEvts(bitmasks)
subs := make([]*Subscription, numHosts)
@ -331,9 +297,9 @@ func TestSubscriptionLeaveNotification(t *testing.T) {
defer cancel()
const numHosts = 20
hosts := getNetHosts(t, ctx, numHosts)
psubs := getPubsubs(ctx, hosts)
bitmasks := getBitmasks(psubs, []byte{0xf0, 0x0b, 0xa1, 0x20})
hosts := getDefaultHosts(t, numHosts)
psubs := getBlossomSubs(ctx, hosts)
bitmasks := getBitmasks(psubs, []byte{0x00, 0x01})
evts := getBitmaskEvts(bitmasks)
subs := make([]*Subscription, numHosts)
@ -411,11 +377,11 @@ func TestSubscriptionManyNotifications(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 33
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
evts := getBitmaskEvts(bitmasks)
subs := make([]*Subscription, numHosts)
@ -516,11 +482,11 @@ func TestSubscriptionNotificationSubUnSub(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 35
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
for i := 1; i < numHosts; i++ {
connect(t, hosts[0], hosts[i])
@ -534,11 +500,11 @@ func TestBitmaskRelay(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 5
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
// [0.Rel] - [1.Rel] - [2.Sub]
// |
@ -552,6 +518,7 @@ func TestBitmaskRelay(t *testing.T) {
time.Sleep(time.Millisecond * 100)
var subs []*Subscription
var subscribedBitmasks []*Bitmask
for i, bitmask := range bitmasks {
if i == 2 || i == 4 {
@ -561,6 +528,7 @@ func TestBitmaskRelay(t *testing.T) {
}
subs = append(subs, sub)
subscribedBitmasks = append(subscribedBitmasks, bitmask)
} else {
_, err := bitmask.Relay()
if err != nil {
@ -569,14 +537,15 @@ func TestBitmaskRelay(t *testing.T) {
}
}
time.Sleep(time.Millisecond * 100)
// Give enough time to build the relay
time.Sleep(time.Second * 2)
for i := 0; i < 100; i++ {
msg := []byte("message")
msg := []byte(fmt.Sprintf("message %d", i))
owner := rand.Intn(len(bitmasks))
owner := rand.Intn(len(subscribedBitmasks))
err := bitmasks[owner].Publish(ctx, msg)
err := subscribedBitmasks[owner].Publish(ctx, subscribedBitmasks[owner].bitmask, msg)
if err != nil {
t.Fatal(err)
}
@ -598,11 +567,11 @@ func TestBitmaskRelayReuse(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 1
hosts := getNetHosts(t, ctx, numHosts)
pubsubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, numHosts)
pubsubs := getBlossomSubs(ctx, hosts)
bitmasks := getBitmasks(pubsubs, bitmask)
relay1Cancel, err := bitmasks[0].Relay()
@ -665,11 +634,11 @@ func TestBitmaskRelayOnClosedBitmask(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 1
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
err := bitmasks[0].Close()
if err != nil {
@ -687,9 +656,9 @@ func TestProducePanic(t *testing.T) {
defer cancel()
const numHosts = 5
bitmaskID := []byte{0xf0, 0x0b, 0xa1, 0x20}
hosts := getNetHosts(t, ctx, numHosts)
ps := getPubsub(ctx, hosts[0])
bitmaskID := []byte{0x00, 0x01}
hosts := getDefaultHosts(t, numHosts)
ps := getBlossomSub(ctx, hosts[0])
// Create bitmask
bitmask, err := ps.Join(bitmaskID)
@ -698,13 +667,13 @@ func TestProducePanic(t *testing.T) {
}
// Create subscription we're going to cancel
s, err := bitmask.Subscribe()
s, err := bitmask[0].Subscribe()
if err != nil {
t.Fatal(err)
}
// Create second subscription to keep us alive on the subscription map
// after the first one is canceled
s2, err := bitmask.Subscribe()
s2, err := bitmask[0].Subscribe()
if err != nil {
t.Fatal(err)
}
@ -789,12 +758,12 @@ func TestMinBitmaskSizeNoDiscovery(t *testing.T) {
defer cancel()
const numHosts = 3
bitmaskID := []byte{0xf0, 0x0b, 0xa1, 0x20}
hosts := getNetHosts(t, ctx, numHosts)
bitmaskID := []byte{0x00, 0x01}
hosts := getDefaultHosts(t, numHosts)
sender := getPubsub(ctx, hosts[0])
receiver1 := getPubsub(ctx, hosts[1])
receiver2 := getPubsub(ctx, hosts[2])
sender := getBlossomSub(ctx, hosts[0])
receiver1 := getBlossomSub(ctx, hosts[1])
receiver2 := getBlossomSub(ctx, hosts[2])
connectAll(t, hosts)
@ -804,19 +773,24 @@ func TestMinBitmaskSizeNoDiscovery(t *testing.T) {
t.Fatal(err)
}
_, err = sendBitmask[0].Subscribe()
if err != nil {
t.Fatal(err)
}
// Receiver creates and subscribes to the bitmask
receiveBitmask1, err := receiver1.Join(bitmaskID)
if err != nil {
t.Fatal(err)
}
sub1, err := receiveBitmask1.Subscribe()
sub1, err := receiveBitmask1[0].Subscribe()
if err != nil {
t.Fatal(err)
}
oneMsg := []byte("minimum one")
if err := sendBitmask.Publish(ctx, oneMsg, WithReadiness(MinBitmaskSize(1))); err != nil {
if err := sendBitmask[0].Publish(ctx, sendBitmask[0].bitmask, oneMsg, WithReadiness(MinBitmaskSize(1))); err != nil {
t.Fatal(err)
}
@ -832,7 +806,7 @@ func TestMinBitmaskSizeNoDiscovery(t *testing.T) {
{
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
if err := sendBitmask.Publish(ctx, twoMsg, WithReadiness(MinBitmaskSize(2))); !errors.Is(err, context.DeadlineExceeded) {
if err := sendBitmask[0].Publish(ctx, sendBitmask[0].bitmask, twoMsg, WithReadiness(MinBitmaskSize(2))); !errors.Is(err, context.DeadlineExceeded) {
t.Fatal(err)
}
}
@ -843,15 +817,17 @@ func TestMinBitmaskSizeNoDiscovery(t *testing.T) {
t.Fatal(err)
}
sub2, err := receiveBitmask2.Subscribe()
sub2, err := receiveBitmask2[0].Subscribe()
if err != nil {
t.Fatal(err)
}
twoMsg = []byte("minimum two, 2")
{
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
if err := sendBitmask.Publish(ctx, twoMsg, WithReadiness(MinBitmaskSize(2))); err != nil {
if err := sendBitmask[0].Publish(ctx, sendBitmask[0].bitmask, twoMsg, WithReadiness(MinBitmaskSize(2))); err != nil {
t.Fatal(err)
}
}
@ -867,20 +843,20 @@ func TestWithBitmaskMsgIdFunction(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmaskA, bitmaskB := []byte{0xf0, 0x0b, 0xa1, 0x2a}, []byte{0xf0, 0x0b, 0xa1, 0x2b}
bitmaskA, bitmaskB := []byte{0x20, 0x00, 0x00, 0x00}, []byte{0x00, 0x00, 0x80, 0x00}
const numHosts = 2
hosts := getNetHosts(t, ctx, numHosts)
pubsubs := getPubsubs(ctx, hosts, WithMessageIdFn(func(pmsg *pb.Message) string {
hosts := getDefaultHosts(t, numHosts)
pubsubs := getBlossomSubs(ctx, hosts, WithMessageIdFn(func(pmsg *pb.Message) []byte {
hash := sha256.Sum256(pmsg.Data)
return string(hash[:])
return hash[:]
}))
connectAll(t, hosts)
bitmasksA := getBitmasks(pubsubs, bitmaskA) // uses global msgIdFn
bitmasksB := getBitmasks(pubsubs, bitmaskB, WithBitmaskMessageIdFn(func(pmsg *pb.Message) string { // uses custom
bitmasksB := getBitmasks(pubsubs, bitmaskB, WithBitmaskMessageIdFn(func(pmsg *pb.Message) []byte { // uses custom
hash := sha1.Sum(pmsg.Data)
return string(hash[:])
return hash[:]
}))
payload := []byte("pubsub rocks")
@ -890,7 +866,12 @@ func TestWithBitmaskMsgIdFunction(t *testing.T) {
t.Fatal(err)
}
err = bitmasksA[1].Publish(ctx, payload, WithReadiness(MinBitmaskSize(1)))
_, err = bitmasksA[1].Subscribe()
if err != nil {
t.Fatal(err)
}
err = bitmasksA[1].Publish(ctx, bitmasksA[1].bitmask, payload, WithReadiness(MinBitmaskSize(1)))
if err != nil {
t.Fatal(err)
}
@ -905,7 +886,14 @@ func TestWithBitmaskMsgIdFunction(t *testing.T) {
t.Fatal(err)
}
err = bitmasksB[1].Publish(ctx, payload, WithReadiness(MinBitmaskSize(1)))
_, err = bitmasksB[1].Subscribe()
if err != nil {
t.Fatal(err)
}
payload = []byte("but blossomsub has more sensible scale strategies")
err = bitmasksB[1].Publish(ctx, bitmasksB[1].bitmask, payload, WithReadiness(MinBitmaskSize(1)))
if err != nil {
t.Fatal(err)
}
@ -915,7 +903,7 @@ func TestWithBitmaskMsgIdFunction(t *testing.T) {
t.Fatal(err)
}
if msgA.ID == msgB.ID {
if bytes.Equal(msgA.ID, msgB.ID) {
t.Fatal("msg ids are equal")
}
}
@ -926,23 +914,23 @@ func TestBitmaskPublishWithKeyInvalidParameters(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 5
virtualPeer := tnet.RandPeerNetParamsOrFatal(t)
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
t.Run("nil sign private key should error", func(t *testing.T) {
withVirtualKey := WithSecretKeyAndPeerId(nil, virtualPeer.ID)
err := bitmasks[0].Publish(ctx, []byte("buff"), withVirtualKey)
err := bitmasks[0].Publish(ctx, bitmask, []byte("buff"), withVirtualKey)
if err != ErrNilSignKey {
t.Fatal("error should have been of type errNilSignKey")
}
})
t.Run("empty peer ID should error", func(t *testing.T) {
withVirtualKey := WithSecretKeyAndPeerId(virtualPeer.PrivKey, "")
err := bitmasks[0].Publish(ctx, []byte("buff"), withVirtualKey)
err := bitmasks[0].Publish(ctx, bitmask, []byte("buff2"), withVirtualKey)
if err != ErrEmptyPeerID {
t.Fatal("error should have been of type errEmptyPeerID")
}
@ -953,12 +941,12 @@ func TestBitmaskRelayPublishWithKey(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
const numHosts = 5
virtualPeer := tnet.RandPeerNetParamsOrFatal(t)
hosts := getNetHosts(t, ctx, numHosts)
bitmasks := getBitmasks(getPubsubs(ctx, hosts), bitmask)
hosts := getDefaultHosts(t, numHosts)
bitmasks := getBitmasks(getBlossomSubs(ctx, hosts), bitmask)
// [0.Rel] - [1.Rel] - [2.Sub]
// |
@ -972,6 +960,7 @@ func TestBitmaskRelayPublishWithKey(t *testing.T) {
time.Sleep(time.Millisecond * 100)
var subs []*Subscription
var senders []*Bitmask
for i, bitmaskValue := range bitmasks {
if i == 2 || i == 4 {
@ -981,6 +970,7 @@ func TestBitmaskRelayPublishWithKey(t *testing.T) {
}
subs = append(subs, sub)
senders = append(senders, bitmaskValue)
} else {
_, err := bitmaskValue.Relay()
if err != nil {
@ -989,15 +979,15 @@ func TestBitmaskRelayPublishWithKey(t *testing.T) {
}
}
time.Sleep(time.Millisecond * 100)
time.Sleep(time.Second * 2)
for i := 0; i < 100; i++ {
msg := []byte("message")
msg := []byte(fmt.Sprintf("message %d", i))
owner := rand.Intn(len(bitmasks))
owner := rand.Intn(len(senders))
withVirtualKey := WithSecretKeyAndPeerId(virtualPeer.PrivKey, virtualPeer.ID)
err := bitmasks[owner].Publish(ctx, msg, withVirtualKey)
err := senders[owner].Publish(ctx, senders[owner].bitmask, msg, withVirtualKey)
if err != nil {
t.Fatal(err)
}
@ -1022,10 +1012,10 @@ func TestWithLocalPublication(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bitmask := []byte{0x7e, 57}
bitmask := []byte{0x01, 0x00}
hosts := getNetHosts(t, ctx, 2)
pubsubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
pubsubs := getBlossomSubs(ctx, hosts)
bitmasks := getBitmasks(pubsubs, bitmask)
connectAll(t, hosts)
@ -1041,7 +1031,7 @@ func TestWithLocalPublication(t *testing.T) {
t.Fatal(err)
}
err = bitmasks[0].Publish(ctx, payload, WithLocalPublication(true))
err = bitmasks[0].Publish(ctx, bitmasks[0].bitmask, payload, WithLocalPublication(true))
if err != nil {
t.Fatal(err)
}

View File

@ -38,11 +38,16 @@ func TestBlacklist(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 2)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
psubs := getBlossomSubs(ctx, hosts)
connect(t, hosts[0], hosts[1])
sub, err := psubs[1].Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
bitmasks, err := psubs[0].Join([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
sub, err := psubs[1].Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
@ -51,11 +56,11 @@ func TestBlacklist(t *testing.T) {
psubs[1].BlacklistPeer(hosts[0].ID())
time.Sleep(time.Millisecond * 100)
psubs[0].Publish([]byte{0xff, 0x00, 0x00, 0x00}, []byte("message"))
bitmasks[0].Publish(ctx, bitmasks[0].bitmask, []byte("message"))
wctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
_, err = sub.Next(wctx)
_, err = sub[0].Next(wctx)
if err == nil {
t.Fatal("got message from blacklisted peer")
@ -66,16 +71,21 @@ func TestBlacklist2(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 2)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
psubs := getBlossomSubs(ctx, hosts)
connect(t, hosts[0], hosts[1])
_, err := psubs[0].Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
bitmasks, err := psubs[0].Join([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
sub1, err := psubs[1].Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
_, err = psubs[0].Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
sub1, err := psubs[1].Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
@ -84,11 +94,11 @@ func TestBlacklist2(t *testing.T) {
psubs[1].BlacklistPeer(hosts[0].ID())
time.Sleep(time.Millisecond * 100)
psubs[0].Publish([]byte{0xff, 0x00, 0x00, 0x00}, []byte("message"))
bitmasks[0].Publish(ctx, bitmasks[0].bitmask, []byte("message"))
wctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
_, err = sub1.Next(wctx)
_, err = sub1[0].Next(wctx)
if err == nil {
t.Fatal("got message from blacklisted peer")
@ -99,25 +109,30 @@ func TestBlacklist3(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 2)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
psubs := getBlossomSubs(ctx, hosts)
psubs[1].BlacklistPeer(hosts[0].ID())
time.Sleep(time.Millisecond * 100)
connect(t, hosts[0], hosts[1])
sub, err := psubs[1].Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
bitmasks, err := psubs[0].Join([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
sub, err := psubs[1].Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond * 100)
psubs[0].Publish([]byte{0xff, 0x00, 0x00, 0x00}, []byte("message"))
bitmasks[0].Publish(ctx, bitmasks[0].bitmask, []byte("message"))
wctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
_, err = sub.Next(wctx)
_, err = sub[0].Next(wctx)
if err == nil {
t.Fatal("got message from blacklisted peer")

View File

@ -4,8 +4,11 @@ import (
"bytes"
"context"
"fmt"
"io"
"math/rand"
"slices"
"sort"
"sync"
"time"
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
@ -21,8 +24,8 @@ import (
)
const (
// BlossomSubID_v12 is the protocol ID for version 1.2.1 of the BlossomSub protocol.
BlossomSubID_v12 = protocol.ID("/blossomsub/1.2.1")
// BlossomSubID_v2 is the protocol ID for version 2.0.0 of the BlossomSub protocol.
BlossomSubID_v2 = protocol.ID("/blossomsub/2.0.0")
)
// Defines the default BlossomSub parameters.
@ -35,8 +38,8 @@ var (
BlossomSubHistoryLength = 5
BlossomSubHistoryGossip = 3
BlossomSubDlazy = 6
BlossomSubGossipFactor = 0.25
BlossomSubGossipRetransmission = 1
BlossomSubGossipRetransmission = 3
BlossomSubBitmaskWidth = 256
BlossomSubHeartbeatInitialDelay = 100 * time.Millisecond
BlossomSubHeartbeatInterval = 1 * time.Second
BlossomSubFanoutTTL = 60 * time.Second
@ -87,6 +90,9 @@ type BlossomSubParams struct {
// Dout must be set below Dlo, and must not exceed D / 2.
Dout int
// BitmaskWidth sets the size of the bitmask for subscriptions.
BitmaskWidth int
// gossip parameters
// HistoryLength controls the size of the message cache used for gossip.
@ -105,15 +111,9 @@ type BlossomSubParams struct {
// Dlazy affects how many peers we will emit gossip to at each heartbeat.
// We will send gossip to at least Dlazy peers outside our mesh. The actual
// number may be more, depending on GossipFactor and how many peers we're
// connected to.
// number may be less, depending on how many peers we're connected to.
Dlazy int
// GossipFactor affects how many peers we will emit gossip to at each heartbeat.
// We will send gossip to GossipFactor * (total number of non-mesh peers), or
// Dlazy, whichever is greater.
GossipFactor float64
// GossipRetransmission controls how many times we will allow a peer to request
// the same message id through IWANT gossip before we start ignoring them. This is designed
// to prevent peers from spamming us with requests and wasting our resources.
@ -214,7 +214,7 @@ func NewBlossomSubWithRouter(ctx context.Context, h host.Host, rt PubSubRouter,
}
// NewBlossomSubRouter returns a new BlossomSubRouter with custom parameters.
func NewBlossomSubRouter(h host.Host, params BlossomSubParams, addrBook peerstore.AddrBook) *BlossomSubRouter {
func NewBlossomSubRouter(h host.Host, params BlossomSubParams) *BlossomSubRouter {
return &BlossomSubRouter{
peers: make(map[peer.ID]protocol.ID),
mesh: make(map[string]map[peer.ID]struct{}),
@ -222,7 +222,7 @@ func NewBlossomSubRouter(h host.Host, params BlossomSubParams, addrBook peerstor
lastpub: make(map[string]int64),
gossip: make(map[peer.ID][]*pb.ControlIHave),
control: make(map[peer.ID]*pb.ControlMessage),
cab: addrBook,
cab: pstoremem.NewAddrBook(),
backoff: make(map[string]map[peer.ID]time.Time),
peerhave: make(map[peer.ID]int),
iasked: make(map[peer.ID]int),
@ -272,7 +272,6 @@ func DefaultBlossomSubParams() BlossomSubParams {
HistoryLength: BlossomSubHistoryLength,
HistoryGossip: BlossomSubHistoryGossip,
Dlazy: BlossomSubDlazy,
GossipFactor: BlossomSubGossipFactor,
GossipRetransmission: BlossomSubGossipRetransmission,
HeartbeatInitialDelay: BlossomSubHeartbeatInitialDelay,
HeartbeatInterval: BlossomSubHeartbeatInterval,
@ -453,6 +452,7 @@ type BlossomSubRouter struct {
backoff map[string]map[peer.ID]time.Time // prune backoff
connect chan connectInfo // px connection requests
cab peerstore.AddrBook
meshMx sync.RWMutex
protos []protocol.ID
feature BlossomSubFeatureTest
@ -556,11 +556,18 @@ func (bs *BlossomSubRouter) manageAddrBook() {
log.Errorf("failed to subscribe to peer identification events: %v", err)
return
}
defer sub.Close()
for {
select {
case <-bs.p.ctx.Done():
cabCloser, ok := bs.cab.(io.Closer)
if ok {
errClose := cabCloser.Close()
if errClose != nil {
log.Warnf("failed to close addr book: %v", errClose)
}
}
sub.Close()
return
case ev := <-sub.Out():
switch ev := ev.(type) {
@ -620,9 +627,11 @@ func (bs *BlossomSubRouter) RemovePeer(p peer.ID) {
log.Debugf("PEERDOWN: Remove disconnected peer %s", p)
bs.tracer.RemovePeer(p)
delete(bs.peers, p)
bs.meshMx.Lock()
for _, peers := range bs.mesh {
delete(peers, p)
}
bs.meshMx.Unlock()
for _, peers := range bs.fanout {
delete(peers, p)
}
@ -638,7 +647,7 @@ func (bs *BlossomSubRouter) EnoughPeers(bitmask []byte, suggested int) bool {
return false
}
fsPeers, gsPeers := 0, 0
fsPeers, bsPeers := 0, 0
// floodsub peers
for p := range tmap {
if !bs.feature(BlossomSubFeatureMesh, bs.peers[p]) {
@ -646,14 +655,16 @@ func (bs *BlossomSubRouter) EnoughPeers(bitmask []byte, suggested int) bool {
}
}
bs.meshMx.RLock()
// BlossomSub peers
gsPeers = len(bs.mesh[string(bitmask)])
bsPeers = len(bs.mesh[string(bitmask)])
bs.meshMx.RUnlock()
if suggested == 0 {
suggested = bs.params.Dlo
}
if fsPeers+gsPeers >= suggested || gsPeers >= bs.params.Dhi {
if fsPeers+bsPeers >= suggested || bsPeers >= bs.params.Dhi {
return true
}
@ -719,7 +730,9 @@ func (bs *BlossomSubRouter) handleIHave(p peer.ID, ctl *pb.ControlMessage) []*pb
iwant := make(map[string]struct{})
for _, ihave := range ctl.GetIhave() {
bitmask := ihave.GetBitmask()
bs.meshMx.RLock()
_, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
continue
}
@ -728,11 +741,18 @@ func (bs *BlossomSubRouter) handleIHave(p peer.ID, ctl *pb.ControlMessage) []*pb
continue
}
for _, mid := range ihave.GetMessageIDs() {
checkIwantMsgsLoop:
for msgIdx, mid := range ihave.GetMessageIDs() {
// prevent remote peer from sending too many msg_ids on a single IHAVE message
if msgIdx >= bs.params.MaxIHaveLength {
log.Debugf("IHAVE: peer %s has sent IHAVE on bitmask %s with too many messages (%d); ignoring remaining msgs", p, bitmask, len(ihave.MessageIDs))
break checkIwantMsgsLoop
}
if bs.p.seenMessage(mid) {
continue
}
iwant[mid] = struct{}{}
iwant[string(mid)] = struct{}{}
}
}
@ -747,9 +767,9 @@ func (bs *BlossomSubRouter) handleIHave(p peer.ID, ctl *pb.ControlMessage) []*pb
log.Debugf("IHAVE: Asking for %d out of %d messages from %s", iask, len(iwant), p)
iwantlst := make([]string, 0, len(iwant))
iwantlst := make([][]byte, 0, len(iwant))
for mid := range iwant {
iwantlst = append(iwantlst, mid)
iwantlst = append(iwantlst, []byte(mid))
}
// truncate to the messages we are actually asking for and update the iasked counter
@ -786,7 +806,7 @@ func (bs *BlossomSubRouter) handleIWant(p peer.ID, ctl *pb.ControlMessage) []*pb
continue
}
ihave[mid] = msg.Message
ihave[string(mid)] = msg.Message
}
}
@ -818,7 +838,9 @@ func (bs *BlossomSubRouter) handleGraft(p peer.ID, ctl *pb.ControlMessage) []*pb
continue
}
bs.meshMx.RLock()
peers, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
// don't do PX when there is an unknown bitmask to avoid leaking our peers
doPX = false
@ -907,7 +929,9 @@ func (bs *BlossomSubRouter) handlePrune(p peer.ID, ctl *pb.ControlMessage) {
for _, prune := range ctl.GetPrune() {
bitmask := prune.GetBitmask()
bs.meshMx.RLock()
peers, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
continue
}
@ -1046,57 +1070,73 @@ func (bs *BlossomSubRouter) Publish(msg *Message) {
tosend := make(map[peer.ID]struct{})
// any peers in the bitmask?
tmap, ok := bs.p.bitmasks[string(bitmask)]
if !ok {
return
}
if bs.floodPublish && from == bs.p.host.ID() {
for p := range tmap {
_, direct := bs.direct[p]
if direct || bs.score.Score(p) >= bs.publishThreshold {
tosend[p] = struct{}{}
}
}
} else {
// direct peers
for p := range bs.direct {
_, inBitmask := tmap[p]
if inBitmask {
tosend[p] = struct{}{}
}
sliced := SliceBitmask(bitmask)
// bloom publish:
if len(sliced) != 1 {
// any peers in all slices of the bitmask?
peers := bs.p.getPeersInBitmask(bitmask)
if len(peers) == 0 {
return
}
// floodsub peers
for p := range tmap {
if !bs.feature(BlossomSubFeatureMesh, bs.peers[p]) && bs.score.Score(p) >= bs.publishThreshold {
tosend[p] = struct{}{}
}
for _, p := range peers {
tosend[p] = struct{}{}
}
// BlossomSub peers
gmap, ok := bs.mesh[string(bitmask)]
} else { // classic gossip mesh
// any peers in the bitmask?
tmap, ok := bs.p.bitmasks[string(bitmask)]
if !ok {
// we are not in the mesh for bitmask, use fanout peers
gmap, ok = bs.fanout[string(bitmask)]
if !ok || len(gmap) == 0 {
// we don't have any, pick some with score above the publish threshold
peers := bs.getPeers(bitmask, bs.params.D, func(p peer.ID) bool {
_, direct := bs.direct[p]
return !direct && bs.score.Score(p) >= bs.publishThreshold
})
return
}
if len(peers) > 0 {
gmap = peerListToMap(peers)
bs.fanout[string(bitmask)] = gmap
if bs.floodPublish && from == bs.p.host.ID() {
for p := range tmap {
_, direct := bs.direct[p]
if direct || bs.score.Score(p) >= bs.publishThreshold {
tosend[p] = struct{}{}
}
}
} else {
// direct peers
for p := range bs.direct {
_, inBitmask := tmap[p]
if inBitmask {
tosend[p] = struct{}{}
}
}
bs.lastpub[string(bitmask)] = time.Now().UnixNano()
}
for p := range gmap {
tosend[p] = struct{}{}
// floodsub peers
for p := range tmap {
if !bs.feature(BlossomSubFeatureMesh, bs.peers[p]) && bs.score.Score(p) >= bs.publishThreshold {
tosend[p] = struct{}{}
}
}
// BlossomSub peers
bs.meshMx.RLock()
gmap, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
// we are not in the mesh for bitmask, use fanout peers
gmap, ok = bs.fanout[string(bitmask)]
if !ok || len(gmap) == 0 {
// we don't have any, pick some with score above the publish threshold
peers := bs.getPeers(bitmask, bs.params.D, func(p peer.ID) bool {
_, direct := bs.direct[p]
return !direct && bs.score.Score(p) >= bs.publishThreshold
})
if len(peers) > 0 {
gmap = peerListToMap(peers)
bs.fanout[string(bitmask)] = gmap
}
}
bs.lastpub[string(bitmask)] = time.Now().UnixNano()
}
for p := range gmap {
tosend[p] = struct{}{}
}
}
}
@ -1111,7 +1151,9 @@ func (bs *BlossomSubRouter) Publish(msg *Message) {
}
func (bs *BlossomSubRouter) Join(bitmask []byte) {
bs.meshMx.RLock()
gmap, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if ok {
return
}
@ -1146,7 +1188,9 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) {
}
}
bs.meshMx.Lock()
bs.mesh[string(bitmask)] = gmap
bs.meshMx.Unlock()
delete(bs.fanout, string(bitmask))
delete(bs.lastpub, string(bitmask))
} else {
@ -1158,7 +1202,9 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) {
return !direct && !doBackOff && bs.score.Score(p) >= 0
})
gmap = peerListToMap(peers)
bs.meshMx.Lock()
bs.mesh[string(bitmask)] = gmap
bs.meshMx.Unlock()
}
for p := range gmap {
@ -1169,7 +1215,9 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) {
}
func (bs *BlossomSubRouter) Leave(bitmask []byte) {
bs.meshMx.RLock()
gmap, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
return
}
@ -1177,7 +1225,9 @@ func (bs *BlossomSubRouter) Leave(bitmask []byte) {
log.Debugf("LEAVE %s", bitmask)
bs.tracer.Leave(bitmask)
bs.meshMx.Lock()
delete(bs.mesh, string(bitmask))
bs.meshMx.Unlock()
for p := range gmap {
log.Debugf("LEAVE: Remove mesh link to %s in %s", p, bitmask)
@ -1226,7 +1276,9 @@ func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC) {
delete(bs.gossip, p)
}
bs.p.peersMx.RLock()
mch, ok := bs.p.peers[p]
bs.p.peersMx.RUnlock()
if !ok {
return
}
@ -1237,8 +1289,9 @@ func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC) {
return
}
outCopy := copyRPC(out)
// Potentially split the RPC into multiple RPCs that are below the max message size
outRPCs := appendOrMergeRPC(nil, bs.p.maxMessageSize, *out)
outRPCs := appendOrMergeRPC(nil, bs.p.maxMessageSize, outCopy)
for _, rpc := range outRPCs {
if rpc.Size() > bs.p.maxMessageSize {
// This should only happen if a single message/control is above the maxMessageSize.
@ -1273,19 +1326,19 @@ func (bs *BlossomSubRouter) doSendRPC(rpc *RPC, p peer.ID, mch chan *RPC) {
// If an RPC is too large and can't be split further (e.g. Message data is
// bigger than the RPC limit), then it will be returned as an oversized RPC.
// The caller should filter out oversized RPCs.
func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
func appendOrMergeRPC(slice []*RPC, limit int, elems ...*RPC) []*RPC {
if len(elems) == 0 {
return slice
}
if len(slice) == 0 && len(elems) == 1 && elems[0].Size() < limit {
// Fast path: no merging needed and only one element
return append(slice, &elems[0])
return append(slice, elems[0])
}
out := slice
if len(out) == 0 {
out = append(out, &RPC{RPC: pb.RPC{}})
out = append(out, &RPC{RPC: &pb.RPC{}})
out[0].from = elems[0].from
}
@ -1299,7 +1352,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
for _, msg := range elem.GetPublish() {
if lastRPC.Publish = append(lastRPC.Publish, msg); lastRPC.Size() > limit {
lastRPC.Publish = lastRPC.Publish[:len(lastRPC.Publish)-1]
lastRPC = &RPC{RPC: pb.RPC{}, from: elem.from}
lastRPC = &RPC{RPC: &pb.RPC{}, from: elem.from}
lastRPC.Publish = append(lastRPC.Publish, msg)
out = append(out, lastRPC)
}
@ -1309,7 +1362,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
for _, sub := range elem.GetSubscriptions() {
if lastRPC.Subscriptions = append(lastRPC.Subscriptions, sub); lastRPC.Size() > limit {
lastRPC.Subscriptions = lastRPC.Subscriptions[:len(lastRPC.Subscriptions)-1]
lastRPC = &RPC{RPC: pb.RPC{}, from: elem.from}
lastRPC = &RPC{RPC: &pb.RPC{}, from: elem.from}
lastRPC.Subscriptions = append(lastRPC.Subscriptions, sub)
out = append(out, lastRPC)
}
@ -1321,7 +1374,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
lastRPC.Control = &pb.ControlMessage{}
if lastRPC.Size() > limit {
lastRPC.Control = nil
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
out = append(out, lastRPC)
}
}
@ -1329,7 +1382,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
for _, graft := range ctl.GetGraft() {
if lastRPC.Control.Graft = append(lastRPC.Control.Graft, graft); lastRPC.Size() > limit {
lastRPC.Control.Graft = lastRPC.Control.Graft[:len(lastRPC.Control.Graft)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
lastRPC.Control.Graft = append(lastRPC.Control.Graft, graft)
out = append(out, lastRPC)
}
@ -1338,7 +1391,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
for _, prune := range ctl.GetPrune() {
if lastRPC.Control.Prune = append(lastRPC.Control.Prune, prune); lastRPC.Size() > limit {
lastRPC.Control.Prune = lastRPC.Control.Prune[:len(lastRPC.Control.Prune)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{}}, from: elem.from}
lastRPC.Control.Prune = append(lastRPC.Control.Prune, prune)
out = append(out, lastRPC)
}
@ -1352,7 +1405,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
newIWant := &pb.ControlIWant{}
if lastRPC.Control.Iwant = append(lastRPC.Control.Iwant, newIWant); lastRPC.Size() > limit {
lastRPC.Control.Iwant = lastRPC.Control.Iwant[:len(lastRPC.Control.Iwant)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{
Iwant: []*pb.ControlIWant{newIWant},
}}, from: elem.from}
out = append(out, lastRPC)
@ -1361,8 +1414,8 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
for _, msgID := range iwant.GetMessageIDs() {
if lastRPC.Control.Iwant[0].MessageIDs = append(lastRPC.Control.Iwant[0].MessageIDs, msgID); lastRPC.Size() > limit {
lastRPC.Control.Iwant[0].MessageIDs = lastRPC.Control.Iwant[0].MessageIDs[:len(lastRPC.Control.Iwant[0].MessageIDs)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{
Iwant: []*pb.ControlIWant{{MessageIDs: []string{msgID}}},
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{
Iwant: []*pb.ControlIWant{{MessageIDs: [][]byte{msgID}}},
}}, from: elem.from}
out = append(out, lastRPC)
}
@ -1376,7 +1429,7 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
newIhave := &pb.ControlIHave{Bitmask: ihave.Bitmask}
if lastRPC.Control.Ihave = append(lastRPC.Control.Ihave, newIhave); lastRPC.Size() > limit {
lastRPC.Control.Ihave = lastRPC.Control.Ihave[:len(lastRPC.Control.Ihave)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{
Ihave: []*pb.ControlIHave{newIhave},
}}, from: elem.from}
out = append(out, lastRPC)
@ -1386,8 +1439,8 @@ func appendOrMergeRPC(slice []*RPC, limit int, elems ...RPC) []*RPC {
lastIHave := lastRPC.Control.Ihave[len(lastRPC.Control.Ihave)-1]
if lastIHave.MessageIDs = append(lastIHave.MessageIDs, msgID); lastRPC.Size() > limit {
lastIHave.MessageIDs = lastIHave.MessageIDs[:len(lastIHave.MessageIDs)-1]
lastRPC = &RPC{RPC: pb.RPC{Control: &pb.ControlMessage{
Ihave: []*pb.ControlIHave{{Bitmask: ihave.Bitmask, MessageIDs: []string{msgID}}},
lastRPC = &RPC{RPC: &pb.RPC{Control: &pb.ControlMessage{
Ihave: []*pb.ControlIHave{{Bitmask: ihave.Bitmask, MessageIDs: [][]byte{msgID}}},
}}, from: elem.from}
out = append(out, lastRPC)
}
@ -1408,7 +1461,6 @@ func (bs *BlossomSubRouter) heartbeatTimer() {
}
ticker := time.NewTicker(bs.params.HeartbeatInterval)
defer ticker.Stop()
for {
select {
@ -1416,9 +1468,11 @@ func (bs *BlossomSubRouter) heartbeatTimer() {
select {
case bs.p.eval <- bs.heartbeat:
case <-bs.p.ctx.Done():
ticker.Stop()
return
}
case <-bs.p.ctx.Done():
ticker.Stop()
return
}
}
@ -1426,14 +1480,6 @@ func (bs *BlossomSubRouter) heartbeatTimer() {
func (bs *BlossomSubRouter) heartbeat() {
start := time.Now()
defer func() {
if bs.params.SlowHeartbeatWarning > 0 {
slowWarning := time.Duration(bs.params.SlowHeartbeatWarning * float64(bs.params.HeartbeatInterval))
if dt := time.Since(start); dt > slowWarning {
log.Warnw("slow heartbeat", "took", dt)
}
}
}()
bs.heartbeatTicks++
@ -1465,6 +1511,7 @@ func (bs *BlossomSubRouter) heartbeat() {
}
// maintain the mesh for bitmasks we have joined
bs.meshMx.Lock()
for bitmask, peers := range bs.mesh {
bitmask := []byte(bitmask)
prunePeer := func(p peer.ID) {
@ -1521,7 +1568,9 @@ func (bs *BlossomSubRouter) heartbeat() {
// We keep the first D_score peers by score and the remaining up to D randomly
// under the constraint that we keep D_out peers in the mesh (if we have that many)
shufflePeers(plst[bs.params.Dscore:])
if len(plst) > bs.params.Dscore {
shufflePeers(plst[bs.params.Dscore:])
}
// count the outbound peers we are keeping
outbound := 0
@ -1637,7 +1686,15 @@ func (bs *BlossomSubRouter) heartbeat() {
// 2nd arg are mesh peers excluded from gossip. We already push
// messages to them, so its redundant to gossip IHAVEs.
bs.emitGossip(bitmask, peers)
if bs.params.SlowHeartbeatWarning > 0 {
slowWarning := time.Duration(bs.params.SlowHeartbeatWarning * float64(bs.params.HeartbeatInterval))
if dt := time.Since(start); dt > slowWarning {
log.Warnw("slow heartbeat", "took", dt)
}
}
}
bs.meshMx.Unlock()
// expire fanout for bitmasks we haven't published to in a while
now := time.Now().UnixNano()
@ -1799,7 +1856,7 @@ func (bs *BlossomSubRouter) emitGossip(bitmask []byte, exclude map[peer.ID]struc
}
// shuffle to emit in random order
shuffleStrings(mids)
shuffleBytes(mids)
// if we are emitting more than BlossomSubMaxIHaveLength mids, truncate the list
if len(mids) > bs.params.MaxIHaveLength {
@ -1821,10 +1878,6 @@ func (bs *BlossomSubRouter) emitGossip(bitmask []byte, exclude map[peer.ID]struc
}
target := bs.params.Dlazy
factor := int(bs.params.GossipFactor * float64(len(peers)))
if factor > target {
target = factor
}
if target > len(peers) {
target = len(peers)
@ -1840,8 +1893,8 @@ func (bs *BlossomSubRouter) emitGossip(bitmask []byte, exclude map[peer.ID]struc
// we do this per peer so that we emit a different set for each peer.
// we have enough redundancy in the system that this will significantly increase the message
// coverage when we do truncate.
peerMids = make([]string, bs.params.MaxIHaveLength)
shuffleStrings(mids)
peerMids = make([][]byte, bs.params.MaxIHaveLength)
shuffleBytes(mids)
copy(peerMids, mids)
}
bs.enqueueGossip(p, &pb.ControlIHave{Bitmask: bitmask, MessageIDs: peerMids})
@ -1896,7 +1949,9 @@ func (bs *BlossomSubRouter) piggybackControl(p peer.ID, out *RPC, ctl *pb.Contro
for _, graft := range ctl.GetGraft() {
bitmask := graft.GetBitmask()
bs.meshMx.RLock()
peers, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
continue
}
@ -1908,7 +1963,9 @@ func (bs *BlossomSubRouter) piggybackControl(p peer.ID, out *RPC, ctl *pb.Contro
for _, prune := range ctl.GetPrune() {
bitmask := prune.GetBitmask()
bs.meshMx.RLock()
peers, ok := bs.mesh[string(bitmask)]
bs.meshMx.RUnlock()
if !ok {
toprune = append(toprune, prune)
continue
@ -1980,25 +2037,47 @@ func (bs *BlossomSubRouter) makePrune(p peer.ID, bitmask []byte, doPX bool, isUn
}
func (bs *BlossomSubRouter) getPeers(bitmask []byte, count int, filter func(peer.ID) bool) []peer.ID {
tmap, ok := bs.p.bitmasks[string(bitmask)]
if !ok {
return nil
}
bitmaskSlices := SliceBitmask(bitmask)
peers := make([]peer.ID, 0, len(tmap))
for p := range tmap {
if bs.feature(BlossomSubFeatureMesh, bs.peers[p]) && filter(p) && bs.p.peerFilter(p, bitmask) {
peers = append(peers, p)
set := []peer.ID{}
for _, slice := range bitmaskSlices {
tmap, ok := bs.p.bitmasks[string(slice)]
if !ok {
return nil
}
peers := make([]peer.ID, 0, len(tmap))
for p := range tmap {
if bs.feature(BlossomSubFeatureMesh, bs.peers[p]) && filter(p) && bs.p.peerFilter(p, slice) {
peers = append(peers, p)
}
}
if len(set) == 0 {
set = peers
} else {
newSet := []peer.ID{}
for _, p := range peers {
if slices.Contains(set, p) {
newSet = append(newSet, p)
}
}
if len(newSet) == 0 {
return nil
}
set = newSet
}
}
shufflePeers(peers)
shufflePeers(set)
if count > 0 && len(peers) > count {
peers = peers[:count]
if count > 0 && len(set) > count {
set = set[:count]
}
return peers
return set
}
// WithDefaultTagTracer returns the tag tracer of the BlossomSubRouter as a PubSub option.
@ -2039,7 +2118,7 @@ func shufflePeerInfo(peers []*pb.PeerInfo) {
}
}
func shuffleStrings(lst []string) {
func shuffleBytes(lst [][]byte) {
for i := range lst {
j := rand.Intn(i + 1)
lst[i], lst[j] = lst[j], lst[i]

View File

@ -6,11 +6,11 @@ import (
"time"
"github.com/benbjohnson/clock"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
)
@ -70,9 +70,14 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
t.Fatal(err)
}
netw := swarmt.GenSwarm(t)
defer netw.Close()
h := bhost.NewBlankHost(netw, bhost.WithConnectionManager(connmgrs[i]))
h, err := libp2p.New(
libp2p.ResourceManager(&network.NullResourceManager{}),
libp2p.ConnectionManager(connmgrs[i]),
)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { h.Close() })
honestHosts[i] = h
honestPeers[h.ID()] = struct{}{}
}
@ -83,23 +88,23 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
WithFloodPublish(true))
// sybil squatters to be connected later
sybilHosts := getNetHosts(t, ctx, nSquatter)
sybilHosts := getDefaultHosts(t, nSquatter)
for _, h := range sybilHosts {
squatter := &sybilSquatter{h: h}
h.SetStreamHandler(BlossomSubID_v12, squatter.handleStream)
h.SetStreamHandler(BlossomSubID_v2, squatter.handleStream)
}
// connect the honest hosts
connectAll(t, honestHosts)
for _, h := range honestHosts {
if len(h.Network().Conns()) != nHonest-1 {
if len(h.Network().Conns()) < nHonest-1 {
t.Errorf("expected to have conns to all honest peers, have %d", len(h.Network().Conns()))
}
}
// subscribe everyone to the bitmask
bitmask := []byte{0xff, 0x00, 0x00, 0x00}
bitmask := []byte{0x00, 0x80, 0x00, 0x00}
for _, ps := range psubs {
_, err := ps.Subscribe(bitmask)
if err != nil {
@ -113,8 +118,13 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
// have all the hosts publish enough messages to ensure that they get some delivery credit
nMessages := BlossomSubConnTagMessageDeliveryCap * 2
for _, ps := range psubs {
b, err := ps.Join(bitmask)
if err != nil {
t.Fatal(err)
}
for i := 0; i < nMessages; i++ {
ps.Publish(bitmask, []byte("hello"))
b[0].Publish(ctx, b[0].bitmask, []byte("hello"))
}
}
@ -122,7 +132,7 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
decayClock.Add(time.Second)
// verify that they've given each other delivery connection tags
tag := "pubsub-deliveries:test"
tag := "pubsub-deliveries:" + string([]byte{0x00, 0x80, 0x00, 0x00})
for _, h := range honestHosts {
for _, h2 := range honestHosts {
if h.ID() == h2.ID() {
@ -136,12 +146,12 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
}
// now connect the sybils to put pressure on the real hosts' connection managers
allHosts := append(honestHosts, sybilHosts...)
allHosts := honestHosts
connectAll(t, allHosts)
// verify that we have a bunch of connections
for _, h := range honestHosts {
if len(h.Network().Conns()) != nHonest+nSquatter-1 {
if len(h.Network().Conns()) < nHonest-1 {
t.Errorf("expected to have conns to all peers, have %d", len(h.Network().Conns()))
}
}
@ -165,7 +175,7 @@ func TestBlossomSubConnTagMessageDeliveries(t *testing.T) {
if nDishonestConns > connLimit-nHonest {
t.Errorf("expected most dishonest conns to be pruned, have %d", nDishonestConns)
}
if nHonestConns != nHonest-1 {
if nHonestConns < nHonest-1 {
t.Errorf("expected all honest conns to be preserved, have %d", nHonestConns)
}
}

View File

@ -14,22 +14,22 @@ type BlossomSubFeatureTest = func(BlossomSubFeature, protocol.ID) bool
type BlossomSubFeature int
const (
// Protocol supports basic BlossomSub Mesh -- BlossomSub-v1.2 compatible
// Protocol supports basic BlossomSub Mesh -- BlossomSub-v2 compatible
BlossomSubFeatureMesh = iota
// Protocol supports Peer eXchange on prune -- BlossomSub-v1.2 compatible
// Protocol supports Peer eXchange on prune -- BlossomSub-v2 compatible
BlossomSubFeaturePX
)
// BlossomSubDefaultProtocols is the default BlossomSub router protocol list
var BlossomSubDefaultProtocols = []protocol.ID{BlossomSubID_v12, FloodSubID}
var BlossomSubDefaultProtocols = []protocol.ID{BlossomSubID_v2}
// BlossomSubDefaultFeatures is the feature test function for the default BlossomSub protocols
func BlossomSubDefaultFeatures(feat BlossomSubFeature, proto protocol.ID) bool {
switch feat {
case BlossomSubFeatureMesh:
return proto == BlossomSubID_v12
return proto == BlossomSubID_v2
case BlossomSubFeaturePX:
return proto == BlossomSubID_v12
return proto == BlossomSubID_v2
default:
return false
}

View File

@ -12,47 +12,46 @@ import (
)
func TestDefaultBlossomSubFeatures(t *testing.T) {
if BlossomSubDefaultFeatures(BlossomSubFeatureMesh, FloodSubID) {
t.Fatal("floodsub should not support Mesh")
}
if !BlossomSubDefaultFeatures(BlossomSubFeatureMesh, BlossomSubID_v12) {
t.Fatal("BlossomSub-v1.2 should support Mesh")
if !BlossomSubDefaultFeatures(BlossomSubFeatureMesh, BlossomSubID_v2) {
t.Fatal("BlossomSub-v2.0 should support Mesh")
}
if BlossomSubDefaultFeatures(BlossomSubFeaturePX, FloodSubID) {
t.Fatal("floodsub should not support PX")
}
if !BlossomSubDefaultFeatures(BlossomSubFeatureMesh, BlossomSubID_v12) {
t.Fatal("BlossomSub-v1.2 should support PX")
if !BlossomSubDefaultFeatures(BlossomSubFeaturePX, BlossomSubID_v2) {
t.Fatal("BlossomSub-v2.0 should support PX")
}
}
func TestBlossomSubCustomProtocols(t *testing.T) {
customsub := protocol.ID("customsub/1.0.0")
protos := []protocol.ID{customsub, FloodSubID}
protos := []protocol.ID{customsub}
features := func(feat BlossomSubFeature, proto protocol.ID) bool {
return proto == customsub
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 3)
hosts := getDefaultHosts(t, 3)
bsubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubProtocols(protos, features))
fsub := getPubsub(ctx, hosts[2])
psubs := append(bsubs, fsub)
connectAll(t, hosts)
bitmask := []byte{0xff, 0x00, 0x00, 0x00}
bitmask := []byte{0x00, 0x80, 0x00, 0x00}
var bitmasks []*Bitmask
var subs []*Subscription
for _, ps := range psubs {
for _, ps := range bsubs {
b, err := ps.Join(bitmask)
if err != nil {
t.Fatal(err)
}
subch, err := ps.Subscribe(bitmask)
if err != nil {
t.Fatal(err)
}
subs = append(subs, subch)
subs = append(subs, subch...)
bitmasks = append(bitmasks, b...)
}
// wait for heartbeats to build mesh
@ -92,9 +91,8 @@ func TestBlossomSubCustomProtocols(t *testing.T) {
for i := 0; i < 10; i++ {
msg := []byte(fmt.Sprintf("%d it's not quite a floooooood %d", i, i))
owner := rand.Intn(len(psubs))
psubs[owner].Publish(bitmask, msg)
owner := rand.Intn(len(bsubs))
bitmasks[owner].Publish(ctx, bitmasks[owner].bitmask, msg)
for _, sub := range subs {
got, err := sub.Next(ctx)

View File

@ -17,11 +17,11 @@ func TestBlossomSubMatchingFn(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
h := getNetHosts(t, ctx, 4)
h := getDefaultHosts(t, 4)
psubs := []*PubSub{
getBlossomSub(ctx, h[0], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{customsubA100, BlossomSubID_v12}, BlossomSubDefaultFeatures)),
getBlossomSub(ctx, h[0], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{customsubA100, BlossomSubID_v2}, BlossomSubDefaultFeatures)),
getBlossomSub(ctx, h[1], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{customsubA101Beta}, BlossomSubDefaultFeatures)),
getBlossomSub(ctx, h[2], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{BlossomSubID_v12}, BlossomSubDefaultFeatures)),
getBlossomSub(ctx, h[2], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{BlossomSubID_v2}, BlossomSubDefaultFeatures)),
getBlossomSub(ctx, h[3], WithProtocolMatchFn(protocolNameMatch), WithBlossomSubProtocols([]protocol.ID{customsubB100}, BlossomSubDefaultFeatures)),
}
@ -39,23 +39,30 @@ func TestBlossomSubMatchingFn(t *testing.T) {
// build the mesh
var subs []*Subscription
var bitmasks []*Bitmask
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
b, err := ps.Join([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs = append(subs, sub)
bitmasks = append(bitmasks, b...)
sub, err := ps.Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs = append(subs, sub...)
}
time.Sleep(time.Second)
// publish a message
msg := []byte("message")
psubs[0].Publish([]byte{0xff, 0x00, 0x00, 0x00}, msg)
bitmasks[0].Publish(ctx, bitmasks[0].bitmask, msg)
assertReceive(t, subs[0], msg)
assertReceive(t, subs[1], msg) // Should match via semver over CustomSub name, ignoring the version
assertReceive(t, subs[2], msg) // Should match via BlossomSubID_v11
assertReceive(t, subs[2], msg) // Should match via BlossomSubID_v2
// No message should be received because customsubA and customsubB have different names
ctxTimeout, timeoutCancel := context.WithTimeout(context.Background(), 1*time.Second)

View File

@ -2,6 +2,7 @@ package blossomsub
import (
"context"
"fmt"
"math/rand"
"strconv"
"sync"
@ -11,11 +12,10 @@ import (
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-msgio"
"google.golang.org/protobuf/proto"
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"github.com/libp2p/go-msgio/protoio"
)
// Test that when BlossomSub receives too many IWANT messages from a peer
@ -25,7 +25,7 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
defer cancel()
// Create legitimate and attacker hosts
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
legit := hosts[0]
attacker := hosts[1]
@ -36,25 +36,11 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
}
// Subscribe to mybitmask on the legit host
mybitmask := []byte{0xff, 0x00, 0x00}
_, err = ps.Subscribe(mybitmask)
if err != nil {
t.Fatal(err)
}
// Used to publish a message with random data
publishMsg := func() {
data := make([]byte, 16)
rand.Read(data)
if err = ps.Publish(mybitmask, data); err != nil {
t.Fatal(err)
}
}
mybitmask := []byte{0x20, 0x00, 0x00}
// Wait a bit after the last message before checking we got the
// right number of messages
msgWaitMax := time.Second
msgWaitMax := 10 * time.Second
msgCount := 0
msgTimer := time.NewTimer(msgWaitMax)
@ -65,7 +51,22 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
// <original message> + BlossomSubGossipRetransmission
exp := 1 + BlossomSubGossipRetransmission
if msgCount != exp {
t.Fatalf("Expected %d messages, got %d", exp, msgCount)
panic(fmt.Sprintf("Expected %d messages, got %d", exp, msgCount))
}
}
bitmasks, err := ps.Join(mybitmask)
if err != nil {
t.Fatal(err)
}
// Used to publish a message with random data
publishMsg := func() {
data := make([]byte, 16)
rand.Read(data)
if err := bitmasks[0].Publish(ctx, bitmasks[0].bitmask, data); err != nil {
t.Fatal(err)
}
}
@ -84,6 +85,7 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
sub := sub
if sub.GetSubscribe() {
// Reply by subcribing to the bitmask and grafting to the peer
writeMsg(&pb.RPC{
@ -94,7 +96,7 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
go func() {
// Wait for a short interval to make sure the legit host
// received and processed the subscribe + graft
time.Sleep(100 * time.Millisecond)
time.Sleep(1 * time.Second)
// Publish a message from the legit host
publishMsg()
@ -118,15 +120,22 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
// Send an IWANT with the message ID, causing the legit host
// to send another message (until it cuts off the attacker for
// being spammy)
iwantlst := []string{DefaultMsgIdFn(msg)}
iwantlst := [][]byte{DefaultMsgIdFn(msg)}
iwant := []*pb.ControlIWant{{MessageIDs: iwantlst}}
orpc := rpcWithControl(nil, nil, iwant, nil, nil)
writeMsg(&orpc.RPC)
writeMsg(orpc.RPC)
}
})
connect(t, hosts[0], hosts[1])
time.Sleep(100 * time.Millisecond)
_, err = ps.Subscribe(mybitmask)
if err != nil {
t.Fatal(err)
}
<-ctx.Done()
}
@ -142,7 +151,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
defer cancel()
// Create legitimate and attacker hosts
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
legit := hosts[0]
attacker := hosts[1]
@ -166,7 +175,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
}
// Subscribe to mybitmask on the legit host
mybitmask := []byte{0xff, 0x00, 0x00}
mybitmask := []byte{0x20, 0x00, 0x00}
_, err = ps.Subscribe(mybitmask)
if err != nil {
t.Fatal(err)
@ -188,6 +197,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
sub := sub
if sub.GetSubscribe() {
// Reply by subcribing to the bitmask and grafting to the peer
writeMsg(&pb.RPC{
@ -204,10 +214,10 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
// Send a bunch of IHAVEs
for i := 0; i < 3*BlossomSubMaxIHaveLength; i++ {
ihavelst := []string{"someid" + strconv.Itoa(i)}
ihavelst := [][]byte{[]byte("someid" + strconv.Itoa(i))}
ihave := []*pb.ControlIHave{{Bitmask: sub.Bitmask, MessageIDs: ihavelst}}
orpc := rpcWithControl(nil, ihave, nil, nil, nil)
writeMsg(&orpc.RPC)
writeMsg(orpc.RPC)
}
select {
@ -234,10 +244,10 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
// Send a bunch of IHAVEs
for i := 0; i < 3*BlossomSubMaxIHaveLength; i++ {
ihavelst := []string{"someid" + strconv.Itoa(i+100)}
ihavelst := [][]byte{[]byte("someid" + strconv.Itoa(i+100))}
ihave := []*pb.ControlIHave{{Bitmask: sub.Bitmask, MessageIDs: ihavelst}}
orpc := rpcWithControl(nil, ihave, nil, nil, nil)
writeMsg(&orpc.RPC)
writeMsg(orpc.RPC)
}
select {
@ -292,7 +302,7 @@ func TestBlossomSubAttackGRAFTNonExistentBitmask(t *testing.T) {
defer cancel()
// Create legitimate and attacker hosts
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
legit := hosts[0]
attacker := hosts[1]
@ -303,7 +313,7 @@ func TestBlossomSubAttackGRAFTNonExistentBitmask(t *testing.T) {
}
// Subscribe to mybitmask on the legit host
mybitmask := []byte{0xff, 0x00, 0x00}
mybitmask := []byte{0x20, 0x00, 0x00}
_, err = ps.Subscribe(mybitmask)
if err != nil {
t.Fatal(err)
@ -322,6 +332,7 @@ func TestBlossomSubAttackGRAFTNonExistentBitmask(t *testing.T) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
sub := sub
if sub.GetSubscribe() {
// Reply by subcribing to the bitmask and grafting to the peer
writeMsg(&pb.RPC{
@ -330,7 +341,7 @@ func TestBlossomSubAttackGRAFTNonExistentBitmask(t *testing.T) {
})
// Graft to the peer on a non-existent bitmask
nonExistentBitmask := []byte{0xff, 0x00, 0x00, 0xff, 0xff, 0xff}
nonExistentBitmask := []byte{0x20, 0x00, 0x00, 0x02, 0xff, 0xff}
writeMsg(&pb.RPC{
Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: nonExistentBitmask}}},
})
@ -376,7 +387,7 @@ func TestBlossomSubAttackGRAFTDuringBackoff(t *testing.T) {
defer cancel()
// Create legitimate and attacker hosts
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
legit := hosts[0]
attacker := hosts[1]
@ -400,7 +411,7 @@ func TestBlossomSubAttackGRAFTDuringBackoff(t *testing.T) {
}
// Subscribe to mybitmask on the legit host
mybitmask := []byte{0xff, 0x00, 0x00}
mybitmask := []byte{0x20, 0x00, 0x00}
_, err = ps.Subscribe(mybitmask)
if err != nil {
t.Fatal(err)
@ -422,6 +433,7 @@ func TestBlossomSubAttackGRAFTDuringBackoff(t *testing.T) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
sub := sub
if sub.GetSubscribe() {
// Reply by subcribing to the bitmask and grafting to the peer
graft := []*pb.ControlGraft{{Bitmask: sub.Bitmask}}
@ -617,11 +629,11 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) {
defer cancel()
// Create legitimate and attacker hosts
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
legit := hosts[0]
attacker := hosts[1]
mybitmask := []byte{0xff, 0x00, 0x00}
mybitmask := []byte{0x20, 0x00, 0x00}
// Create parameters with reasonable default values
params := &PeerScoreParams{
@ -664,6 +676,7 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) {
ps, err := NewBlossomSub(ctx, legit,
WithEventTracer(tracer),
WithPeerScore(params, thresholds),
WithMessageSignaturePolicy(StrictSign),
)
if err != nil {
t.Fatal(err)
@ -766,7 +779,7 @@ type MockBSOnRead func(writeMsg func(*pb.RPC), irpc *pb.RPC)
func newMockBS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg MockBSOnRead) {
// Listen on the BlossomSub protocol
const BlossomSubID = protocol.ID("/meshsub/1.0.0")
const BlossomSubID = BlossomSubID_v2
const maxMessageSize = 1024 * 1024
attacker.SetStreamHandler(BlossomSubID, func(stream network.Stream) {
// When an incoming stream is opened, set up an outgoing stream
@ -776,13 +789,17 @@ func newMockBS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg
t.Fatal(err)
}
r := protoio.NewDelimitedReader(stream, maxMessageSize)
w := protoio.NewDelimitedWriter(ostream)
r := msgio.NewVarintReaderSize(stream, maxMessageSize)
w := msgio.NewVarintWriter(ostream)
var irpc pb.RPC
writeMsg := func(rpc *pb.RPC) {
if err = w.WriteMsg(rpc); err != nil {
out, err := proto.Marshal(rpc)
if err != nil {
t.Fatalf("error writing RPC: %s", err)
}
if err = w.WriteMsg(out); err != nil {
t.Fatalf("error writing RPC: %s", err)
}
}
@ -795,8 +812,21 @@ func newMockBS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg
}
irpc.Reset()
v, err := r.ReadMsg()
err := r.ReadMsg(&irpc)
// Bail out when the test finishes
if ctx.Err() != nil {
return
}
if err != nil {
t.Fatal(err)
}
err = proto.Unmarshal(v, &irpc)
if err != nil {
t.Fatal(err)
}
// Bail out when the test finishes
if ctx.Err() != nil {

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ import (
pool "github.com/libp2p/go-buffer-pool"
"github.com/multiformats/go-varint"
"google.golang.org/protobuf/proto"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
@ -18,7 +19,9 @@ import (
// get the initial RPC containing all of our subscriptions to send to new peers
func (p *PubSub) getHelloPacket() *RPC {
var rpc RPC
var rpc = &RPC{
RPC: new(pb.RPC),
}
subscriptions := make(map[string]bool)
@ -37,7 +40,7 @@ func (p *PubSub) getHelloPacket() *RPC {
}
rpc.Subscriptions = append(rpc.Subscriptions, as)
}
return &rpc
return rpc
}
func (p *PubSub) handleNewStream(s network.Stream) {
@ -52,14 +55,6 @@ func (p *PubSub) handleNewStream(s network.Stream) {
p.inboundStreams[peer] = s
p.inboundStreamsMx.Unlock()
defer func() {
p.inboundStreamsMx.Lock()
if p.inboundStreams[peer] == s {
delete(p.inboundStreams, peer)
}
p.inboundStreamsMx.Unlock()
}()
r := msgio.NewVarintReaderSize(s, p.maxMessageSize)
for {
msgbytes, err := r.ReadMsg()
@ -74,18 +69,30 @@ func (p *PubSub) handleNewStream(s network.Stream) {
s.Close()
}
p.inboundStreamsMx.Lock()
if p.inboundStreams[peer] == s {
delete(p.inboundStreams, peer)
}
p.inboundStreamsMx.Unlock()
return
}
if len(msgbytes) == 0 {
continue
}
rpc := new(RPC)
rpc := &RPC{
RPC: new(pb.RPC),
}
err = rpc.Unmarshal(msgbytes)
r.ReleaseMsg(msgbytes)
if err != nil {
s.Reset()
log.Warnf("bogus rpc from %s: %s", s.Conn().RemotePeer(), err)
p.inboundStreamsMx.Lock()
if p.inboundStreams[peer] == s {
delete(p.inboundStreams, peer)
}
p.inboundStreamsMx.Unlock()
return
}
@ -95,6 +102,11 @@ func (p *PubSub) handleNewStream(s network.Stream) {
case <-p.ctx.Done():
// Close is useless because the other side isn't reading.
s.Reset()
p.inboundStreamsMx.Lock()
if p.inboundStreams[peer] == s {
delete(p.inboundStreams, peer)
}
p.inboundStreamsMx.Unlock()
return
}
}
@ -156,37 +168,38 @@ func (p *PubSub) handlePeerDead(s network.Stream) {
}
func (p *PubSub) handleSendingMessages(ctx context.Context, s network.Stream, outgoing <-chan *RPC) {
writeRpc := func(rpc *RPC) error {
size := uint64(rpc.Size())
buf := pool.Get(varint.UvarintSize(size) + int(size))
defer pool.Put(buf)
n := binary.PutUvarint(buf, size)
_, err := rpc.MarshalTo(buf[n:])
if err != nil {
return err
}
_, err = s.Write(buf)
return err
}
defer s.Close()
for {
select {
case rpc, ok := <-outgoing:
if !ok {
s.Close()
return
}
err := writeRpc(rpc)
size := uint64(rpc.Size())
buf := pool.Get(varint.UvarintSize(size) + int(size))
n := binary.PutUvarint(buf, size)
_, err := rpc.MarshalTo(buf[n:])
if err != nil {
s.Reset()
log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err)
s.Close()
return
}
_, err = s.Write(buf)
if err != nil {
s.Reset()
log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err)
s.Close()
return
}
pool.Put(buf)
case <-ctx.Done():
s.Close()
return
}
}
@ -194,14 +207,14 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s network.Stream, ou
func rpcWithSubs(subs ...*pb.RPC_SubOpts) *RPC {
return &RPC{
RPC: pb.RPC{
RPC: &pb.RPC{
Subscriptions: subs,
},
}
}
func rpcWithMessages(msgs ...*pb.Message) *RPC {
return &RPC{RPC: pb.RPC{Publish: msgs}}
return &RPC{RPC: &pb.RPC{Publish: msgs}}
}
func rpcWithControl(msgs []*pb.Message,
@ -210,7 +223,7 @@ func rpcWithControl(msgs []*pb.Message,
graft []*pb.ControlGraft,
prune []*pb.ControlPrune) *RPC {
return &RPC{
RPC: pb.RPC{
RPC: &pb.RPC{
Publish: msgs,
Control: &pb.ControlMessage{
Ihave: ihave,
@ -224,10 +237,7 @@ func rpcWithControl(msgs []*pb.Message,
func copyRPC(rpc *RPC) *RPC {
res := new(RPC)
*res = *rpc
if rpc.Control != nil {
res.Control = new(pb.ControlMessage)
*res.Control = *rpc.Control
}
copiedRPC := (proto.Clone(rpc.RPC)).(*pb.RPC)
res.RPC = copiedRPC
return res
}

View File

@ -120,7 +120,6 @@ func (d *discover) pollTimer() {
}
ticker := time.NewTicker(DiscoveryPollInterval)
defer ticker.Stop()
for {
select {
@ -128,9 +127,11 @@ func (d *discover) pollTimer() {
select {
case d.p.eval <- d.requestDiscovery:
case <-d.p.ctx.Done():
ticker.Stop()
return
}
case <-d.p.ctx.Done():
ticker.Stop()
return
}
}
@ -197,7 +198,6 @@ func (d *discover) Advertise(bitmask []byte) {
}
t := time.NewTimer(next)
defer t.Stop()
for advertisingCtx.Err() == nil {
select {
@ -211,9 +211,11 @@ func (d *discover) Advertise(bitmask []byte) {
}
t.Reset(next)
case <-advertisingCtx.Done():
t.Stop()
return
}
}
t.Stop()
}()
}
@ -248,7 +250,6 @@ func (d *discover) Bootstrap(ctx context.Context, bitmask []byte, ready RouterRe
if !t.Stop() {
<-t.C
}
defer t.Stop()
for {
// Check if ready for publishing
@ -259,11 +260,14 @@ func (d *discover) Bootstrap(ctx context.Context, bitmask []byte, ready RouterRe
bootstrapped <- done
}:
if <-bootstrapped {
t.Stop()
return true
}
case <-d.p.ctx.Done():
t.Stop()
return false
case <-ctx.Done():
t.Stop()
return false
}
@ -272,16 +276,20 @@ func (d *discover) Bootstrap(ctx context.Context, bitmask []byte, ready RouterRe
select {
case d.discoverQ <- disc:
case <-d.p.ctx.Done():
t.Stop()
return false
case <-ctx.Done():
t.Stop()
return false
}
select {
case <-disc.done:
case <-d.p.ctx.Done():
t.Stop()
return false
case <-ctx.Done():
t.Stop()
return false
}
@ -289,8 +297,10 @@ func (d *discover) Bootstrap(ctx context.Context, bitmask []byte, ready RouterRe
select {
case <-t.C:
case <-d.p.ctx.Done():
t.Stop()
return false
case <-ctx.Done():
t.Stop()
return false
}
}
@ -298,15 +308,16 @@ func (d *discover) Bootstrap(ctx context.Context, bitmask []byte, ready RouterRe
func (d *discover) handleDiscovery(ctx context.Context, bitmask []byte, opts []discovery.Option) {
discoverCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
peerCh, err := d.discovery.FindPeers(discoverCtx, string(bitmask), opts...)
if err != nil {
log.Debugf("error finding peers for bitmask %s: %v", bitmask, err)
cancel()
return
}
d.connector.Connect(ctx, peerCh)
cancel()
}
type discoverReq struct {
@ -321,11 +332,11 @@ type pubSubDiscovery struct {
}
func (d *pubSubDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {
return d.Discovery.Advertise(ctx, "floodsub:"+ns, append(opts, d.opts...)...)
return d.Discovery.Advertise(ctx, "blossomsub:"+ns, append(opts, d.opts...)...)
}
func (d *pubSubDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {
return d.Discovery.FindPeers(ctx, "floodsub:"+ns, append(opts, d.opts...)...)
return d.Discovery.FindPeers(ctx, "blossomsub:"+ns, append(opts, d.opts...)...)
}
// WithDiscoveryOpts passes libp2p Discovery options into the PubSub discovery subsystem

View File

@ -123,101 +123,6 @@ func (d *dummyDiscovery) FindPeers(ctx context.Context, ns string, opts ...disco
return retCh, nil
}
func TestSimpleDiscovery(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Setup Discovery server and pubsub clients
const numHosts = 20
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
server := newDiscoveryServer()
discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(1 * time.Minute)}
hosts := getNetHosts(t, ctx, numHosts)
psubs := make([]*PubSub, numHosts)
bitmaskHandlers := make([]*Bitmask, numHosts)
for i, h := range hosts {
disc := &mockDiscoveryClient{h, server}
ps := getPubsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...)))
psubs[i] = ps
bitmaskHandlers[i], _ = ps.Join(bitmask)
}
// Subscribe with all but one pubsub instance
msgs := make([]*Subscription, numHosts)
for i, th := range bitmaskHandlers[1:] {
subch, err := th.Subscribe()
if err != nil {
t.Fatal(err)
}
msgs[i+1] = subch
}
// Wait for the advertisements to go through then check that they did
for {
server.mx.Lock()
numPeers := len(server.db["floodsub:foobar"])
server.mx.Unlock()
if numPeers == numHosts-1 {
break
} else {
time.Sleep(time.Millisecond * 100)
}
}
for i, h := range hosts[1:] {
if !server.hasPeerRecord("floodsub:"+string(bitmask), h.ID()) {
t.Fatalf("Server did not register host %d with ID: %s", i+1, h.ID().Pretty())
}
}
// Try subscribing followed by publishing a single message
subch, err := bitmaskHandlers[0].Subscribe()
if err != nil {
t.Fatal(err)
}
msgs[0] = subch
msg := []byte("first message")
if err := bitmaskHandlers[0].Publish(ctx, msg, WithReadiness(MinBitmaskSize(numHosts-1))); err != nil {
t.Fatal(err)
}
for _, sub := range msgs {
got, err := sub.Next(ctx)
if err != nil {
t.Fatal(sub.err)
}
if !bytes.Equal(msg, got.Data) {
t.Fatal("got wrong message!")
}
}
// Try random peers sending messages and make sure they are received
for i := 0; i < 100; i++ {
msg := []byte(fmt.Sprintf("%d the flooooooood %d", i, i))
owner := rand.Intn(len(psubs))
if err := bitmaskHandlers[owner].Publish(ctx, msg, WithReadiness(MinBitmaskSize(1))); err != nil {
t.Fatal(err)
}
for _, sub := range msgs {
got, err := sub.Next(ctx)
if err != nil {
t.Fatal(sub.err)
}
if !bytes.Equal(msg, got.Data) {
t.Fatal("got wrong message!")
}
}
}
}
func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
t.Skip("flaky test disabled")
ctx, cancel := context.WithCancel(context.Background())
@ -226,15 +131,15 @@ func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
// Setup Discovery server and pubsub clients
partitionSize := BlossomSubDlo - 1
numHosts := partitionSize * 2
const ttl = 1 * time.Minute
const ttl = 10 * time.Minute
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
server1, server2 := newDiscoveryServer(), newDiscoveryServer()
discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(ttl)}
// Put the pubsub clients into two partitions
hosts := getNetHosts(t, ctx, numHosts)
hosts := getDefaultHosts(t, numHosts)
psubs := make([]*PubSub, numHosts)
bitmaskHandlers := make([]*Bitmask, numHosts)
@ -246,7 +151,8 @@ func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
disc := &mockDiscoveryClient{h, s}
ps := getBlossomSub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...)))
psubs[i] = ps
bitmaskHandlers[i], _ = ps.Join(bitmask)
handler, _ := ps.Join(bitmask)
bitmaskHandlers[i] = handler[0]
}
msgs := make([]*Subscription, numHosts)
@ -265,7 +171,7 @@ func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
}
for i := 0; i < partitionSize; i++ {
if _, err := server1.Advertise("floodsub:"+string(bitmask), *host.InfoFromHost(hosts[i+partitionSize]), ttl); err != nil {
if _, err := server1.Advertise("blossomsub:"+string(bitmask), *host.InfoFromHost(hosts[i+partitionSize]), ttl); err != nil {
t.Fatal(err)
}
}
@ -276,7 +182,7 @@ func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
owner := rand.Intn(numHosts)
if err := bitmaskHandlers[owner].Publish(ctx, msg, WithReadiness(MinBitmaskSize(numHosts-1))); err != nil {
if err := bitmaskHandlers[owner].Publish(ctx, bitmaskHandlers[owner].bitmask, msg, WithReadiness(MinBitmaskSize(numHosts-1))); err != nil {
t.Fatal(err)
}
@ -292,7 +198,6 @@ func TestBlossomSubDiscoveryAfterBootstrap(t *testing.T) {
}
}
//lint:ignore U1000 used only by skipped tests at present
func waitUntilBlossomSubMeshCount(ps *PubSub, bitmask []byte, count int) {
done := false
doneCh := make(chan bool, 1)

View File

@ -1,112 +0,0 @@
package blossomsub
import (
"context"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
const (
FloodSubID = protocol.ID("/floodsub/1.0.0")
FloodSubBitmaskSearchSize = 5
)
// NewFloodsubWithProtocols returns a new floodsub-enabled PubSub objecting using the protocols specified in ps.
func NewFloodsubWithProtocols(ctx context.Context, h host.Host, ps []protocol.ID, opts ...Option) (*PubSub, error) {
rt := &FloodSubRouter{
protocols: ps,
}
return NewPubSub(ctx, h, rt, opts...)
}
// NewFloodSub returns a new PubSub object using the FloodSubRouter.
func NewFloodSub(ctx context.Context, h host.Host, opts ...Option) (*PubSub, error) {
return NewFloodsubWithProtocols(ctx, h, []protocol.ID{FloodSubID}, opts...)
}
type FloodSubRouter struct {
p *PubSub
protocols []protocol.ID
tracer *pubsubTracer
}
func (fs *FloodSubRouter) Protocols() []protocol.ID {
return fs.protocols
}
func (fs *FloodSubRouter) Attach(p *PubSub) {
fs.p = p
fs.tracer = p.tracer
}
func (fs *FloodSubRouter) PeerScore(p peer.ID) float64 {
return fs.p.PeerScore(p)
}
func (fs *FloodSubRouter) AddPeer(p peer.ID, proto protocol.ID) {
fs.tracer.AddPeer(p, proto)
}
func (fs *FloodSubRouter) RemovePeer(p peer.ID) {
fs.tracer.RemovePeer(p)
}
func (fs *FloodSubRouter) EnoughPeers(bitmask []byte, suggested int) bool {
// check all peers in the bitmask
tmap, ok := fs.p.bitmasks[string(bitmask)]
if !ok {
return false
}
if suggested == 0 {
suggested = FloodSubBitmaskSearchSize
}
if len(tmap) >= suggested {
return true
}
return false
}
func (fs *FloodSubRouter) AcceptFrom(peer.ID) AcceptStatus {
return AcceptAll
}
func (fs *FloodSubRouter) HandleRPC(rpc *RPC) {}
func (fs *FloodSubRouter) Publish(msg *Message) {
from := msg.ReceivedFrom
bitmask := msg.GetBitmask()
out := rpcWithMessages(msg.Message)
for pid := range fs.p.bitmasks[string(bitmask)] {
if pid == from || pid == peer.ID(msg.GetFrom()) {
continue
}
mch, ok := fs.p.peers[pid]
if !ok {
continue
}
select {
case mch <- out:
fs.tracer.SendRPC(out, pid)
default:
log.Infof("dropping message to peer %s: queue full", pid)
fs.tracer.DropRPC(out, pid)
// Drop it. The peer is too slow.
}
}
}
func (fs *FloodSubRouter) Join(bitmask []byte) {
fs.tracer.Join(bitmask)
}
func (fs *FloodSubRouter) Leave(bitmask []byte) {
fs.tracer.Leave(bitmask)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +1,122 @@
module source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub
go 1.18
go 1.21
toolchain go1.22.4
replace github.com/multiformats/go-multiaddr => ../go-multiaddr
replace github.com/multiformats/go-multiaddr-dns => ../go-multiaddr-dns
replace github.com/libp2p/go-libp2p => ../go-libp2p
replace github.com/libp2p/go-libp2p-gostream => ../go-libp2p-gostream
require (
github.com/benbjohnson/clock v1.3.0
github.com/gogo/protobuf v1.3.2
github.com/benbjohnson/clock v1.3.5
github.com/ipfs/go-log/v2 v2.5.1
github.com/libp2p/go-buffer-pool v0.1.0
github.com/libp2p/go-libp2p v0.25.0
github.com/libp2p/go-libp2p v0.35.4
github.com/libp2p/go-libp2p-testing v0.12.0
github.com/libp2p/go-msgio v0.3.0
github.com/multiformats/go-multiaddr v0.8.0
github.com/multiformats/go-multiaddr v0.12.4
github.com/multiformats/go-varint v0.0.7
google.golang.org/protobuf v1.28.1
google.golang.org/protobuf v1.34.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
github.com/ipfs/go-cid v0.3.2 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-nat v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.1 // indirect
github.com/libp2p/go-reuseport v0.2.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.0 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.1.1 // indirect
github.com/multiformats/go-multicodec v0.7.0 // indirect
github.com/multiformats/go-multihash v0.2.1 // indirect
github.com/multiformats/go-multistream v0.4.0 // indirect
github.com/onsi/ginkgo/v2 v2.5.1 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pion/datachannel v1.5.6 // indirect
github.com/pion/dtls/v2 v2.2.11 // indirect
github.com/pion/ice/v2 v2.3.25 // indirect
github.com/pion/interceptor v0.1.29 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.12 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.14 // indirect
github.com/pion/rtp v1.8.6 // indirect
github.com/pion/sctp v1.8.16 // indirect
github.com/pion/sdp/v3 v3.0.9 // indirect
github.com/pion/srtp/v2 v2.0.18 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/transport/v2 v2.2.5 // indirect
github.com/pion/turn/v2 v2.1.6 // indirect
github.com/pion/webrtc/v3 v3.2.40 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/quic-go/quic-go v0.37.5 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/quic-go v0.44.0 // indirect
github.com/quic-go/webtransport-go v0.8.0 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/testify v1.8.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.7.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
go.uber.org/dig v1.17.1 // indirect
go.uber.org/fx v1.22.1 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

View File

@ -10,8 +10,9 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -21,24 +22,26 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA=
github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@ -47,16 +50,18 @@ github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0
github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@ -69,38 +74,45 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM=
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc=
github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@ -109,13 +121,18 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI=
github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -127,32 +144,32 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=
github.com/libp2p/go-libp2p v0.25.0 h1:ND6Hc6ZYCzC8S++C4mOD7LdPnLXRkNbr12/8FXgUfIo=
github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560=
github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k=
github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ=
github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ=
github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
@ -161,77 +178,122 @@ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdn
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU=
github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI=
github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8=
github.com/multiformats/go-multicodec v0.7.0 h1:rTUjGOwjlhGHbEMbPoSUJowG1spZTVsITRANCjKTUAQ=
github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
github.com/multiformats/go-multistream v0.4.0 h1:5i4JbawClkbuaX+mIVXiHQYVPxUW+zjv6w7jtSRukxc=
github.com/multiformats/go-multistream v0.4.0/go.mod h1:BS6ZSYcA4NwYEaIMeCtpJydp2Dc+fNRA6uJMSu/m8+4=
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE=
github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA=
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw=
github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg=
github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks=
github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/ice/v2 v2.3.25 h1:M5rJA07dqhi3nobJIg+uPtcVjFECTrhcR3n0ns8kDZs=
github.com/pion/ice/v2 v2.3.25/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/pion/interceptor v0.1.29 h1:39fsnlP1U8gw2JzOFWdfCU82vHvhW9o0rZnZF56wF+M=
github.com/pion/interceptor v0.1.29/go.mod h1:ri+LGNjRUc5xUNtDEPzfdkmSqISixVTBF/z/Zms/6T4=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.6 h1:MTmn/b0aWWsAzux2AmP8WGllusBVw4NPYPVFFd7jUPw=
github.com/pion/rtp v1.8.6/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.13/go.mod h1:YKSgO/bO/6aOMP9LCie1DuD7m+GamiK2yIiPM6vH+GA=
github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY=
github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE=
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc=
github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4=
github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0=
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU=
github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
github.com/quic-go/webtransport-go v0.5.1 h1:1eVb7WDWCRoaeTtFHpFBJ6WDN1bSrPrRoW6tZgSw0Ow=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg=
github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM=
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -266,15 +328,18 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
@ -282,18 +347,24 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.22.1 h1:nvvln7mwyT5s1q201YE29V/BFrGor6vMiDNpU/78Mys=
go.uber.org/fx v1.22.1/go.mod h1:HT2M7d7RHo+ebKGh9NRcrsrHHfpZ60nW3QRubMRfv48=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -303,11 +374,22 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230725012225-302865e7556b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -317,8 +399,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -335,9 +421,19 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -351,7 +447,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -368,19 +467,52 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -392,11 +524,12 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -417,27 +550,28 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -45,7 +45,7 @@ func (gt *gossipTracer) Start(bs *BlossomSubRouter) {
}
// track a promise to deliver a message from a list of msgIDs we are requesting
func (gt *gossipTracer) AddPromise(p peer.ID, msgIDs []string) {
func (gt *gossipTracer) AddPromise(p peer.ID, msgIDs [][]byte) {
if gt == nil {
return
}
@ -54,12 +54,11 @@ func (gt *gossipTracer) AddPromise(p peer.ID, msgIDs []string) {
mid := msgIDs[idx]
gt.Lock()
defer gt.Unlock()
promises, ok := gt.promises[mid]
promises, ok := gt.promises[string(mid)]
if !ok {
promises = make(map[peer.ID]time.Time)
gt.promises[mid] = promises
gt.promises[string(mid)] = promises
}
_, ok = promises[p]
@ -70,8 +69,10 @@ func (gt *gossipTracer) AddPromise(p peer.ID, msgIDs []string) {
peerPromises = make(map[string]struct{})
gt.peerPromises[p] = peerPromises
}
peerPromises[mid] = struct{}{}
peerPromises[string(mid)] = struct{}{}
}
gt.Unlock()
}
// returns the number of broken promises for each peer who didn't follow up
@ -82,7 +83,6 @@ func (gt *gossipTracer) GetBrokenPromises() map[peer.ID]int {
}
gt.Lock()
defer gt.Unlock()
var res map[peer.ID]int
now := time.Now()
@ -111,6 +111,7 @@ func (gt *gossipTracer) GetBrokenPromises() map[peer.ID]int {
}
}
gt.Unlock()
return res
}
@ -120,24 +121,26 @@ func (gt *gossipTracer) fulfillPromise(msg *Message) {
mid := gt.idGen.ID(msg)
gt.Lock()
defer gt.Unlock()
promises, ok := gt.promises[mid]
promises, ok := gt.promises[string(mid)]
if !ok {
gt.Unlock()
return
}
delete(gt.promises, mid)
delete(gt.promises, string(mid))
// delete the promise for all peers that promised it, as they have no way to fulfill it.
for p := range promises {
peerPromises, ok := gt.peerPromises[p]
if ok {
delete(peerPromises, mid)
delete(peerPromises, string(mid))
if len(peerPromises) == 0 {
delete(gt.peerPromises, p)
}
}
}
gt.Unlock()
}
func (gt *gossipTracer) DeliverMessage(msg *Message) {
@ -181,10 +184,10 @@ func (gt *gossipTracer) UndeliverableMessage(msg *Message) {}
func (gt *gossipTracer) ThrottlePeer(p peer.ID) {
gt.Lock()
defer gt.Unlock()
peerPromises, ok := gt.peerPromises[p]
if !ok {
gt.Unlock()
return
}
@ -197,4 +200,5 @@ func (gt *gossipTracer) ThrottlePeer(p peer.ID) {
}
delete(gt.peerPromises, p)
gt.Unlock()
}

View File

@ -18,7 +18,7 @@ func TestBrokenPromises(t *testing.T) {
peerB := peer.ID("B")
peerC := peer.ID("C")
var mids []string
var mids [][]byte
for i := 0; i < 100; i++ {
m := makeTestMessage(i)
m.From = []byte(peerA)
@ -72,7 +72,7 @@ func TestNoBrokenPromises(t *testing.T) {
peerB := peer.ID("B")
var msgs []*pb.Message
var mids []string
var mids [][]byte
for i := 0; i < 100; i++ {
m := makeTestMessage(i)
m.From = []byte(peerA)

View File

@ -30,7 +30,7 @@ func NewMessageCache(gossip, history int) *MessageCache {
peertx: make(map[string]map[peer.ID]int),
history: make([][]CacheEntry, history),
gossip: gossip,
msgID: func(msg *Message) string {
msgID: func(msg *Message) []byte {
return DefaultMsgIdFn(msg.Message)
},
}
@ -41,47 +41,47 @@ type MessageCache struct {
peertx map[string]map[peer.ID]int
history [][]CacheEntry
gossip int
msgID func(*Message) string
msgID func(*Message) []byte
}
func (mc *MessageCache) SetMsgIdFn(msgID func(*Message) string) {
func (mc *MessageCache) SetMsgIdFn(msgID func(*Message) []byte) {
mc.msgID = msgID
}
type CacheEntry struct {
mid string
mid []byte
bitmask []byte
}
func (mc *MessageCache) Put(msg *Message) {
mid := mc.msgID(msg)
mc.msgs[mid] = msg
mc.msgs[string(mid)] = msg
mc.history[0] = append(mc.history[0], CacheEntry{mid: mid, bitmask: msg.GetBitmask()})
}
func (mc *MessageCache) Get(mid string) (*Message, bool) {
m, ok := mc.msgs[mid]
func (mc *MessageCache) Get(mid []byte) (*Message, bool) {
m, ok := mc.msgs[string(mid)]
return m, ok
}
func (mc *MessageCache) GetForPeer(mid string, p peer.ID) (*Message, int, bool) {
m, ok := mc.msgs[mid]
func (mc *MessageCache) GetForPeer(mid []byte, p peer.ID) (*Message, int, bool) {
m, ok := mc.msgs[string(mid)]
if !ok {
return nil, 0, false
}
tx, ok := mc.peertx[mid]
tx, ok := mc.peertx[string(mid)]
if !ok {
tx = make(map[peer.ID]int)
mc.peertx[mid] = tx
mc.peertx[string(mid)] = tx
}
tx[p]++
return m, tx[p], true
}
func (mc *MessageCache) GetGossipIDs(bitmask []byte) []string {
var mids []string
func (mc *MessageCache) GetGossipIDs(bitmask []byte) [][]byte {
var mids [][]byte
for _, entries := range mc.history[:mc.gossip] {
for _, entry := range entries {
if bytes.Equal(entry.bitmask, bitmask) {
@ -95,8 +95,8 @@ func (mc *MessageCache) GetGossipIDs(bitmask []byte) []string {
func (mc *MessageCache) Shift() {
last := mc.history[len(mc.history)-1]
for _, entry := range last {
delete(mc.msgs, entry.mid)
delete(mc.peertx, entry.mid)
delete(mc.msgs, string(entry.mid))
delete(mc.peertx, string(entry.mid))
}
for i := len(mc.history) - 2; i >= 0; i-- {
mc.history[i+1] = mc.history[i]

View File

@ -1,6 +1,7 @@
package blossomsub
import (
"bytes"
"encoding/binary"
"fmt"
"testing"
@ -33,14 +34,14 @@ func TestMessageCache(t *testing.T) {
}
}
gids := mcache.GetGossipIDs([]byte{0x7e, 0x57})
gids := mcache.GetGossipIDs([]byte{0x01, 0x00})
if len(gids) != 10 {
t.Fatalf("Expected 10 gossip IDs; got %d", len(gids))
}
for i := 0; i < 10; i++ {
mid := msgID(msgs[i])
if mid != gids[i] {
if !bytes.Equal(mid, gids[i]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
@ -62,21 +63,21 @@ func TestMessageCache(t *testing.T) {
}
}
gids = mcache.GetGossipIDs([]byte{0x7e, 0x57})
gids = mcache.GetGossipIDs([]byte{0x01, 0x00})
if len(gids) != 20 {
t.Fatalf("Expected 20 gossip IDs; got %d", len(gids))
}
for i := 0; i < 10; i++ {
mid := msgID(msgs[i])
if mid != gids[10+i] {
if !bytes.Equal(mid, gids[10+i]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
for i := 10; i < 20; i++ {
mid := msgID(msgs[i])
if mid != gids[i-10] {
if !bytes.Equal(mid, gids[i-10]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
@ -125,28 +126,28 @@ func TestMessageCache(t *testing.T) {
}
}
gids = mcache.GetGossipIDs([]byte{0x7e, 0x57})
gids = mcache.GetGossipIDs([]byte{0x01, 0x00})
if len(gids) != 30 {
t.Fatalf("Expected 30 gossip IDs; got %d", len(gids))
}
for i := 0; i < 10; i++ {
mid := msgID(msgs[50+i])
if mid != gids[i] {
if !bytes.Equal(mid, gids[i]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
for i := 10; i < 20; i++ {
mid := msgID(msgs[30+i])
if mid != gids[i] {
if !bytes.Equal(mid, gids[i]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
for i := 20; i < 30; i++ {
mid := msgID(msgs[10+i])
if mid != gids[i] {
if !bytes.Equal(mid, gids[i]) {
t.Fatalf("GossipID mismatch for message %d", i)
}
}
@ -157,11 +158,11 @@ func makeTestMessage(n int) *pb.Message {
seqno := make([]byte, 8)
binary.BigEndian.PutUint64(seqno, uint64(n))
data := []byte(fmt.Sprintf("%d", n))
bitmask := []byte{0x7e, 0x57}
bitmask := []byte{0x01, 0x00}
return &pb.Message{
Data: data,
Bitmask: bitmask,
From: []byte([]byte{0x7e, 0x57}),
From: []byte([]byte{0x01, 0x00}),
Seqno: seqno,
}
}

View File

@ -30,8 +30,8 @@ func (m *msgIDGenerator) Set(bitmask []byte, gen MsgIdFunction) {
}
// ID computes ID for the msg or short-circuits with the cached value.
func (m *msgIDGenerator) ID(msg *Message) string {
if msg.ID != "" {
func (m *msgIDGenerator) ID(msg *Message) []byte {
if len(msg.ID) != 0 {
return msg.ID
}
@ -40,7 +40,7 @@ func (m *msgIDGenerator) ID(msg *Message) string {
}
// RawID computes ID for the proto 'msg'.
func (m *msgIDGenerator) RawID(msg *pb.Message) string {
func (m *msgIDGenerator) RawID(msg *pb.Message) []byte {
m.bitmaskGensLk.RLock()
gen, ok := m.bitmaskGens[string(msg.GetBitmask())]
m.bitmaskGensLk.RUnlock()

View File

@ -1,75 +0,0 @@
package blossomsub
import (
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
)
var _ network.Notifiee = (*PubSubNotif)(nil)
type PubSubNotif PubSub
func (p *PubSubNotif) OpenedStream(n network.Network, s network.Stream) {
}
func (p *PubSubNotif) ClosedStream(n network.Network, s network.Stream) {
}
func (p *PubSubNotif) Connected(n network.Network, c network.Conn) {
// ignore transient connections
if c.Stat().Limited {
return
}
go func() {
p.newPeersPrioLk.RLock()
p.newPeersMx.Lock()
p.newPeersPend[c.RemotePeer()] = struct{}{}
p.newPeersMx.Unlock()
p.newPeersPrioLk.RUnlock()
select {
case p.newPeers <- struct{}{}:
default:
}
}()
}
func (p *PubSubNotif) Disconnected(n network.Network, c network.Conn) {
}
func (p *PubSubNotif) Listen(n network.Network, _ ma.Multiaddr) {
}
func (p *PubSubNotif) ListenClose(n network.Network, _ ma.Multiaddr) {
}
func (p *PubSubNotif) Initialize() {
isTransient := func(pid peer.ID) bool {
for _, c := range p.host.Network().ConnsToPeer(pid) {
if !c.Stat().Limited {
return false
}
}
return true
}
p.newPeersPrioLk.RLock()
p.newPeersMx.Lock()
for _, pid := range p.host.Network().Peers() {
if isTransient(pid) {
continue
}
p.newPeersPend[pid] = struct{}{}
}
p.newPeersMx.Unlock()
p.newPeersPrioLk.RUnlock()
select {
case p.newPeers <- struct{}{}:
default:
}
}

View File

@ -246,9 +246,8 @@ type ControlIHave struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Bitmask []byte `protobuf:"bytes,1,opt,name=bitmask,proto3" json:"bitmask,omitempty"`
// implementors from other languages should use bytes here - go protobuf emits invalid utf8 strings
MessageIDs []string `protobuf:"bytes,2,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"`
Bitmask []byte `protobuf:"bytes,1,opt,name=bitmask,proto3" json:"bitmask,omitempty"`
MessageIDs [][]byte `protobuf:"bytes,2,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"`
}
func (x *ControlIHave) Reset() {
@ -290,7 +289,7 @@ func (x *ControlIHave) GetBitmask() []byte {
return nil
}
func (x *ControlIHave) GetMessageIDs() []string {
func (x *ControlIHave) GetMessageIDs() [][]byte {
if x != nil {
return x.MessageIDs
}
@ -302,8 +301,7 @@ type ControlIWant struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// implementors from other languages should use bytes here - go protobuf emits invalid utf8 strings
MessageIDs []string `protobuf:"bytes,1,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"`
MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"`
}
func (x *ControlIWant) Reset() {
@ -338,7 +336,7 @@ func (*ControlIWant) Descriptor() ([]byte, []int) {
return file_rpc_proto_rawDescGZIP(), []int{4}
}
func (x *ControlIWant) GetMessageIDs() []string {
func (x *ControlIWant) GetMessageIDs() [][]byte {
if x != nil {
return x.MessageIDs
}
@ -612,10 +610,10 @@ var file_rpc_proto_rawDesc = []byte{
0x49, 0x48, 0x61, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x12,
0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22,
0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22,
0x2e, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x12,
0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22,
0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22,
0x28, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x71, 0x0a, 0x0c, 0x43, 0x6f, 0x6e,

View File

@ -34,13 +34,11 @@ message ControlMessage {
message ControlIHave {
bytes bitmask = 1;
// implementors from other languages should use bytes here - go protobuf emits invalid utf8 strings
repeated string messageIDs = 2;
repeated bytes messageIDs = 2;
}
message ControlIWant {
// implementors from other languages should use bytes here - go protobuf emits invalid utf8 strings
repeated string messageIDs = 1;
repeated bytes messageIDs = 1;
}
message ControlGraft {

View File

@ -23,19 +23,20 @@ const (
type TraceEvent_Type int32
const (
TraceEvent_PUBLISH_MESSAGE TraceEvent_Type = 0
TraceEvent_REJECT_MESSAGE TraceEvent_Type = 1
TraceEvent_DUPLICATE_MESSAGE TraceEvent_Type = 2
TraceEvent_DELIVER_MESSAGE TraceEvent_Type = 3
TraceEvent_ADD_PEER TraceEvent_Type = 4
TraceEvent_REMOVE_PEER TraceEvent_Type = 5
TraceEvent_RECV_RPC TraceEvent_Type = 6
TraceEvent_SEND_RPC TraceEvent_Type = 7
TraceEvent_DROP_RPC TraceEvent_Type = 8
TraceEvent_JOIN TraceEvent_Type = 9
TraceEvent_LEAVE TraceEvent_Type = 10
TraceEvent_GRAFT TraceEvent_Type = 11
TraceEvent_PRUNE TraceEvent_Type = 12
TraceEvent_PUBLISH_MESSAGE TraceEvent_Type = 0
TraceEvent_REJECT_MESSAGE TraceEvent_Type = 1
TraceEvent_DUPLICATE_MESSAGE TraceEvent_Type = 2
TraceEvent_DELIVER_MESSAGE TraceEvent_Type = 3
TraceEvent_ADD_PEER TraceEvent_Type = 4
TraceEvent_REMOVE_PEER TraceEvent_Type = 5
TraceEvent_RECV_RPC TraceEvent_Type = 6
TraceEvent_SEND_RPC TraceEvent_Type = 7
TraceEvent_DROP_RPC TraceEvent_Type = 8
TraceEvent_JOIN TraceEvent_Type = 9
TraceEvent_LEAVE TraceEvent_Type = 10
TraceEvent_GRAFT TraceEvent_Type = 11
TraceEvent_PRUNE TraceEvent_Type = 12
TraceEvent_UNDELIVERABLE_MESSAGE TraceEvent_Type = 13
)
// Enum value maps for TraceEvent_Type.
@ -54,21 +55,23 @@ var (
10: "LEAVE",
11: "GRAFT",
12: "PRUNE",
13: "UNDELIVERABLE_MESSAGE",
}
TraceEvent_Type_value = map[string]int32{
"PUBLISH_MESSAGE": 0,
"REJECT_MESSAGE": 1,
"DUPLICATE_MESSAGE": 2,
"DELIVER_MESSAGE": 3,
"ADD_PEER": 4,
"REMOVE_PEER": 5,
"RECV_RPC": 6,
"SEND_RPC": 7,
"DROP_RPC": 8,
"JOIN": 9,
"LEAVE": 10,
"GRAFT": 11,
"PRUNE": 12,
"PUBLISH_MESSAGE": 0,
"REJECT_MESSAGE": 1,
"DUPLICATE_MESSAGE": 2,
"DELIVER_MESSAGE": 3,
"ADD_PEER": 4,
"REMOVE_PEER": 5,
"RECV_RPC": 6,
"SEND_RPC": 7,
"DROP_RPC": 8,
"JOIN": 9,
"LEAVE": 10,
"GRAFT": 11,
"PRUNE": 12,
"UNDELIVERABLE_MESSAGE": 13,
}
)
@ -104,22 +107,23 @@ type TraceEvent struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type *TraceEvent_Type `protobuf:"varint,1,opt,name=type,proto3,enum=blossomsub.pb.TraceEvent_Type,oneof" json:"type,omitempty"`
PeerID []byte `protobuf:"bytes,2,opt,name=peerID,proto3,oneof" json:"peerID,omitempty"`
Timestamp *int64 `protobuf:"varint,3,opt,name=timestamp,proto3,oneof" json:"timestamp,omitempty"`
PublishMessage *TraceEvent_PublishMessage `protobuf:"bytes,4,opt,name=publishMessage,proto3,oneof" json:"publishMessage,omitempty"`
RejectMessage *TraceEvent_RejectMessage `protobuf:"bytes,5,opt,name=rejectMessage,proto3,oneof" json:"rejectMessage,omitempty"`
DuplicateMessage *TraceEvent_DuplicateMessage `protobuf:"bytes,6,opt,name=duplicateMessage,proto3,oneof" json:"duplicateMessage,omitempty"`
DeliverMessage *TraceEvent_DeliverMessage `protobuf:"bytes,7,opt,name=deliverMessage,proto3,oneof" json:"deliverMessage,omitempty"`
AddPeer *TraceEvent_AddPeer `protobuf:"bytes,8,opt,name=addPeer,proto3,oneof" json:"addPeer,omitempty"`
RemovePeer *TraceEvent_RemovePeer `protobuf:"bytes,9,opt,name=removePeer,proto3,oneof" json:"removePeer,omitempty"`
RecvRPC *TraceEvent_RecvRPC `protobuf:"bytes,10,opt,name=recvRPC,proto3,oneof" json:"recvRPC,omitempty"`
SendRPC *TraceEvent_SendRPC `protobuf:"bytes,11,opt,name=sendRPC,proto3,oneof" json:"sendRPC,omitempty"`
DropRPC *TraceEvent_DropRPC `protobuf:"bytes,12,opt,name=dropRPC,proto3,oneof" json:"dropRPC,omitempty"`
Join *TraceEvent_Join `protobuf:"bytes,13,opt,name=join,proto3,oneof" json:"join,omitempty"`
Leave *TraceEvent_Leave `protobuf:"bytes,14,opt,name=leave,proto3,oneof" json:"leave,omitempty"`
Graft *TraceEvent_Graft `protobuf:"bytes,15,opt,name=graft,proto3,oneof" json:"graft,omitempty"`
Prune *TraceEvent_Prune `protobuf:"bytes,16,opt,name=prune,proto3,oneof" json:"prune,omitempty"`
Type *TraceEvent_Type `protobuf:"varint,1,opt,name=type,proto3,enum=blossomsub.pb.TraceEvent_Type,oneof" json:"type,omitempty"`
PeerID []byte `protobuf:"bytes,2,opt,name=peerID,proto3,oneof" json:"peerID,omitempty"`
Timestamp *int64 `protobuf:"varint,3,opt,name=timestamp,proto3,oneof" json:"timestamp,omitempty"`
PublishMessage *TraceEvent_PublishMessage `protobuf:"bytes,4,opt,name=publishMessage,proto3,oneof" json:"publishMessage,omitempty"`
RejectMessage *TraceEvent_RejectMessage `protobuf:"bytes,5,opt,name=rejectMessage,proto3,oneof" json:"rejectMessage,omitempty"`
DuplicateMessage *TraceEvent_DuplicateMessage `protobuf:"bytes,6,opt,name=duplicateMessage,proto3,oneof" json:"duplicateMessage,omitempty"`
DeliverMessage *TraceEvent_DeliverMessage `protobuf:"bytes,7,opt,name=deliverMessage,proto3,oneof" json:"deliverMessage,omitempty"`
AddPeer *TraceEvent_AddPeer `protobuf:"bytes,8,opt,name=addPeer,proto3,oneof" json:"addPeer,omitempty"`
RemovePeer *TraceEvent_RemovePeer `protobuf:"bytes,9,opt,name=removePeer,proto3,oneof" json:"removePeer,omitempty"`
RecvRPC *TraceEvent_RecvRPC `protobuf:"bytes,10,opt,name=recvRPC,proto3,oneof" json:"recvRPC,omitempty"`
SendRPC *TraceEvent_SendRPC `protobuf:"bytes,11,opt,name=sendRPC,proto3,oneof" json:"sendRPC,omitempty"`
DropRPC *TraceEvent_DropRPC `protobuf:"bytes,12,opt,name=dropRPC,proto3,oneof" json:"dropRPC,omitempty"`
Join *TraceEvent_Join `protobuf:"bytes,13,opt,name=join,proto3,oneof" json:"join,omitempty"`
Leave *TraceEvent_Leave `protobuf:"bytes,14,opt,name=leave,proto3,oneof" json:"leave,omitempty"`
Graft *TraceEvent_Graft `protobuf:"bytes,15,opt,name=graft,proto3,oneof" json:"graft,omitempty"`
Prune *TraceEvent_Prune `protobuf:"bytes,16,opt,name=prune,proto3,oneof" json:"prune,omitempty"`
UndeliverableMessage *TraceEvent_UndeliverableMessage `protobuf:"bytes,17,opt,name=undeliverableMessage,proto3,oneof" json:"undeliverableMessage,omitempty"`
}
func (x *TraceEvent) Reset() {
@ -266,6 +270,13 @@ func (x *TraceEvent) GetPrune() *TraceEvent_Prune {
return nil
}
func (x *TraceEvent) GetUndeliverableMessage() *TraceEvent_UndeliverableMessage {
if x != nil {
return x.UndeliverableMessage
}
return nil
}
type TraceEventBatch struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1036,6 +1047,69 @@ func (x *TraceEvent_Prune) GetBitmask() []byte {
return nil
}
type TraceEvent_UndeliverableMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
MessageID []byte `protobuf:"bytes,1,opt,name=messageID,proto3,oneof" json:"messageID,omitempty"`
Bitmask []byte `protobuf:"bytes,2,opt,name=bitmask,proto3,oneof" json:"bitmask,omitempty"`
ReceivedFrom []byte `protobuf:"bytes,3,opt,name=receivedFrom,proto3,oneof" json:"receivedFrom,omitempty"`
}
func (x *TraceEvent_UndeliverableMessage) Reset() {
*x = TraceEvent_UndeliverableMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TraceEvent_UndeliverableMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TraceEvent_UndeliverableMessage) ProtoMessage() {}
func (x *TraceEvent_UndeliverableMessage) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TraceEvent_UndeliverableMessage.ProtoReflect.Descriptor instead.
func (*TraceEvent_UndeliverableMessage) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 13}
}
func (x *TraceEvent_UndeliverableMessage) GetMessageID() []byte {
if x != nil {
return x.MessageID
}
return nil
}
func (x *TraceEvent_UndeliverableMessage) GetBitmask() []byte {
if x != nil {
return x.Bitmask
}
return nil
}
func (x *TraceEvent_UndeliverableMessage) GetReceivedFrom() []byte {
if x != nil {
return x.ReceivedFrom
}
return nil
}
type TraceEvent_RPCMeta struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1049,7 +1123,7 @@ type TraceEvent_RPCMeta struct {
func (x *TraceEvent_RPCMeta) Reset() {
*x = TraceEvent_RPCMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[15]
mi := &file_trace_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1062,7 +1136,7 @@ func (x *TraceEvent_RPCMeta) String() string {
func (*TraceEvent_RPCMeta) ProtoMessage() {}
func (x *TraceEvent_RPCMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[15]
mi := &file_trace_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1075,7 +1149,7 @@ func (x *TraceEvent_RPCMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_RPCMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_RPCMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 13}
return file_trace_proto_rawDescGZIP(), []int{0, 14}
}
func (x *TraceEvent_RPCMeta) GetMessages() []*TraceEvent_MessageMeta {
@ -1111,7 +1185,7 @@ type TraceEvent_MessageMeta struct {
func (x *TraceEvent_MessageMeta) Reset() {
*x = TraceEvent_MessageMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[16]
mi := &file_trace_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1124,7 +1198,7 @@ func (x *TraceEvent_MessageMeta) String() string {
func (*TraceEvent_MessageMeta) ProtoMessage() {}
func (x *TraceEvent_MessageMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[16]
mi := &file_trace_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1137,7 +1211,7 @@ func (x *TraceEvent_MessageMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_MessageMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_MessageMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 14}
return file_trace_proto_rawDescGZIP(), []int{0, 15}
}
func (x *TraceEvent_MessageMeta) GetMessageID() []byte {
@ -1166,7 +1240,7 @@ type TraceEvent_SubMeta struct {
func (x *TraceEvent_SubMeta) Reset() {
*x = TraceEvent_SubMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[17]
mi := &file_trace_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1179,7 +1253,7 @@ func (x *TraceEvent_SubMeta) String() string {
func (*TraceEvent_SubMeta) ProtoMessage() {}
func (x *TraceEvent_SubMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[17]
mi := &file_trace_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1192,7 +1266,7 @@ func (x *TraceEvent_SubMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_SubMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_SubMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 15}
return file_trace_proto_rawDescGZIP(), []int{0, 16}
}
func (x *TraceEvent_SubMeta) GetSubscribe() bool {
@ -1223,7 +1297,7 @@ type TraceEvent_ControlMeta struct {
func (x *TraceEvent_ControlMeta) Reset() {
*x = TraceEvent_ControlMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[18]
mi := &file_trace_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1236,7 +1310,7 @@ func (x *TraceEvent_ControlMeta) String() string {
func (*TraceEvent_ControlMeta) ProtoMessage() {}
func (x *TraceEvent_ControlMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[18]
mi := &file_trace_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1249,7 +1323,7 @@ func (x *TraceEvent_ControlMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_ControlMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_ControlMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 16}
return file_trace_proto_rawDescGZIP(), []int{0, 17}
}
func (x *TraceEvent_ControlMeta) GetIhave() []*TraceEvent_ControlIHaveMeta {
@ -1292,7 +1366,7 @@ type TraceEvent_ControlIHaveMeta struct {
func (x *TraceEvent_ControlIHaveMeta) Reset() {
*x = TraceEvent_ControlIHaveMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[19]
mi := &file_trace_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1305,7 +1379,7 @@ func (x *TraceEvent_ControlIHaveMeta) String() string {
func (*TraceEvent_ControlIHaveMeta) ProtoMessage() {}
func (x *TraceEvent_ControlIHaveMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[19]
mi := &file_trace_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1318,7 +1392,7 @@ func (x *TraceEvent_ControlIHaveMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_ControlIHaveMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_ControlIHaveMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 17}
return file_trace_proto_rawDescGZIP(), []int{0, 18}
}
func (x *TraceEvent_ControlIHaveMeta) GetBitmask() []byte {
@ -1346,7 +1420,7 @@ type TraceEvent_ControlIWantMeta struct {
func (x *TraceEvent_ControlIWantMeta) Reset() {
*x = TraceEvent_ControlIWantMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[20]
mi := &file_trace_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1359,7 +1433,7 @@ func (x *TraceEvent_ControlIWantMeta) String() string {
func (*TraceEvent_ControlIWantMeta) ProtoMessage() {}
func (x *TraceEvent_ControlIWantMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[20]
mi := &file_trace_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1372,7 +1446,7 @@ func (x *TraceEvent_ControlIWantMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_ControlIWantMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_ControlIWantMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 18}
return file_trace_proto_rawDescGZIP(), []int{0, 19}
}
func (x *TraceEvent_ControlIWantMeta) GetMessageIDs() [][]byte {
@ -1393,7 +1467,7 @@ type TraceEvent_ControlGraftMeta struct {
func (x *TraceEvent_ControlGraftMeta) Reset() {
*x = TraceEvent_ControlGraftMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[21]
mi := &file_trace_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1406,7 +1480,7 @@ func (x *TraceEvent_ControlGraftMeta) String() string {
func (*TraceEvent_ControlGraftMeta) ProtoMessage() {}
func (x *TraceEvent_ControlGraftMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[21]
mi := &file_trace_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1419,7 +1493,7 @@ func (x *TraceEvent_ControlGraftMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_ControlGraftMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_ControlGraftMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 19}
return file_trace_proto_rawDescGZIP(), []int{0, 20}
}
func (x *TraceEvent_ControlGraftMeta) GetBitmask() []byte {
@ -1441,7 +1515,7 @@ type TraceEvent_ControlPruneMeta struct {
func (x *TraceEvent_ControlPruneMeta) Reset() {
*x = TraceEvent_ControlPruneMeta{}
if protoimpl.UnsafeEnabled {
mi := &file_trace_proto_msgTypes[22]
mi := &file_trace_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1454,7 +1528,7 @@ func (x *TraceEvent_ControlPruneMeta) String() string {
func (*TraceEvent_ControlPruneMeta) ProtoMessage() {}
func (x *TraceEvent_ControlPruneMeta) ProtoReflect() protoreflect.Message {
mi := &file_trace_proto_msgTypes[22]
mi := &file_trace_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1467,7 +1541,7 @@ func (x *TraceEvent_ControlPruneMeta) ProtoReflect() protoreflect.Message {
// Deprecated: Use TraceEvent_ControlPruneMeta.ProtoReflect.Descriptor instead.
func (*TraceEvent_ControlPruneMeta) Descriptor() ([]byte, []int) {
return file_trace_proto_rawDescGZIP(), []int{0, 20}
return file_trace_proto_rawDescGZIP(), []int{0, 21}
}
func (x *TraceEvent_ControlPruneMeta) GetBitmask() []byte {
@ -1488,7 +1562,7 @@ var File_trace_proto protoreflect.FileDescriptor
var file_trace_proto_rawDesc = []byte{
0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x62,
0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x22, 0xfe, 0x1e, 0x0a,
0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x22, 0xca, 0x21, 0x0a,
0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x62, 0x6c, 0x6f, 0x73,
0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45,
@ -1555,38 +1629,106 @@ var file_trace_proto_rawDesc = []byte{
0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d,
0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x48, 0x0f, 0x52, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65,
0x88, 0x01, 0x01, 0x1a, 0x6c, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d,
0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74,
0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x1a, 0xcd, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x0c,
0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12,
0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48,
0x02, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x03, 0x52,
0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65,
0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x1a, 0xa8, 0x01, 0x0a, 0x10, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63,
0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x01, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88,
0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0c, 0x48, 0x02, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01,
0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42,
0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d,
0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xa6, 0x01, 0x0a,
0x0e, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x88, 0x01, 0x01, 0x12, 0x67, 0x0a, 0x14, 0x75, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72,
0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x2e, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70,
0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x6e, 0x64,
0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x48, 0x10, 0x52, 0x14, 0x75, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x61, 0x62,
0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x1a, 0x6c, 0x0a, 0x0e,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21,
0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01,
0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01,
0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xcd, 0x01, 0x0a, 0x0d, 0x52,
0x65, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x09,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12,
0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73,
0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73,
0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x03, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x49, 0x44, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46,
0x72, 0x6f, 0x6d, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xa8, 0x01, 0x0a, 0x10, 0x44,
0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88,
0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72,
0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65,
0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x02, 0x52, 0x07,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x63,
0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69,
0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xa6, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65,
0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65,
0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
0x48, 0x02, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d,
0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49,
0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x42, 0x0f, 0x0a,
0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x1a, 0x56,
0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65,
0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65,
0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x88, 0x01,
0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06,
0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x50, 0x65, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01,
0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x1a, 0x88, 0x01, 0x0a,
0x07, 0x52, 0x65, 0x63, 0x76, 0x52, 0x50, 0x43, 0x12, 0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65,
0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00,
0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01,
0x01, 0x12, 0x3a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x21, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e,
0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65,
0x74, 0x61, 0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a,
0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x07,
0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a, 0x76, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x52,
0x50, 0x43, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x12,
0x3a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72,
0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61,
0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f,
0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a,
0x76, 0x0a, 0x07, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x50, 0x43, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x65,
0x6e, 0x64, 0x54, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65,
0x6e, 0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73,
0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61,
0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x42, 0x07,
0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a, 0x31, 0x0a, 0x04, 0x4a, 0x6f, 0x69, 0x6e, 0x12,
0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x32, 0x0a, 0x05, 0x4c, 0x65,
0x61, 0x76, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88,
0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x5a,
0x0a, 0x05, 0x47, 0x72, 0x61, 0x66, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49,
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49,
0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x5a, 0x0a, 0x05, 0x50, 0x72,
0x75, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01,
0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42,
0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xac, 0x01, 0x0a, 0x14, 0x55, 0x6e, 0x64, 0x65, 0x6c,
0x69, 0x76, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88,
0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20,
@ -1596,156 +1738,109 @@ var file_trace_proto_rawDesc = []byte{
0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74,
0x6d, 0x61, 0x73, 0x6b, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x64, 0x46, 0x72, 0x6f, 0x6d, 0x1a, 0x56, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72,
0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a,
0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65,
0x72, 0x49, 0x44, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x0a,
0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x70,
0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70,
0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65,
0x72, 0x49, 0x44, 0x1a, 0x88, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x76, 0x52, 0x50, 0x43, 0x12,
0x27, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x64, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d,
0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74,
0x61, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
0x64, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a, 0x76,
0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x50, 0x43, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x65, 0x6e,
0x64, 0x54, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65, 0x6e,
0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75,
0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e,
0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x88,
0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x42, 0x07, 0x0a,
0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a, 0x76, 0x0a, 0x07, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x50,
0x43, 0x12, 0x1b, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3a,
0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62,
0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61,
0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61, 0x48,
0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73,
0x65, 0x6e, 0x64, 0x54, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x1a, 0x31,
0x0a, 0x04, 0x4a, 0x6f, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61,
0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x1a, 0x32, 0x0a, 0x05, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69,
0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69,
0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x5a, 0x0a, 0x05, 0x47, 0x72, 0x61, 0x66, 0x74, 0x12, 0x1b,
0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00,
0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70,
0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x1a, 0x5a, 0x0a, 0x05, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65,
0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65,
0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61,
0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d,
0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49,
0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0xe5, 0x01,
0x0a, 0x07, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x6c,
0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63,
0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65,
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0c,
0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e,
0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75,
0x62, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75,
0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x00, 0x52, 0x07, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x1a, 0x69, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x4d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49,
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61,
0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d,
0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x1a, 0x65, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x09, 0x73,
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00,
0x52, 0x09, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d,
0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a,
0x0a, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x95, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x05, 0x69, 0x68, 0x61, 0x76, 0x65,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d,
0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d, 0x65,
0x74, 0x61, 0x52, 0x05, 0x69, 0x68, 0x61, 0x76, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x69, 0x77, 0x61,
0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73,
0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74,
0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x77, 0x61, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x05, 0x67,
0x72, 0x61, 0x66, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f,
0x64, 0x46, 0x72, 0x6f, 0x6d, 0x1a, 0xe5, 0x01, 0x0a, 0x07, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x74,
0x61, 0x12, 0x41, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62,
0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x6c, 0x6f,
0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61,
0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x67, 0x72, 0x61, 0x66, 0x74, 0x12, 0x40, 0x0a,
0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x62, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0c, 0x73,
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x07, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62,
0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61,
0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50,
0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x1a,
0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d,
0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88,
0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49,
0x44, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x32,
0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65,
0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49,
0x44, 0x73, 0x1a, 0x3d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61,
0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61,
0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x1a, 0x53, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e,
0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0xcf, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
0x13, 0x0a, 0x0f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41,
0x47, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x4d,
0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x55, 0x50, 0x4c,
0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12,
0x13, 0x0a, 0x0f, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41,
0x47, 0x45, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x44, 0x44, 0x5f, 0x50, 0x45, 0x45, 0x52,
0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x50, 0x45, 0x45,
0x52, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x43, 0x56, 0x5f, 0x52, 0x50, 0x43, 0x10,
0x06, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x07, 0x12,
0x0c, 0x0a, 0x08, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x08, 0x12, 0x08, 0x0a,
0x04, 0x4a, 0x4f, 0x49, 0x4e, 0x10, 0x09, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x45, 0x41, 0x56, 0x45,
0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x41, 0x46, 0x54, 0x10, 0x0b, 0x12, 0x09, 0x0a,
0x05, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x10, 0x0c, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70,
0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x0c, 0x0a, 0x0a,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x10, 0x0a,
0x0e, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42,
0x13, 0x0a, 0x11, 0x5f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x50,
0x65, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x65,
0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x52, 0x50, 0x43, 0x42, 0x0a,
0x0a, 0x08, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x52, 0x50, 0x43, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64,
0x72, 0x6f, 0x70, 0x52, 0x50, 0x43, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x42,
0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72,
0x61, 0x66, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x22, 0x42, 0x0a,
0x0f, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68,
0x12, 0x2f, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e,
0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63,
0x68, 0x42, 0x43, 0x5a, 0x41, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69, 0x6c,
0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c, 0x69,
0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f, 0x67,
0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2d, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d,
0x73, 0x75, 0x62, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d,
0x65, 0x74, 0x61, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x88, 0x01,
0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x1a, 0x69, 0x0a,
0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x09,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x00, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12,
0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c,
0x0a, 0x0a, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x42, 0x0a, 0x0a, 0x08,
0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x65, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x4d,
0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61,
0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a,
0x95, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12,
0x40, 0x0a, 0x05, 0x69, 0x68, 0x61, 0x76, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a,
0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54,
0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x68, 0x61, 0x76,
0x65, 0x12, 0x40, 0x0a, 0x05, 0x69, 0x77, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62,
0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x77,
0x61, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x05, 0x67, 0x72, 0x61, 0x66, 0x74, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e,
0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05,
0x67, 0x72, 0x61, 0x66, 0x74, 0x12, 0x40, 0x0a, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x18, 0x04,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75,
0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61,
0x52, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x1a, 0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07,
0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62,
0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x32, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x1a, 0x3d, 0x0a, 0x10, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d,
0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48,
0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a,
0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x53, 0x0a, 0x10, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a,
0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00,
0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05,
0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x65, 0x65,
0x72, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0xea,
0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x55, 0x42, 0x4c, 0x49,
0x53, 0x48, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e,
0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x01,
0x12, 0x15, 0x0a, 0x11, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x45,
0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x45, 0x4c, 0x49, 0x56,
0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08,
0x41, 0x44, 0x44, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45,
0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x52,
0x45, 0x43, 0x56, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x45, 0x4e,
0x44, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x52, 0x4f, 0x50, 0x5f,
0x52, 0x50, 0x43, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x4a, 0x4f, 0x49, 0x4e, 0x10, 0x09, 0x12,
0x09, 0x0a, 0x05, 0x4c, 0x45, 0x41, 0x56, 0x45, 0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52,
0x41, 0x46, 0x54, 0x10, 0x0b, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x10, 0x0c,
0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x41, 0x42, 0x4c,
0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0d, 0x42, 0x07, 0x0a, 0x05, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42,
0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x11, 0x0a,
0x0f, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x65, 0x6c, 0x69,
0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61,
0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x52, 0x50,
0x43, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x52, 0x50, 0x43, 0x42, 0x0a, 0x0a,
0x08, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x50, 0x43, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6a, 0x6f,
0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x42, 0x08, 0x0a, 0x06,
0x5f, 0x67, 0x72, 0x61, 0x66, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65,
0x42, 0x17, 0x0a, 0x15, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x61, 0x62,
0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x42, 0x0a, 0x0f, 0x54, 0x72, 0x61,
0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2f, 0x0a, 0x05,
0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x6c,
0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63,
0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x42, 0x43, 0x5a,
0x41, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69,
0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75,
0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69,
0x62, 0x70, 0x32, 0x70, 0x2d, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2f,
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1761,32 +1856,33 @@ func file_trace_proto_rawDescGZIP() []byte {
}
var file_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
var file_trace_proto_goTypes = []interface{}{
(TraceEvent_Type)(0), // 0: blossomsub.pb.TraceEvent.Type
(*TraceEvent)(nil), // 1: blossomsub.pb.TraceEvent
(*TraceEventBatch)(nil), // 2: blossomsub.pb.TraceEventBatch
(*TraceEvent_PublishMessage)(nil), // 3: blossomsub.pb.TraceEvent.PublishMessage
(*TraceEvent_RejectMessage)(nil), // 4: blossomsub.pb.TraceEvent.RejectMessage
(*TraceEvent_DuplicateMessage)(nil), // 5: blossomsub.pb.TraceEvent.DuplicateMessage
(*TraceEvent_DeliverMessage)(nil), // 6: blossomsub.pb.TraceEvent.DeliverMessage
(*TraceEvent_AddPeer)(nil), // 7: blossomsub.pb.TraceEvent.AddPeer
(*TraceEvent_RemovePeer)(nil), // 8: blossomsub.pb.TraceEvent.RemovePeer
(*TraceEvent_RecvRPC)(nil), // 9: blossomsub.pb.TraceEvent.RecvRPC
(*TraceEvent_SendRPC)(nil), // 10: blossomsub.pb.TraceEvent.SendRPC
(*TraceEvent_DropRPC)(nil), // 11: blossomsub.pb.TraceEvent.DropRPC
(*TraceEvent_Join)(nil), // 12: blossomsub.pb.TraceEvent.Join
(*TraceEvent_Leave)(nil), // 13: blossomsub.pb.TraceEvent.Leave
(*TraceEvent_Graft)(nil), // 14: blossomsub.pb.TraceEvent.Graft
(*TraceEvent_Prune)(nil), // 15: blossomsub.pb.TraceEvent.Prune
(*TraceEvent_RPCMeta)(nil), // 16: blossomsub.pb.TraceEvent.RPCMeta
(*TraceEvent_MessageMeta)(nil), // 17: blossomsub.pb.TraceEvent.MessageMeta
(*TraceEvent_SubMeta)(nil), // 18: blossomsub.pb.TraceEvent.SubMeta
(*TraceEvent_ControlMeta)(nil), // 19: blossomsub.pb.TraceEvent.ControlMeta
(*TraceEvent_ControlIHaveMeta)(nil), // 20: blossomsub.pb.TraceEvent.ControlIHaveMeta
(*TraceEvent_ControlIWantMeta)(nil), // 21: blossomsub.pb.TraceEvent.ControlIWantMeta
(*TraceEvent_ControlGraftMeta)(nil), // 22: blossomsub.pb.TraceEvent.ControlGraftMeta
(*TraceEvent_ControlPruneMeta)(nil), // 23: blossomsub.pb.TraceEvent.ControlPruneMeta
(TraceEvent_Type)(0), // 0: blossomsub.pb.TraceEvent.Type
(*TraceEvent)(nil), // 1: blossomsub.pb.TraceEvent
(*TraceEventBatch)(nil), // 2: blossomsub.pb.TraceEventBatch
(*TraceEvent_PublishMessage)(nil), // 3: blossomsub.pb.TraceEvent.PublishMessage
(*TraceEvent_RejectMessage)(nil), // 4: blossomsub.pb.TraceEvent.RejectMessage
(*TraceEvent_DuplicateMessage)(nil), // 5: blossomsub.pb.TraceEvent.DuplicateMessage
(*TraceEvent_DeliverMessage)(nil), // 6: blossomsub.pb.TraceEvent.DeliverMessage
(*TraceEvent_AddPeer)(nil), // 7: blossomsub.pb.TraceEvent.AddPeer
(*TraceEvent_RemovePeer)(nil), // 8: blossomsub.pb.TraceEvent.RemovePeer
(*TraceEvent_RecvRPC)(nil), // 9: blossomsub.pb.TraceEvent.RecvRPC
(*TraceEvent_SendRPC)(nil), // 10: blossomsub.pb.TraceEvent.SendRPC
(*TraceEvent_DropRPC)(nil), // 11: blossomsub.pb.TraceEvent.DropRPC
(*TraceEvent_Join)(nil), // 12: blossomsub.pb.TraceEvent.Join
(*TraceEvent_Leave)(nil), // 13: blossomsub.pb.TraceEvent.Leave
(*TraceEvent_Graft)(nil), // 14: blossomsub.pb.TraceEvent.Graft
(*TraceEvent_Prune)(nil), // 15: blossomsub.pb.TraceEvent.Prune
(*TraceEvent_UndeliverableMessage)(nil), // 16: blossomsub.pb.TraceEvent.UndeliverableMessage
(*TraceEvent_RPCMeta)(nil), // 17: blossomsub.pb.TraceEvent.RPCMeta
(*TraceEvent_MessageMeta)(nil), // 18: blossomsub.pb.TraceEvent.MessageMeta
(*TraceEvent_SubMeta)(nil), // 19: blossomsub.pb.TraceEvent.SubMeta
(*TraceEvent_ControlMeta)(nil), // 20: blossomsub.pb.TraceEvent.ControlMeta
(*TraceEvent_ControlIHaveMeta)(nil), // 21: blossomsub.pb.TraceEvent.ControlIHaveMeta
(*TraceEvent_ControlIWantMeta)(nil), // 22: blossomsub.pb.TraceEvent.ControlIWantMeta
(*TraceEvent_ControlGraftMeta)(nil), // 23: blossomsub.pb.TraceEvent.ControlGraftMeta
(*TraceEvent_ControlPruneMeta)(nil), // 24: blossomsub.pb.TraceEvent.ControlPruneMeta
}
var file_trace_proto_depIdxs = []int32{
0, // 0: blossomsub.pb.TraceEvent.type:type_name -> blossomsub.pb.TraceEvent.Type
@ -1803,22 +1899,23 @@ var file_trace_proto_depIdxs = []int32{
13, // 11: blossomsub.pb.TraceEvent.leave:type_name -> blossomsub.pb.TraceEvent.Leave
14, // 12: blossomsub.pb.TraceEvent.graft:type_name -> blossomsub.pb.TraceEvent.Graft
15, // 13: blossomsub.pb.TraceEvent.prune:type_name -> blossomsub.pb.TraceEvent.Prune
1, // 14: blossomsub.pb.TraceEventBatch.batch:type_name -> blossomsub.pb.TraceEvent
16, // 15: blossomsub.pb.TraceEvent.RecvRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
16, // 16: blossomsub.pb.TraceEvent.SendRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
16, // 17: blossomsub.pb.TraceEvent.DropRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
17, // 18: blossomsub.pb.TraceEvent.RPCMeta.messages:type_name -> blossomsub.pb.TraceEvent.MessageMeta
18, // 19: blossomsub.pb.TraceEvent.RPCMeta.subscription:type_name -> blossomsub.pb.TraceEvent.SubMeta
19, // 20: blossomsub.pb.TraceEvent.RPCMeta.control:type_name -> blossomsub.pb.TraceEvent.ControlMeta
20, // 21: blossomsub.pb.TraceEvent.ControlMeta.ihave:type_name -> blossomsub.pb.TraceEvent.ControlIHaveMeta
21, // 22: blossomsub.pb.TraceEvent.ControlMeta.iwant:type_name -> blossomsub.pb.TraceEvent.ControlIWantMeta
22, // 23: blossomsub.pb.TraceEvent.ControlMeta.graft:type_name -> blossomsub.pb.TraceEvent.ControlGraftMeta
23, // 24: blossomsub.pb.TraceEvent.ControlMeta.prune:type_name -> blossomsub.pb.TraceEvent.ControlPruneMeta
25, // [25:25] is the sub-list for method output_type
25, // [25:25] is the sub-list for method input_type
25, // [25:25] is the sub-list for extension type_name
25, // [25:25] is the sub-list for extension extendee
0, // [0:25] is the sub-list for field type_name
16, // 14: blossomsub.pb.TraceEvent.undeliverableMessage:type_name -> blossomsub.pb.TraceEvent.UndeliverableMessage
1, // 15: blossomsub.pb.TraceEventBatch.batch:type_name -> blossomsub.pb.TraceEvent
17, // 16: blossomsub.pb.TraceEvent.RecvRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
17, // 17: blossomsub.pb.TraceEvent.SendRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
17, // 18: blossomsub.pb.TraceEvent.DropRPC.meta:type_name -> blossomsub.pb.TraceEvent.RPCMeta
18, // 19: blossomsub.pb.TraceEvent.RPCMeta.messages:type_name -> blossomsub.pb.TraceEvent.MessageMeta
19, // 20: blossomsub.pb.TraceEvent.RPCMeta.subscription:type_name -> blossomsub.pb.TraceEvent.SubMeta
20, // 21: blossomsub.pb.TraceEvent.RPCMeta.control:type_name -> blossomsub.pb.TraceEvent.ControlMeta
21, // 22: blossomsub.pb.TraceEvent.ControlMeta.ihave:type_name -> blossomsub.pb.TraceEvent.ControlIHaveMeta
22, // 23: blossomsub.pb.TraceEvent.ControlMeta.iwant:type_name -> blossomsub.pb.TraceEvent.ControlIWantMeta
23, // 24: blossomsub.pb.TraceEvent.ControlMeta.graft:type_name -> blossomsub.pb.TraceEvent.ControlGraftMeta
24, // 25: blossomsub.pb.TraceEvent.ControlMeta.prune:type_name -> blossomsub.pb.TraceEvent.ControlPruneMeta
26, // [26:26] is the sub-list for method output_type
26, // [26:26] is the sub-list for method input_type
26, // [26:26] is the sub-list for extension type_name
26, // [26:26] is the sub-list for extension extendee
0, // [0:26] is the sub-list for field type_name
}
func init() { file_trace_proto_init() }
@ -2008,7 +2105,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_RPCMeta); i {
switch v := v.(*TraceEvent_UndeliverableMessage); i {
case 0:
return &v.state
case 1:
@ -2020,7 +2117,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_MessageMeta); i {
switch v := v.(*TraceEvent_RPCMeta); i {
case 0:
return &v.state
case 1:
@ -2032,7 +2129,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_SubMeta); i {
switch v := v.(*TraceEvent_MessageMeta); i {
case 0:
return &v.state
case 1:
@ -2044,7 +2141,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlMeta); i {
switch v := v.(*TraceEvent_SubMeta); i {
case 0:
return &v.state
case 1:
@ -2056,7 +2153,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlIHaveMeta); i {
switch v := v.(*TraceEvent_ControlMeta); i {
case 0:
return &v.state
case 1:
@ -2068,7 +2165,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlIWantMeta); i {
switch v := v.(*TraceEvent_ControlIHaveMeta); i {
case 0:
return &v.state
case 1:
@ -2080,7 +2177,7 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlGraftMeta); i {
switch v := v.(*TraceEvent_ControlIWantMeta); i {
case 0:
return &v.state
case 1:
@ -2092,6 +2189,18 @@ func file_trace_proto_init() {
}
}
file_trace_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlGraftMeta); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_trace_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TraceEvent_ControlPruneMeta); i {
case 0:
return &v.state
@ -2121,16 +2230,17 @@ func file_trace_proto_init() {
file_trace_proto_msgTypes[15].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[16].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[17].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[19].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[21].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[18].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[20].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[22].OneofWrappers = []interface{}{}
file_trace_proto_msgTypes[23].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_trace_proto_rawDesc,
NumEnums: 1,
NumMessages: 23,
NumMessages: 24,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -22,6 +22,7 @@ message TraceEvent {
optional Leave leave = 14;
optional Graft graft = 15;
optional Prune prune = 16;
optional UndeliverableMessage undeliverableMessage = 17;
enum Type {
PUBLISH_MESSAGE = 0;
@ -37,6 +38,7 @@ message TraceEvent {
LEAVE = 10;
GRAFT = 11;
PRUNE = 12;
UNDELIVERABLE_MESSAGE = 13;
}
message PublishMessage {
@ -105,6 +107,12 @@ message TraceEvent {
optional bytes bitmask = 2;
}
message UndeliverableMessage {
optional bytes messageID = 1;
optional bytes bitmask = 2;
optional bytes receivedFrom = 3;
}
message RPCMeta {
repeated MessageMeta messages = 1;
repeated SubMeta subscription = 2;

View File

@ -204,13 +204,12 @@ func newPeerGater(ctx context.Context, host host.Host, params *PeerGaterParams)
func (pg *peerGater) background(ctx context.Context) {
tick := time.NewTicker(pg.params.DecayInterval)
defer tick.Stop()
for {
select {
case <-tick.C:
pg.decayStats()
case <-ctx.Done():
tick.Stop()
return
}
}
@ -218,7 +217,6 @@ func (pg *peerGater) background(ctx context.Context) {
func (pg *peerGater) decayStats() {
pg.Lock()
defer pg.Unlock()
pg.validate *= pg.params.GlobalDecay
if pg.validate < pg.params.DecayToZero {
@ -256,6 +254,8 @@ func (pg *peerGater) decayStats() {
delete(pg.ipStats, ip)
}
}
pg.Unlock()
}
func (pg *peerGater) getPeerStats(p peer.ID) *peerGaterStats {
@ -323,21 +323,23 @@ func (pg *peerGater) AcceptFrom(p peer.ID) AcceptStatus {
}
pg.Lock()
defer pg.Unlock()
// check the quiet period; if the validation queue has not throttled for more than the Quiet
// interval, we turn off the circuit breaker and accept.
if time.Since(pg.lastThrottle) > pg.params.Quiet {
pg.Unlock()
return AcceptAll
}
// no throttle events -- or they have decayed; accept.
if pg.throttle == 0 {
pg.Unlock()
return AcceptAll
}
// check the throttle/validate ration; if it is below threshold we accept.
if pg.validate != 0 && pg.throttle/pg.validate < pg.params.Threshold {
pg.Unlock()
return AcceptAll
}
@ -346,6 +348,7 @@ func (pg *peerGater) AcceptFrom(p peer.ID) AcceptStatus {
// compute the goodput of the peer; the denominator is the weighted mix of message counters
total := st.deliver + pg.params.DuplicateWeight*st.duplicate + pg.params.IgnoreWeight*st.ignore + pg.params.RejectWeight*st.reject
if total == 0 {
pg.Unlock()
return AcceptAll
}
@ -355,10 +358,12 @@ func (pg *peerGater) AcceptFrom(p peer.ID) AcceptStatus {
// accepted; this is not a sinkhole/blacklist.
threshold := (1 + st.deliver) / (1 + total)
if rand.Float64() < threshold {
pg.Unlock()
return AcceptAll
}
log.Debugf("throttling peer %s with threshold %f", p, threshold)
pg.Unlock()
return AcceptControl
}
@ -368,21 +373,21 @@ var _ RawTracer = (*peerGater)(nil)
// tracer interface
func (pg *peerGater) AddPeer(p peer.ID, proto protocol.ID) {
pg.Lock()
defer pg.Unlock()
st := pg.getPeerStats(p)
st.connected++
pg.Unlock()
}
func (pg *peerGater) RemovePeer(p peer.ID) {
pg.Lock()
defer pg.Unlock()
st := pg.getPeerStats(p)
st.connected--
st.expire = time.Now().Add(pg.params.RetainStats)
delete(pg.peerStats, p)
pg.Unlock()
}
func (pg *peerGater) Join(bitmask []byte) {}
@ -392,14 +397,13 @@ func (pg *peerGater) Prune(p peer.ID, bitmask []byte) {}
func (pg *peerGater) ValidateMessage(msg *Message) {
pg.Lock()
defer pg.Unlock()
pg.validate++
pg.Unlock()
}
func (pg *peerGater) DeliverMessage(msg *Message) {
pg.Lock()
defer pg.Unlock()
st := pg.getPeerStats(msg.ReceivedFrom)
@ -411,11 +415,11 @@ func (pg *peerGater) DeliverMessage(msg *Message) {
}
st.deliver += weight
pg.Unlock()
}
func (pg *peerGater) RejectMessage(msg *Message, reason string) {
pg.Lock()
defer pg.Unlock()
switch reason {
case RejectValidationQueueFull:
@ -432,14 +436,15 @@ func (pg *peerGater) RejectMessage(msg *Message, reason string) {
st := pg.getPeerStats(msg.ReceivedFrom)
st.reject++
}
pg.Unlock()
}
func (pg *peerGater) DuplicateMessage(msg *Message) {
pg.Lock()
defer pg.Unlock()
st := pg.getPeerStats(msg.ReceivedFrom)
st.duplicate++
pg.Unlock()
}
func (pg *peerGater) ThrottlePeer(p peer.ID) {}

View File

@ -0,0 +1,112 @@
package blossomsub
import (
"context"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
func (ps *PubSub) watchForNewPeers(ctx context.Context) {
// We don't bother subscribing to "connectivity" events because we always run identify after
// every new connection.
sub, err := ps.host.EventBus().Subscribe([]interface{}{
&event.EvtPeerIdentificationCompleted{},
&event.EvtPeerProtocolsUpdated{},
})
if err != nil {
log.Errorf("failed to subscribe to peer identification events: %v", err)
return
}
ps.newPeersPrioLk.RLock()
ps.newPeersMx.Lock()
for _, pid := range ps.host.Network().Peers() {
if ps.host.Network().Connectedness(pid) != network.Connected {
continue
}
ps.newPeersPend[pid] = struct{}{}
}
ps.newPeersMx.Unlock()
ps.newPeersPrioLk.RUnlock()
select {
case ps.newPeers <- struct{}{}:
default:
}
var supportsProtocol func(protocol.ID) bool
if ps.protoMatchFunc != nil {
var supportedProtocols []func(protocol.ID) bool
for _, proto := range ps.rt.Protocols() {
supportedProtocols = append(supportedProtocols, ps.protoMatchFunc(proto))
}
supportsProtocol = func(proto protocol.ID) bool {
for _, fn := range supportedProtocols {
if (fn)(proto) {
return true
}
}
return false
}
} else {
supportedProtocols := make(map[protocol.ID]struct{})
for _, proto := range ps.rt.Protocols() {
supportedProtocols[proto] = struct{}{}
}
supportsProtocol = func(proto protocol.ID) bool {
_, ok := supportedProtocols[proto]
return ok
}
}
for ctx.Err() == nil {
var ev any
select {
case <-ctx.Done():
sub.Close()
return
case ev = <-sub.Out():
}
var protos []protocol.ID
var peer peer.ID
switch ev := ev.(type) {
case event.EvtPeerIdentificationCompleted:
peer = ev.Peer
protos = ev.Protocols
case event.EvtPeerProtocolsUpdated:
peer = ev.Peer
protos = ev.Added
default:
continue
}
// We don't bother checking connectivity (connected and non-"limited") here because
// we'll check when actually handling the new peer.
for _, p := range protos {
if supportsProtocol(p) {
ps.notifyNewPeer(peer)
break
}
}
}
sub.Close()
}
func (ps *PubSub) notifyNewPeer(peer peer.ID) {
ps.newPeersPrioLk.RLock()
ps.newPeersMx.Lock()
ps.newPeersPend[peer] = struct{}{}
ps.newPeersMx.Unlock()
ps.newPeersPrioLk.RUnlock()
select {
case ps.newPeers <- struct{}{}:
default:
}
}

View File

@ -1,11 +1,14 @@
package blossomsub
import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math/rand"
"slices"
"sync"
"sync/atomic"
"time"
@ -23,8 +26,8 @@ import (
logging "github.com/ipfs/go-log/v2"
)
// DefaultMaximumMessageSize is 16.7 MB.
const DefaultMaxMessageSize = 1 << 24
// DefaultMaximumMessageSize is 1 MB.
const DefaultMaxMessageSize = 1 << 20
var (
// TimeCacheDuration specifies how long a message ID will be remembered as seen.
@ -147,7 +150,8 @@ type PubSub struct {
blacklist Blacklist
blacklistPeer chan peer.ID
peers map[peer.ID]chan *RPC
peers map[peer.ID]chan *RPC
peersMx sync.RWMutex
inboundStreamsMx sync.Mutex
inboundStreams map[peer.ID]network.Stream
@ -231,7 +235,7 @@ const (
type Message struct {
*pb.Message
ID string
ID []byte
ReceivedFrom peer.ID
ValidatorData interface{}
Local bool
@ -242,7 +246,7 @@ func (m *Message) GetFrom() peer.ID {
}
type RPC struct {
pb.RPC
*pb.RPC
// unexported on purpose, not sending this over the wire
from peer.ID
@ -263,7 +267,7 @@ func NewPubSub(ctx context.Context, h host.Host, rt PubSubRouter, opts ...Option
peerOutboundQueueSize: 32,
signID: h.ID(),
signKey: nil,
signPolicy: StrictSign,
signPolicy: LaxSign,
incoming: make(chan *RPC, 32),
newPeers: make(chan struct{}, 1),
newPeersPend: make(map[peer.ID]struct{}),
@ -330,20 +334,19 @@ func NewPubSub(ctx context.Context, h host.Host, rt PubSubRouter, opts ...Option
h.SetStreamHandler(id, ps.handleNewStream)
}
}
h.Network().Notify((*PubSubNotif)(ps))
go ps.watchForNewPeers(ctx)
ps.val.Start(ps)
go ps.processLoop(ctx)
(*PubSubNotif)(ps).Initialize()
return ps, nil
}
// MsgIdFunction returns a unique ID for the passed Message, and PubSub can be customized to use any
// implementation of this function by configuring it with the Option from WithMessageIdFn.
type MsgIdFunction func(pmsg *pb.Message) string
type MsgIdFunction func(pmsg *pb.Message) []byte
// WithMessageIdFn is an option to customize the way a message ID is computed for a pubsub message.
// The default ID function is DefaultMsgIdFn (concatenate source and seq nr.),
@ -495,14 +498,14 @@ func WithRawTracer(tracer RawTracer) Option {
}
// WithMaxMessageSize sets the global maximum message size for pubsub wire
// messages. The default value is 16.7MiB (DefaultMaxMessageSize).
// messages. The default value is 1MiB (DefaultMaxMessageSize).
//
// Observe the following warnings when setting this option.
//
// WARNING #1: Make sure to change the default protocol prefixes for floodsub
// (FloodSubID) and BlossomSub (BlossomSubID). This avoids accidentally joining
// the public default network, which uses the default max message size, and
// therefore will cause messages to be dropped.
// WARNING #1: Make sure to change the default protocol prefixes for BlossomSub
// (BlossomSubID). This avoids accidentally joining the public default network,
// which uses the default max message size, and therefore will cause messages to
// be dropped.
//
// WARNING #2: Reducing the default max message limit is fine, if you are
// certain that your application messages will not exceed the new limit.
@ -563,11 +566,13 @@ func WithAppSpecificRpcInspector(inspector func(peer.ID, *RPC) error) Option {
// processLoop handles all inputs arriving on the channels
func (p *PubSub) processLoop(ctx context.Context) {
defer func() {
p.peersMx.Lock()
// Clean up go routines.
for _, ch := range p.peers {
close(ch)
}
p.peers = nil
p.peersMx.Unlock()
p.bitmasks = nil
p.seenMessages.Done()
}()
@ -580,7 +585,9 @@ func (p *PubSub) processLoop(ctx context.Context) {
case s := <-p.newPeerStream:
pid := s.Conn().RemotePeer()
p.peersMx.RLock()
ch, ok := p.peers[pid]
p.peersMx.RUnlock()
if !ok {
log.Warn("new stream for unknown peer: ", pid)
s.Reset()
@ -590,7 +597,9 @@ func (p *PubSub) processLoop(ctx context.Context) {
if p.blacklist.Contains(pid) {
log.Warn("closing stream for blacklisted peer: ", pid)
close(ch)
p.peersMx.Lock()
delete(p.peers, pid)
p.peersMx.Unlock()
s.Reset()
continue
}
@ -598,7 +607,9 @@ func (p *PubSub) processLoop(ctx context.Context) {
p.rt.AddPeer(pid, s.Protocol())
case pid := <-p.newPeerError:
p.peersMx.Lock()
delete(p.peers, pid)
p.peersMx.Unlock()
case <-p.peerDead:
p.handleDeadPeers()
@ -622,22 +633,13 @@ func (p *PubSub) processLoop(ctx context.Context) {
case bitmask := <-p.rmRelay:
p.handleRemoveRelay([]byte(bitmask))
case preq := <-p.getPeers:
tmap, ok := p.bitmasks[string(preq.bitmask)]
if preq.bitmask != nil && !ok {
peers := p.getPeersInBitmask(preq.bitmask)
if len(peers) == 0 {
preq.resp <- nil
continue
} else {
preq.resp <- peers
}
var peers []peer.ID
for p := range p.peers {
if preq.bitmask != nil {
_, ok := tmap[p]
if !ok {
continue
}
}
peers = append(peers, p)
}
preq.resp <- peers
case rpc := <-p.incoming:
p.handleIncomingRPC(rpc)
@ -657,10 +659,14 @@ func (p *PubSub) processLoop(ctx context.Context) {
log.Infof("Blacklisting peer %s", pid)
p.blacklist.Add(pid)
p.peersMx.RLock()
ch, ok := p.peers[pid]
p.peersMx.RUnlock()
if ok {
close(ch)
p.peersMx.Lock()
delete(p.peers, pid)
p.peersMx.Unlock()
for t, tmap := range p.bitmasks {
if _, ok := tmap[pid]; ok {
delete(tmap, pid)
@ -677,6 +683,49 @@ func (p *PubSub) processLoop(ctx context.Context) {
}
}
func (p *PubSub) getPeersInBitmask(bitmask []byte) []peer.ID {
bitmaskSlices := SliceBitmask(bitmask)
var peers []peer.ID
peerloop:
for _, slice := range bitmaskSlices {
tmap, ok := p.bitmasks[string(slice)]
if !ok {
peers = []peer.ID{}
break peerloop
}
var peerset []peer.ID
p.peersMx.RLock()
for p := range p.peers {
_, ok := tmap[p]
if !ok {
continue
}
peerset = append(peerset, p)
}
p.peersMx.RUnlock()
if len(peers) == 0 {
peers = peerset
} else {
var update []peer.ID
for _, p := range peers {
if slices.Contains(peerset, p) {
update = append(update, p)
}
}
peers = update
if len(update) == 0 {
break peerloop
}
}
}
return peers
}
func (p *PubSub) handlePendingPeers() {
p.newPeersPrioLk.Lock()
@ -694,10 +743,13 @@ func (p *PubSub) handlePendingPeers() {
continue
}
p.peersMx.RLock()
if _, ok := p.peers[pid]; ok {
p.peersMx.RUnlock()
log.Debug("already have connection to peer: ", pid)
continue
}
p.peersMx.RUnlock()
if p.blacklist.Contains(pid) {
log.Warn("ignoring connection from blacklisted peer: ", pid)
@ -707,7 +759,9 @@ func (p *PubSub) handlePendingPeers() {
messages := make(chan *RPC, p.peerOutboundQueueSize)
messages <- p.getHelloPacket()
go p.handleNewPeer(p.ctx, pid, messages)
p.peersMx.Lock()
p.peers[pid] = messages
p.peersMx.Unlock()
}
}
@ -724,13 +778,17 @@ func (p *PubSub) handleDeadPeers() {
p.peerDeadPrioLk.Unlock()
for pid := range deadPeers {
p.peersMx.RLock()
ch, ok := p.peers[pid]
p.peersMx.RUnlock()
if !ok {
continue
}
close(ch)
p.peersMx.Lock()
delete(p.peers, pid)
p.peersMx.Unlock()
for t, tmap := range p.bitmasks {
if _, ok := tmap[pid]; ok {
@ -753,7 +811,9 @@ func (p *PubSub) handleDeadPeers() {
log.Debugf("peer declared dead but still connected; respawning writer: %s", pid)
messages := make(chan *RPC, p.peerOutboundQueueSize)
messages <- p.getHelloPacket()
p.peersMx.Lock()
p.peers[pid] = messages
p.peersMx.Unlock()
go p.handleNewPeerWithBackoff(p.ctx, pid, backoffDelay, messages)
}
}
@ -917,6 +977,7 @@ func (p *PubSub) announce(bitmask []byte, sub bool) {
}
out := rpcWithSubs(subopt)
p.peersMx.RLock()
for pid, peer := range p.peers {
select {
case peer <- out:
@ -927,6 +988,7 @@ func (p *PubSub) announce(bitmask []byte, sub bool) {
go p.announceRetry(pid, bitmask, sub)
}
}
p.peersMx.RUnlock()
}
func (p *PubSub) announceRetry(pid peer.ID, bitmask []byte, sub bool) {
@ -950,7 +1012,9 @@ func (p *PubSub) announceRetry(pid peer.ID, bitmask []byte, sub bool) {
}
func (p *PubSub) doAnnounceRetry(pid peer.ID, bitmask []byte, sub bool) {
p.peersMx.RLock()
peer, ok := p.peers[pid]
p.peersMx.RUnlock()
if !ok {
return
}
@ -975,14 +1039,13 @@ func (p *PubSub) doAnnounceRetry(pid peer.ID, bitmask []byte, sub bool) {
// Only called from processLoop.
func (p *PubSub) notifySubs(msg *Message) {
bitmask := msg.GetBitmask()
subs := p.mySubs[string(bitmask)]
slices := SliceBitmask(bitmask)
// o := rand.Intn(len(slices))
subs := p.mySubs[string(slices[0])]
for f := range subs {
select {
case f.ch <- msg:
case <-time.After(5 * time.Millisecond):
// it's unreasonable to immediately fall over because a subscriber didn't
// answer, message delivery sometimes lands next nanosecond and dropping
// it when there's room is absurd.
default:
p.tracer.UndeliverableMessage(msg)
log.Infof("Can't deliver message to subscription for bitmask %x; subscriber too slow", bitmask)
}
@ -990,14 +1053,14 @@ func (p *PubSub) notifySubs(msg *Message) {
}
// seenMessage returns whether we already saw this message before
func (p *PubSub) seenMessage(id string) bool {
return p.seenMessages.Has(id)
func (p *PubSub) seenMessage(id []byte) bool {
return p.seenMessages.Has(string(id))
}
// markSeen marks a message as seen such that seenMessage returns `true' for the given id
// returns true if the message was freshly marked
func (p *PubSub) markSeen(id string) bool {
return p.seenMessages.Add(id)
func (p *PubSub) markSeen(id []byte) bool {
return p.seenMessages.Add(string(id))
}
// subscribedToMessage returns whether we are subscribed to one of the bitmasks
@ -1008,9 +1071,15 @@ func (p *PubSub) subscribedToMsg(msg *pb.Message) bool {
}
bitmask := msg.GetBitmask()
_, ok := p.mySubs[string(bitmask)]
slices := SliceBitmask(bitmask)
for _, slice := range slices {
_, ok := p.mySubs[string(slice)]
if !ok {
return false
}
}
return ok
return true
}
// canRelayMsg returns whether we are able to relay for one of the bitmasks
@ -1021,9 +1090,15 @@ func (p *PubSub) canRelayMsg(msg *pb.Message) bool {
}
bitmask := msg.GetBitmask()
relays := p.myRelays[string(bitmask)]
slices := SliceBitmask(bitmask)
for _, slice := range slices {
relays := p.myRelays[string(slice)]
if relays > 0 {
return true
}
}
return relays > 0
return false
}
func (p *PubSub) notifyLeave(bitmask []byte, pid peer.ID) {
@ -1049,7 +1124,7 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) {
var err error
subs, err = p.subFilter.FilterIncomingSubscriptions(rpc.from, subs)
if err != nil {
log.Debugf("subscription filter error: %s; ignoring RPC", err)
log.Debugf("subscription filter error: %s; ignoring RPC\n", err)
return
}
}
@ -1103,7 +1178,7 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) {
continue
}
p.pushMsg(&Message{pmsg, "", rpc.from, nil, false})
p.pushMsg(&Message{pmsg, []byte{}, rpc.from, nil, false})
}
}
@ -1111,8 +1186,10 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) {
}
// DefaultMsgIdFn returns a unique ID of the passed Message
func DefaultMsgIdFn(pmsg *pb.Message) string {
return string(pmsg.GetFrom()) + string(pmsg.GetSeqno())
func DefaultMsgIdFn(pmsg *pb.Message) []byte {
h := sha256.New()
h.Write(pmsg.Data)
return h.Sum([]byte{0x01})
}
// DefaultPeerFilter accepts all peers on all bitmasks
@ -1233,59 +1310,75 @@ func (p *PubSub) PeerScore(pr peer.ID) float64 {
return p.rt.PeerScore(pr)
}
// Join joins the bitmask and returns a Bitmask handle. Only one Bitmask handle should exist per bitmask, and Join will error if
// the Bitmask handle already exists.
func (p *PubSub) Join(bitmask []byte, opts ...BitmaskOpt) (*Bitmask, error) {
t, ok, err := p.tryJoin(bitmask, opts...)
if err != nil {
return nil, err
// Join joins the bitmasks and returns a set of Bitmask handles. Only one Bitmask
// handle should exist per bit, and Join will error if all the Bitmask handles already exist.
func (p *PubSub) Join(bitmask []byte, opts ...BitmaskOpt) ([]*Bitmask, error) {
ts, news, errs := p.tryJoin(bitmask, opts...)
if len(errs) != 0 {
return nil, errors.Join(errs...)
}
if !ok {
if !slices.Contains(news, true) {
return nil, fmt.Errorf("bitmask already exists")
}
return t, nil
return ts, nil
}
// tryJoin is an internal function that tries to join a bitmask
// Returns the bitmask if it can be created or found
// Returns true if the bitmask was newly created, false otherwise
// Can be removed once pubsub.Publish() and pubsub.Subscribe() are removed
func (p *PubSub) tryJoin(bitmask []byte, opts ...BitmaskOpt) (*Bitmask, bool, error) {
func (p *PubSub) tryJoin(bitmask []byte, opts ...BitmaskOpt) ([]*Bitmask, []bool, []error) {
if p.subFilter != nil && !p.subFilter.CanSubscribe(bitmask) {
return nil, false, fmt.Errorf("bitmask is not allowed by the subscription filter")
return nil, nil, []error{fmt.Errorf("bitmask is not allowed by the subscription filter")}
}
t := &Bitmask{
p: p,
bitmask: bitmask,
evtHandlers: make(map[*BitmaskEventHandler]struct{}),
}
sliced := SliceBitmask(bitmask)
var bitmasks []*Bitmask
var newBitmasks []bool
var errors []error
for _, opt := range opts {
err := opt(t)
if err != nil {
return nil, false, err
loop:
for _, slice := range sliced {
slice := slice
t := &Bitmask{
p: p,
bitmask: slice,
evtHandlers: make(map[*BitmaskEventHandler]struct{}),
}
for _, opt := range opts {
err := opt(t)
if err != nil {
errors = append(errors, err)
continue loop
}
}
resp := make(chan *Bitmask, 1)
select {
case t.p.addBitmask <- &addBitmaskReq{
bitmask: t,
resp: resp,
}:
case <-t.p.ctx.Done():
errors = append(errors, t.p.ctx.Err())
continue loop
}
returnedBitmask := <-resp
if returnedBitmask != t {
bitmasks = append(bitmasks, returnedBitmask)
newBitmasks = append(newBitmasks, false)
} else {
bitmasks = append(bitmasks, t)
newBitmasks = append(newBitmasks, true)
}
}
resp := make(chan *Bitmask, 1)
select {
case t.p.addBitmask <- &addBitmaskReq{
bitmask: t,
resp: resp,
}:
case <-t.p.ctx.Done():
return nil, false, t.p.ctx.Err()
}
returnedBitmask := <-resp
if returnedBitmask != t {
return returnedBitmask, false, nil
}
return t, true, nil
return bitmasks, newBitmasks, errors
}
type addSubReq struct {
@ -1300,14 +1393,24 @@ type SubOpt func(sub *Subscription) error
// before the subscription is processed by the pubsub main loop and propagated to our peers.
//
// Deprecated: use pubsub.Join() and bitmask.Subscribe() instead
func (p *PubSub) Subscribe(bitmask []byte, opts ...SubOpt) (*Subscription, error) {
func (p *PubSub) Subscribe(bitmask []byte, opts ...SubOpt) ([]*Subscription, error) {
// ignore whether the bitmask was newly created or not, since either way we have a valid bitmask to work with
bitmaskHandle, _, err := p.tryJoin(bitmask)
if err != nil {
return nil, err
bitmaskHandles, _, errs := p.tryJoin(bitmask)
if len(errs) != 0 {
return nil, errors.Join(errs...)
}
return bitmaskHandle.Subscribe(opts...)
var subs []*Subscription
for _, handle := range bitmaskHandles {
sub, err := handle.Subscribe(opts...)
if err != nil {
return nil, err
}
subs = append(subs, sub)
}
return subs, nil
}
// WithBufferSize is a Subscribe option to customize the size of the subscribe output buffer.
@ -1335,17 +1438,20 @@ func (p *PubSub) GetBitmasks() []string {
return <-out
}
// Publish publishes data to the given bitmask.
//
// Deprecated: use pubsub.Join() and bitmask.Publish() instead
func (p *PubSub) Publish(bitmask []byte, data []byte, opts ...PubOpt) error {
// ignore whether the bitmask was newly created or not, since either way we have a valid bitmask to work with
t, _, err := p.tryJoin(bitmask)
if err != nil {
return err
func (p *PubSub) Publish(ctx context.Context, bitmask []byte, data []byte, opts ...PubOpt) error {
peers := p.ListPeers(bitmask)
if len(peers) == 0 {
return ErrBitmaskClosed
}
return t.Publish(context.TODO(), data, opts...)
slices := SliceBitmask(bitmask)
o := rand.Intn(len(slices))
b, _, errs := p.tryJoin(slices[o])
if len(errs) != 0 {
return errors.Join(errs...)
}
return b[0].Publish(ctx, bitmask, data, opts...)
}
func (p *PubSub) nextSeqno() []byte {
@ -1430,3 +1536,34 @@ type addRelayReq struct {
bitmask []byte
resp chan RelayCancelFunc
}
func SliceBitmask(bitmask []byte) [][]byte {
sliced := [][]byte{}
if bytes.Equal(bitmask, make([]byte, len(bitmask))) {
sliced = append(sliced, bitmask)
} else {
for i, b := range bitmask {
if b == 0 {
continue
}
// fast: one bit in byte
if b&(b-1) == 0 {
slice := make([]byte, len(bitmask))
slice[i] = b
sliced = append(sliced, slice)
continue
}
for j := 7; j >= 0; j-- {
if (b>>j)&1 == 1 {
slice := make([]byte, len(bitmask))
slice[i] = 1 << j
sliced = append(sliced, slice)
}
}
}
}
return sliced
}

View File

@ -2,48 +2,98 @@ package blossomsub
import (
"context"
"crypto/rand"
"testing"
"time"
"source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
)
// See https://source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/issues/426
func TestPubSubRemovesBlacklistedPeer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
hosts := getNetHosts(t, ctx, 2)
hosts := getDefaultHosts(t, 2)
bl := NewMapBlacklist()
psubs0 := getPubsub(ctx, hosts[0])
psubs1 := getPubsub(ctx, hosts[1], WithBlacklist(bl))
psubs0 := getBlossomSub(ctx, hosts[0])
psubs1 := getBlossomSub(ctx, hosts[1], WithBlacklist(bl))
connect(t, hosts[0], hosts[1])
// Bad peer is blacklisted after it has connected.
// Calling p.BlacklistPeer directly does the right thing but we should also clean
// up the peer if it has been added the the blacklist by another means.
bl.Add(hosts[0].ID())
_, err := psubs0.Subscribe([]byte{0x7e, 0x57})
bitmasks, err := psubs0.Join([]byte{0x01, 0x00})
if err != nil {
t.Fatal(err)
}
sub1, err := psubs1.Subscribe([]byte{0x7e, 0x57})
_, err = psubs0.Subscribe([]byte{0x01, 0x00})
if err != nil {
t.Fatal(err)
}
sub1, err := psubs1.Subscribe([]byte{0x01, 0x00})
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond * 100)
psubs0.Publish([]byte{0x7e, 0x57}, []byte("message"))
bitmasks[0].Publish(ctx, []byte{0x01, 0x00}, []byte("message"))
wctx, cancel2 := context.WithTimeout(ctx, 1*time.Second)
defer cancel2()
_, _ = sub1.Next(wctx)
_, _ = sub1[0].Next(wctx)
// Explicitly cancel context so PubSub cleans up peer channels.
// Issue 426 reports a panic due to a peer channel being closed twice.
cancel()
time.Sleep(time.Millisecond * 100)
}
func TestSliceBitmask(t *testing.T) {
fullVector := []byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}
partialVector := []byte{
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
outputs := SliceBitmask(fullVector)
if len(outputs) != 256 {
t.Fatalf("output length mismatch: %d, expected %d", len(outputs), 256)
}
outputs = SliceBitmask(partialVector)
if len(outputs) != 4 {
t.Fatalf("output length mismatch: %d, expected %d", len(outputs), 4)
}
}
func TestDefaultMsgIdFn(t *testing.T) {
for i := 0; i < 10; i++ {
data := make([]byte, 1024)
rand.Read(data)
// for v2, prepends 0x01
out := DefaultMsgIdFn(&pb.Message{
Data: data,
})
if len(out) != 33 {
t.Fatalf("length mismatch for msg id fn: %d, expected %d\n", len(out), 33)
}
if out[0] != 0x01 {
t.Fatalf("missing prefix byte for msg id fn: %x, expected %x\n", out[:1], []byte{0x01})
}
}
}

View File

@ -1,172 +0,0 @@
package blossomsub
import (
"context"
"math"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
const (
RandomSubID = protocol.ID("/randomsub/1.0.0")
)
var (
RandomSubD = 6
)
// NewRandomSub returns a new PubSub object using RandomSubRouter as the router.
func NewRandomSub(ctx context.Context, h host.Host, size int, opts ...Option) (*PubSub, error) {
rt := &RandomSubRouter{
size: size,
peers: make(map[peer.ID]protocol.ID),
}
return NewPubSub(ctx, h, rt, opts...)
}
// RandomSubRouter is a router that implements a random propagation strategy.
// For each message, it selects the square root of the network size peers, with a min of RandomSubD,
// and forwards the message to them.
type RandomSubRouter struct {
p *PubSub
peers map[peer.ID]protocol.ID
size int
tracer *pubsubTracer
}
func (rs *RandomSubRouter) Protocols() []protocol.ID {
return []protocol.ID{RandomSubID, FloodSubID}
}
func (rs *RandomSubRouter) Attach(p *PubSub) {
rs.p = p
rs.tracer = p.tracer
}
func (rs *RandomSubRouter) PeerScore(p peer.ID) float64 {
return rs.p.PeerScore(p)
}
func (rs *RandomSubRouter) AddPeer(p peer.ID, proto protocol.ID) {
rs.tracer.AddPeer(p, proto)
rs.peers[p] = proto
}
func (rs *RandomSubRouter) RemovePeer(p peer.ID) {
rs.tracer.RemovePeer(p)
delete(rs.peers, p)
}
func (rs *RandomSubRouter) EnoughPeers(bitmask []byte, suggested int) bool {
// check all peers in the bitmask
tmap, ok := rs.p.bitmasks[string(bitmask)]
if !ok {
return false
}
fsPeers := 0
rsPeers := 0
// count floodsub and randomsub peers
for p := range tmap {
switch rs.peers[p] {
case FloodSubID:
fsPeers++
case RandomSubID:
rsPeers++
}
}
if suggested == 0 {
suggested = RandomSubD
}
if fsPeers+rsPeers >= suggested {
return true
}
if rsPeers >= RandomSubD {
return true
}
return false
}
func (rs *RandomSubRouter) AcceptFrom(peer.ID) AcceptStatus {
return AcceptAll
}
func (rs *RandomSubRouter) HandleRPC(rpc *RPC) {}
func (rs *RandomSubRouter) Publish(msg *Message) {
from := msg.ReceivedFrom
tosend := make(map[peer.ID]struct{})
rspeers := make(map[peer.ID]struct{})
src := peer.ID(msg.GetFrom())
bitmask := msg.GetBitmask()
tmap, ok := rs.p.bitmasks[string(bitmask)]
if !ok {
return
}
for p := range tmap {
if p == from || p == src {
continue
}
if rs.peers[p] == FloodSubID {
tosend[p] = struct{}{}
} else {
rspeers[p] = struct{}{}
}
}
if len(rspeers) > RandomSubD {
target := RandomSubD
sqrt := int(math.Ceil(math.Sqrt(float64(rs.size))))
if sqrt > target {
target = sqrt
}
if target > len(rspeers) {
target = len(rspeers)
}
xpeers := peerMapToList(rspeers)
shufflePeers(xpeers)
xpeers = xpeers[:target]
for _, p := range xpeers {
tosend[p] = struct{}{}
}
} else {
for p := range rspeers {
tosend[p] = struct{}{}
}
}
out := rpcWithMessages(msg.Message)
for p := range tosend {
mch, ok := rs.p.peers[p]
if !ok {
continue
}
select {
case mch <- out:
rs.tracer.SendRPC(out, p)
default:
log.Infof("dropping message to peer %s: queue full", p)
rs.tracer.DropRPC(out, p)
}
}
}
func (rs *RandomSubRouter) Join(bitmask []byte) {
rs.tracer.Join(bitmask)
}
func (rs *RandomSubRouter) Leave(bitmask []byte) {
rs.tracer.Join(bitmask)
}

View File

@ -1,192 +0,0 @@
package blossomsub
import (
"context"
"fmt"
"testing"
"time"
"github.com/libp2p/go-libp2p/core/host"
)
func getRandomsub(ctx context.Context, h host.Host, size int, opts ...Option) *PubSub {
ps, err := NewRandomSub(ctx, h, size, opts...)
if err != nil {
panic(err)
}
return ps
}
func getRandomsubs(ctx context.Context, hs []host.Host, size int, opts ...Option) []*PubSub {
var psubs []*PubSub
for _, h := range hs {
psubs = append(psubs, getRandomsub(ctx, h, size, opts...))
}
return psubs
}
func tryReceive(sub *Subscription) *Message {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
m, err := sub.Next(ctx)
if err != nil {
return nil
} else {
return m
}
}
func TestRandomsubSmall(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 10)
psubs := getRandomsubs(ctx, hosts, 10)
connectAll(t, hosts)
var subs []*Subscription
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0x7e, 0x57})
if err != nil {
t.Fatal(err)
}
subs = append(subs, sub)
}
time.Sleep(time.Second)
count := 0
for i := 0; i < 10; i++ {
msg := []byte(fmt.Sprintf("message %d", i))
psubs[i].Publish([]byte{0x7e, 0x57}, msg)
for _, sub := range subs {
if tryReceive(sub) != nil {
count++
}
}
}
if count < 7*len(hosts) {
t.Fatalf("received too few messages; expected at least %d but got %d", 9*len(hosts), count)
}
}
func TestRandomsubBig(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 50)
psubs := getRandomsubs(ctx, hosts, 50)
connectSome(t, hosts, 12)
var subs []*Subscription
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0x7e, 0x57})
if err != nil {
t.Fatal(err)
}
subs = append(subs, sub)
}
time.Sleep(time.Second)
count := 0
for i := 0; i < 10; i++ {
msg := []byte(fmt.Sprintf("message %d", i))
psubs[i].Publish([]byte{0x7e, 0x57}, msg)
for _, sub := range subs {
if tryReceive(sub) != nil {
count++
}
}
}
if count < 7*len(hosts) {
t.Fatalf("received too few messages; expected at least %d but got %d", 9*len(hosts), count)
}
}
func TestRandomsubMixed(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 40)
fsubs := getPubsubs(ctx, hosts[:10])
rsubs := getRandomsubs(ctx, hosts[10:], 30)
psubs := append(fsubs, rsubs...)
connectSome(t, hosts, 12)
var subs []*Subscription
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0x7e, 0x57})
if err != nil {
t.Fatal(err)
}
subs = append(subs, sub)
}
time.Sleep(time.Second)
count := 0
for i := 0; i < 10; i++ {
msg := []byte(fmt.Sprintf("message %d", i))
psubs[i].Publish([]byte{0x7e, 0x57}, msg)
for _, sub := range subs {
if tryReceive(sub) != nil {
count++
}
}
}
if count < 7*len(hosts) {
t.Fatalf("received too few messages; expected at least %d but got %d", 9*len(hosts), count)
}
}
func TestRandomsubEnoughPeers(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 40)
fsubs := getPubsubs(ctx, hosts[:10])
rsubs := getRandomsubs(ctx, hosts[10:], 30)
psubs := append(fsubs, rsubs...)
connectSome(t, hosts, 12)
for _, ps := range psubs {
_, err := ps.Subscribe([]byte{0x7e, 0x57})
if err != nil {
t.Fatal(err)
}
}
time.Sleep(time.Second)
res := make(chan bool, 1)
rsubs[0].eval <- func() {
rs := rsubs[0].rt.(*RandomSubRouter)
res <- rs.EnoughPeers([]byte{0x7e, 0x57}, 0)
}
enough := <-res
if !enough {
t.Fatal("expected enough peers")
}
rsubs[0].eval <- func() {
rs := rsubs[0].rt.(*RandomSubRouter)
res <- rs.EnoughPeers([]byte{0x7e, 0x57}, 100)
}
enough = <-res
if !enough {
t.Fatal("expected enough peers")
}
}

View File

@ -105,7 +105,7 @@ type deliveryRecord struct {
}
type deliveryEntry struct {
id string
id []byte
expire time.Time
next *deliveryEntry
}
@ -200,12 +200,12 @@ func newPeerScore(params *PeerScoreParams) *peerScore {
// Note: assumes that the bitmask score parameters have already been validated
func (ps *peerScore) SetBitmaskScoreParams(bitmask []byte, p *BitmaskScoreParams) error {
ps.Lock()
defer ps.Unlock()
old, exist := ps.params.Bitmasks[string(bitmask)]
ps.params.Bitmasks[string(bitmask)] = p
if !exist {
ps.Unlock()
return nil
}
@ -218,6 +218,7 @@ func (ps *peerScore) SetBitmaskScoreParams(bitmask []byte, p *BitmaskScoreParams
recap = true
}
if !recap {
ps.Unlock()
return nil
}
@ -236,7 +237,7 @@ func (ps *peerScore) SetBitmaskScoreParams(bitmask []byte, p *BitmaskScoreParams
tstats.meshMessageDeliveries = p.MeshMessageDeliveriesCap
}
}
ps.Unlock()
return nil
}
@ -257,9 +258,10 @@ func (ps *peerScore) Score(p peer.ID) float64 {
}
ps.Lock()
defer ps.Unlock()
return ps.score(p)
score := ps.score(p)
ps.Unlock()
return score
}
func (ps *peerScore) score(p peer.ID) float64 {
@ -394,14 +396,15 @@ func (ps *peerScore) AddPenalty(p peer.ID, count int) {
}
ps.Lock()
defer ps.Unlock()
pstats, ok := ps.peerStats[p]
if !ok {
ps.Unlock()
return
}
pstats.behaviourPenalty += float64(count)
ps.Unlock()
}
// periodic maintenance
@ -503,7 +506,6 @@ func (ps *peerScore) inspectScoresExtended() {
// once their expiry has elapsed.
func (ps *peerScore) refreshScores() {
ps.Lock()
defer ps.Unlock()
now := time.Now()
for p, pstats := range ps.peerStats {
@ -562,12 +564,13 @@ func (ps *peerScore) refreshScores() {
pstats.behaviourPenalty = 0
}
}
ps.Unlock()
}
// refreshIPs refreshes IPs we know of peers we're tracking.
func (ps *peerScore) refreshIPs() {
ps.Lock()
defer ps.Unlock()
// peer IPs may change, so we periodically refresh them
//
@ -582,19 +585,20 @@ func (ps *peerScore) refreshIPs() {
pstats.ips = ips
}
}
ps.Unlock()
}
func (ps *peerScore) gcDeliveryRecords() {
ps.Lock()
defer ps.Unlock()
ps.deliveries.gc()
ps.Unlock()
}
// tracer interface
func (ps *peerScore) AddPeer(p peer.ID, proto protocol.ID) {
ps.Lock()
defer ps.Unlock()
pstats, ok := ps.peerStats[p]
if !ok {
@ -606,14 +610,15 @@ func (ps *peerScore) AddPeer(p peer.ID, proto protocol.ID) {
ips := ps.getIPs(p)
ps.setIPs(p, ips, pstats.ips)
pstats.ips = ips
ps.Unlock()
}
func (ps *peerScore) RemovePeer(p peer.ID) {
ps.Lock()
defer ps.Unlock()
pstats, ok := ps.peerStats[p]
if !ok {
ps.Unlock()
return
}
@ -622,6 +627,7 @@ func (ps *peerScore) RemovePeer(p peer.ID) {
if ps.score(p) > 0 {
ps.removeIPs(p, pstats.ips)
delete(ps.peerStats, p)
ps.Unlock()
return
}
@ -641,6 +647,7 @@ func (ps *peerScore) RemovePeer(p peer.ID) {
pstats.connected = false
pstats.expire = time.Now().Add(ps.params.RetainScore)
ps.Unlock()
}
func (ps *peerScore) Join(bitmask []byte) {}
@ -648,15 +655,16 @@ func (ps *peerScore) Leave(bitmask []byte) {}
func (ps *peerScore) Graft(p peer.ID, bitmask []byte) {
ps.Lock()
defer ps.Unlock()
pstats, ok := ps.peerStats[p]
if !ok {
ps.Unlock()
return
}
tstats, ok := pstats.getBitmaskStats(bitmask, ps.params)
if !ok {
ps.Unlock()
return
}
@ -664,19 +672,21 @@ func (ps *peerScore) Graft(p peer.ID, bitmask []byte) {
tstats.graftTime = time.Now()
tstats.meshTime = 0
tstats.meshMessageDeliveriesActive = false
ps.Unlock()
}
func (ps *peerScore) Prune(p peer.ID, bitmask []byte) {
ps.Lock()
defer ps.Unlock()
pstats, ok := ps.peerStats[p]
if !ok {
ps.Unlock()
return
}
tstats, ok := pstats.getBitmaskStats(bitmask, ps.params)
if !ok {
ps.Unlock()
return
}
@ -688,20 +698,20 @@ func (ps *peerScore) Prune(p peer.ID, bitmask []byte) {
}
tstats.inMesh = false
ps.Unlock()
}
func (ps *peerScore) ValidateMessage(msg *Message) {
ps.Lock()
defer ps.Unlock()
// the pubsub subsystem is beginning validation; create a record to track time in
// the validation pipeline with an accurate firstSeen time.
_ = ps.deliveries.getRecord(ps.idGen.ID(msg))
ps.Unlock()
}
func (ps *peerScore) DeliverMessage(msg *Message) {
ps.Lock()
defer ps.Unlock()
ps.markFirstMessageDelivery(msg.ReceivedFrom, msg)
@ -710,6 +720,7 @@ func (ps *peerScore) DeliverMessage(msg *Message) {
// defensive check that this is the first delivery trace -- delivery status should be unknown
if drec.status != deliveryUnknown {
log.Debugf("unexpected delivery trace: message from %s was first seen %s ago and has delivery status %d", msg.ReceivedFrom, time.Since(drec.firstSeen), drec.status)
ps.Unlock()
return
}
@ -723,11 +734,11 @@ func (ps *peerScore) DeliverMessage(msg *Message) {
ps.markDuplicateMessageDelivery(p, msg, time.Time{})
}
}
ps.Unlock()
}
func (ps *peerScore) RejectMessage(msg *Message, reason string) {
ps.Lock()
defer ps.Unlock()
switch reason {
// we don't track those messages, but we penalize the peer as they are clearly invalid
@ -741,18 +752,21 @@ func (ps *peerScore) RejectMessage(msg *Message, reason string) {
fallthrough
case RejectSelfOrigin:
ps.markInvalidMessageDelivery(msg.ReceivedFrom, msg)
ps.Unlock()
return
// we ignore those messages, so do nothing.
case RejectBlacklstedPeer:
fallthrough
case RejectBlacklistedSource:
ps.Unlock()
return
case RejectValidationQueueFull:
// the message was rejected before it entered the validation pipeline;
// we don't know if this message has a valid signature, and thus we also don't know if
// it has a valid message ID; all we can do is ignore it.
ps.Unlock()
return
}
@ -761,6 +775,7 @@ func (ps *peerScore) RejectMessage(msg *Message, reason string) {
// defensive check that this is the first rejection trace -- delivery status should be unknown
if drec.status != deliveryUnknown {
log.Debugf("unexpected rejection trace: message from %s was first seen %s ago and has delivery status %d", msg.ReceivedFrom, time.Since(drec.firstSeen), drec.status)
ps.Unlock()
return
}
@ -771,12 +786,14 @@ func (ps *peerScore) RejectMessage(msg *Message, reason string) {
drec.status = deliveryThrottled
// release the delivery time tracking map to free some memory early
drec.peers = nil
ps.Unlock()
return
case RejectValidationIgnored:
// we were explicitly instructed by the validator to ignore the message but not penalize
// the peer
drec.status = deliveryIgnored
drec.peers = nil
ps.Unlock()
return
}
@ -790,17 +807,18 @@ func (ps *peerScore) RejectMessage(msg *Message, reason string) {
// release the delivery time tracking map to free some memory early
drec.peers = nil
ps.Unlock()
}
func (ps *peerScore) DuplicateMessage(msg *Message) {
ps.Lock()
defer ps.Unlock()
drec := ps.deliveries.getRecord(ps.idGen.ID(msg))
_, ok := drec.peers[msg.ReceivedFrom]
if ok {
// we have already seen this duplicate!
ps.Unlock()
return
}
@ -824,6 +842,7 @@ func (ps *peerScore) DuplicateMessage(msg *Message) {
case deliveryIgnored:
// the message was ignored; do nothing
}
ps.Unlock()
}
func (ps *peerScore) ThrottlePeer(p peer.ID) {}
@ -837,8 +856,8 @@ func (ps *peerScore) DropRPC(rpc *RPC, p peer.ID) {}
func (ps *peerScore) UndeliverableMessage(msg *Message) {}
// message delivery records
func (d *messageDeliveries) getRecord(id string) *deliveryRecord {
rec, ok := d.records[id]
func (d *messageDeliveries) getRecord(id []byte) *deliveryRecord {
rec, ok := d.records[string(id)]
if ok {
return rec
}
@ -846,7 +865,7 @@ func (d *messageDeliveries) getRecord(id string) *deliveryRecord {
now := time.Now()
rec = &deliveryRecord{peers: make(map[peer.ID]struct{}), firstSeen: now}
d.records[id] = rec
d.records[string(id)] = rec
entry := &deliveryEntry{id: id, expire: now.Add(d.seenMsgTTL)}
if d.tail != nil {
@ -867,7 +886,7 @@ func (d *messageDeliveries) gc() {
now := time.Now()
for d.head != nil && now.After(d.head.expire) {
delete(d.records, d.head.id)
delete(d.records, string(d.head.id))
d.head = d.head.next
}

View File

@ -3,6 +3,7 @@ package blossomsub
import (
"fmt"
"google.golang.org/protobuf/proto"
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"github.com/libp2p/go-libp2p/core/crypto"
@ -26,10 +27,8 @@ const (
// StrictNoSign does not produce signatures and drops and penalises incoming messages that carry one
StrictNoSign = msgVerification
// LaxSign produces signatures and validates incoming signatures iff one is present
// Deprecated: it is recommend to either strictly enable, or strictly disable, signatures.
LaxSign = msgSigning
// LaxNoSign does not produce signatures and validates incoming signatures iff one is present
// Deprecated: it is recommend to either strictly enable, or strictly disable, signatures.
LaxNoSign = 0
)
@ -52,7 +51,7 @@ func verifyMessageSignature(m *pb.Message) error {
return err
}
xm := *m
xm := (proto.Clone(m)).(*pb.Message)
xm.Signature = nil
xm.Key = nil
bytes, err := xm.Marshal()

View File

@ -11,12 +11,25 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
)
func mustSubscribe(t *testing.T, ps *PubSub, bitmask []byte) *Subscription {
sub, err := ps.Subscribe(bitmask)
if err != nil {
t.Fatal(err)
}
if len(sub) != 1 {
t.Fatal("must subscribe only allows single bit bitmasks")
}
return sub[0]
}
func TestBasicSubscriptionFilter(t *testing.T) {
peerA := peer.ID("A")
bitmask1 := []byte{0xff, 0x00, 0x00, 0x00}
bitmask2 := []byte{0x00, 0xff, 0x00, 0x00}
bitmask3 := []byte{0x00, 0x00, 0xff, 0x00}
bitmask1 := []byte{0x00, 0x80, 0x00, 0x00}
bitmask2 := []byte{0x00, 0x20, 0x00, 0x00}
bitmask3 := []byte{0x00, 0x00, 0x02, 0x00}
yes := true
subs := []*pb.RPC_SubOpts{
&pb.RPC_SubOpts{
@ -69,9 +82,9 @@ func TestBasicSubscriptionFilter(t *testing.T) {
func TestSubscriptionFilterDeduplication(t *testing.T) {
peerA := peer.ID("A")
bitmask1 := []byte{0xff, 0x00, 0x00, 0x00}
bitmask2 := []byte{0x00, 0xff, 0x00, 0x00}
bitmask3 := []byte{0x00, 0x00, 0xff, 0x00}
bitmask1 := []byte{0x00, 0x80, 0x00, 0x00}
bitmask2 := []byte{0x00, 0x20, 0x00, 0x00}
bitmask3 := []byte{0x00, 0x00, 0x02, 0x00}
yes := true
no := false
subs := []*pb.RPC_SubOpts{
@ -117,17 +130,17 @@ func TestSubscriptionFilterRPC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 2)
ps1 := getPubsub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0xff, 0x00, 0x00, 0x00}, []byte{0x00, 0xff, 0x00, 0x00})))
ps2 := getPubsub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0x00, 0xff, 0x00, 0x00}, []byte{0x00, 0x00, 0xff, 0x00})))
hosts := getDefaultHosts(t, 2)
ps1 := getBlossomSub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0x00, 0x80, 0x00, 0x00}, []byte{0x00, 0x20, 0x00, 0x00})))
ps2 := getBlossomSub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0x00, 0x20, 0x00, 0x00}, []byte{0x00, 0x00, 0x02, 0x00})))
_ = mustSubscribe(t, ps1, []byte{0xff, 0x00, 0x00, 0x00})
_ = mustSubscribe(t, ps1, []byte{0x00, 0xff, 0x00, 0x00})
_ = mustSubscribe(t, ps2, []byte{0x00, 0xff, 0x00, 0x00})
_ = mustSubscribe(t, ps2, []byte{0x00, 0x00, 0xff, 0x00})
_ = mustSubscribe(t, ps1, []byte{0x00, 0x80, 0x00, 0x00})
_ = mustSubscribe(t, ps1, []byte{0x00, 0x20, 0x00, 0x00})
_ = mustSubscribe(t, ps2, []byte{0x00, 0x20, 0x00, 0x00})
_ = mustSubscribe(t, ps2, []byte{0x00, 0x00, 0x02, 0x00})
// check the rejection as well
_, err := ps1.Join([]byte{0x00, 0x00, 0xff, 0x00})
_, err := ps1.Join([]byte{0x00, 0x00, 0x02, 0x00})
if err == nil {
t.Fatal("expected subscription error")
}
@ -140,9 +153,9 @@ func TestSubscriptionFilterRPC(t *testing.T) {
ready := make(chan struct{})
ps1.eval <- func() {
_, sub1 = ps1.bitmasks[string([]byte{0xff, 0x00, 0x00, 0x00})][hosts[1].ID()]
_, sub2 = ps1.bitmasks[string([]byte{0x00, 0xff, 0x00, 0x00})][hosts[1].ID()]
_, sub3 = ps1.bitmasks[string([]byte{0x00, 0x00, 0xff, 0x00})][hosts[1].ID()]
_, sub1 = ps1.bitmasks[string([]byte{0x00, 0x80, 0x00, 0x00})][hosts[1].ID()]
_, sub2 = ps1.bitmasks[string([]byte{0x00, 0x20, 0x00, 0x00})][hosts[1].ID()]
_, sub3 = ps1.bitmasks[string([]byte{0x00, 0x00, 0x02, 0x00})][hosts[1].ID()]
ready <- struct{}{}
}
<-ready
@ -158,9 +171,9 @@ func TestSubscriptionFilterRPC(t *testing.T) {
}
ps2.eval <- func() {
_, sub1 = ps2.bitmasks[string([]byte{0xff, 0x00, 0x00, 0x00})][hosts[0].ID()]
_, sub2 = ps2.bitmasks[string([]byte{0x00, 0xff, 0x00, 0x00})][hosts[0].ID()]
_, sub3 = ps2.bitmasks[string([]byte{0x00, 0x00, 0xff, 0x00})][hosts[0].ID()]
_, sub1 = ps2.bitmasks[string([]byte{0x00, 0x80, 0x00, 0x00})][hosts[0].ID()]
_, sub2 = ps2.bitmasks[string([]byte{0x00, 0x20, 0x00, 0x00})][hosts[0].ID()]
_, sub3 = ps2.bitmasks[string([]byte{0x00, 0x00, 0x02, 0x00})][hosts[0].ID()]
ready <- struct{}{}
}
<-ready

View File

@ -109,9 +109,9 @@ func (t *tagTracer) addDeliveryTag(bitmask []byte) {
return
}
name := fmt.Sprintf("pubsub-deliveries:%s", bitmask)
name := "pubsub-deliveries:" + string(bitmask)
t.Lock()
defer t.Unlock()
tag, err := t.decayer.RegisterDecayingTag(
name,
BlossomSubConnTagDecayInterval,
@ -120,16 +120,19 @@ func (t *tagTracer) addDeliveryTag(bitmask []byte) {
if err != nil {
log.Warnf("unable to create decaying delivery tag: %s", err)
t.Unlock()
return
}
t.decaying[string(bitmask)] = tag
t.Unlock()
}
func (t *tagTracer) removeDeliveryTag(bitmask []byte) {
t.Lock()
defer t.Unlock()
tag, ok := t.decaying[string(bitmask)]
if !ok {
t.Unlock()
return
}
err := tag.Close()
@ -137,17 +140,20 @@ func (t *tagTracer) removeDeliveryTag(bitmask []byte) {
log.Warnf("error closing decaying connmgr tag: %s", err)
}
delete(t.decaying, string(bitmask))
t.Unlock()
}
func (t *tagTracer) bumpDeliveryTag(p peer.ID, bitmask []byte) error {
t.RLock()
defer t.RUnlock()
tag, ok := t.decaying[string(bitmask)]
if !ok {
t.RUnlock()
return fmt.Errorf("no decaying tag registered for bitmask %s", bitmask)
}
return tag.Bump(p, BlossomSubConnTagBumpMessageDelivery)
err := tag.Bump(p, BlossomSubConnTagBumpMessageDelivery)
t.RUnlock()
return err
}
func (t *tagTracer) bumpTagsForMessage(p peer.ID, msg *Message) {
@ -161,15 +167,17 @@ func (t *tagTracer) bumpTagsForMessage(p peer.ID, msg *Message) {
// nearFirstPeers returns the peers who delivered the message while it was still validating
func (t *tagTracer) nearFirstPeers(msg *Message) []peer.ID {
t.Lock()
defer t.Unlock()
peersMap, ok := t.nearFirst[t.idGen.ID(msg)]
peersMap, ok := t.nearFirst[string(t.idGen.ID(msg))]
if !ok {
t.Unlock()
return nil
}
peers := make([]peer.ID, 0, len(peersMap))
for p := range peersMap {
peers = append(peers, p)
}
t.Unlock()
return peers
}
@ -194,7 +202,7 @@ func (t *tagTracer) DeliverMessage(msg *Message) {
// delete the delivery state for this message
t.Lock()
delete(t.nearFirst, t.idGen.ID(msg))
delete(t.nearFirst, string(t.idGen.ID(msg)))
t.Unlock()
}
@ -212,31 +220,32 @@ func (t *tagTracer) Prune(p peer.ID, bitmask []byte) {
func (t *tagTracer) ValidateMessage(msg *Message) {
t.Lock()
defer t.Unlock()
// create map to start tracking the peers who deliver while we're validating
id := t.idGen.ID(msg)
if _, exists := t.nearFirst[id]; exists {
if _, exists := t.nearFirst[string(id)]; exists {
t.Unlock()
return
}
t.nearFirst[id] = make(map[peer.ID]struct{})
t.nearFirst[string(id)] = make(map[peer.ID]struct{})
t.Unlock()
}
func (t *tagTracer) DuplicateMessage(msg *Message) {
t.Lock()
defer t.Unlock()
id := t.idGen.ID(msg)
peers, ok := t.nearFirst[id]
peers, ok := t.nearFirst[string(id)]
if !ok {
t.Unlock()
return
}
peers[msg.ReceivedFrom] = struct{}{}
t.Unlock()
}
func (t *tagTracer) RejectMessage(msg *Message, reason string) {
t.Lock()
defer t.Unlock()
// We want to delete the near-first delivery tracking for messages that have passed through
// the validation pipeline. Other rejection reasons (missing signature, etc) skip the validation
@ -247,8 +256,9 @@ func (t *tagTracer) RejectMessage(msg *Message, reason string) {
case RejectValidationIgnored:
fallthrough
case RejectValidationFailed:
delete(t.nearFirst, t.idGen.ID(msg))
delete(t.nearFirst, string(t.idGen.ID(msg)))
}
t.Unlock()
}
func (t *tagTracer) RemovePeer(peer.ID) {}

View File

@ -55,9 +55,9 @@ func TestTagTracerDirectPeerTags(t *testing.T) {
tt.direct = make(map[peer.ID]struct{})
tt.direct[p1] = struct{}{}
tt.AddPeer(p1, BlossomSubID_v11)
tt.AddPeer(p2, BlossomSubID_v11)
tt.AddPeer(p3, BlossomSubID_v11)
tt.AddPeer(p1, BlossomSubID_v2)
tt.AddPeer(p2, BlossomSubID_v2)
tt.AddPeer(p3, BlossomSubID_v2)
tag := "pubsub:<direct>"
if !cmgr.IsProtected(p1, tag) {
@ -179,7 +179,7 @@ func TestTagTracerDeliveryTagsNearFirst(t *testing.T) {
tt := newTagTracer(cmgr)
bitmask := []byte{0x7e, 0x57}
bitmask := []byte{0x01, 0x00}
p := peer.ID("a-peer")
p2 := peer.ID("another-peer")

View File

@ -36,21 +36,22 @@ func (tc *FirstSeenCache) Done() {
func (tc *FirstSeenCache) Has(s string) bool {
tc.lk.RLock()
defer tc.lk.RUnlock()
_, ok := tc.m[s]
tc.lk.RUnlock()
return ok
}
func (tc *FirstSeenCache) Add(s string) bool {
tc.lk.Lock()
defer tc.lk.Unlock()
_, ok := tc.m[s]
if ok {
tc.lk.Unlock()
return false
}
tc.m[s] = time.Now().Add(tc.ttl)
tc.lk.Unlock()
return true
}

View File

@ -37,22 +37,20 @@ func (tc *LastSeenCache) Done() {
func (tc *LastSeenCache) Add(s string) bool {
tc.lk.Lock()
defer tc.lk.Unlock()
_, ok := tc.m[s]
tc.m[s] = time.Now().Add(tc.ttl)
tc.lk.Unlock()
return !ok
}
func (tc *LastSeenCache) Has(s string) bool {
tc.lk.Lock()
defer tc.lk.Unlock()
_, ok := tc.m[s]
if ok {
tc.m[s] = time.Now().Add(tc.ttl)
}
tc.lk.Unlock()
return ok
}

View File

@ -10,7 +10,6 @@ var backgroundSweepInterval = time.Minute
func background(ctx context.Context, lk sync.Locker, m map[string]time.Time) {
ticker := time.NewTicker(backgroundSweepInterval)
defer ticker.Stop()
for {
select {
@ -18,6 +17,7 @@ func background(ctx context.Context, lk sync.Locker, m map[string]time.Time) {
sweep(lk, m, now)
case <-ctx.Done():
ticker.Stop()
return
}
}
@ -25,11 +25,12 @@ func background(ctx context.Context, lk sync.Locker, m map[string]time.Time) {
func sweep(lk sync.Locker, m map[string]time.Time, now time.Time) {
lk.Lock()
defer lk.Unlock()
for k, expiry := range m {
if expiry.Before(now) {
delete(m, k)
}
}
lk.Unlock()
}

View File

@ -148,19 +148,19 @@ func (t *pubsubTracer) DuplicateMessage(msg *Message) {
return
}
now := time.Now().UnixNano()
evt := &pb.TraceEvent{
Type: pb.TraceEvent_DUPLICATE_MESSAGE.Enum(),
PeerID: []byte(t.pid),
Timestamp: &now,
DuplicateMessage: &pb.TraceEvent_DuplicateMessage{
MessageID: []byte(t.idGen.ID(msg)),
ReceivedFrom: []byte(msg.ReceivedFrom),
Bitmask: msg.Bitmask,
},
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_DUPLICATE_MESSAGE.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// DuplicateMessage: &pb.TraceEvent_DuplicateMessage{
// MessageID: []byte(t.idGen.ID(msg)),
// ReceivedFrom: []byte(msg.ReceivedFrom),
// Bitmask: msg.Bitmask,
// },
// }
t.tracer.Trace(evt)
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) DeliverMessage(msg *Message) {
@ -178,19 +178,19 @@ func (t *pubsubTracer) DeliverMessage(msg *Message) {
return
}
now := time.Now().UnixNano()
evt := &pb.TraceEvent{
Type: pb.TraceEvent_DELIVER_MESSAGE.Enum(),
PeerID: []byte(t.pid),
Timestamp: &now,
DeliverMessage: &pb.TraceEvent_DeliverMessage{
MessageID: []byte(t.idGen.ID(msg)),
Bitmask: msg.Bitmask,
ReceivedFrom: []byte(msg.ReceivedFrom),
},
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_DELIVER_MESSAGE.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// DeliverMessage: &pb.TraceEvent_DeliverMessage{
// MessageID: []byte(t.idGen.ID(msg)),
// Bitmask: msg.Bitmask,
// ReceivedFrom: []byte(msg.ReceivedFrom),
// },
// }
t.tracer.Trace(evt)
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) AddPeer(p peer.ID, proto protocol.ID) {
@ -260,18 +260,18 @@ func (t *pubsubTracer) RecvRPC(rpc *RPC) {
return
}
now := time.Now().UnixNano()
evt := &pb.TraceEvent{
Type: pb.TraceEvent_RECV_RPC.Enum(),
PeerID: []byte(t.pid),
Timestamp: &now,
RecvRPC: &pb.TraceEvent_RecvRPC{
ReceivedFrom: []byte(rpc.from),
Meta: t.traceRPCMeta(rpc),
},
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_RECV_RPC.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// RecvRPC: &pb.TraceEvent_RecvRPC{
// ReceivedFrom: []byte(rpc.from),
// Meta: t.traceRPCMeta(rpc),
// },
// }
t.tracer.Trace(evt)
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) SendRPC(rpc *RPC, p peer.ID) {
@ -287,18 +287,18 @@ func (t *pubsubTracer) SendRPC(rpc *RPC, p peer.ID) {
return
}
now := time.Now().UnixNano()
evt := &pb.TraceEvent{
Type: pb.TraceEvent_SEND_RPC.Enum(),
PeerID: []byte(t.pid),
Timestamp: &now,
SendRPC: &pb.TraceEvent_SendRPC{
SendTo: []byte(p),
Meta: t.traceRPCMeta(rpc),
},
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_SEND_RPC.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// SendRPC: &pb.TraceEvent_SendRPC{
// SendTo: []byte(p),
// Meta: t.traceRPCMeta(rpc),
// },
// }
t.tracer.Trace(evt)
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) DropRPC(rpc *RPC, p peer.ID) {
@ -314,18 +314,18 @@ func (t *pubsubTracer) DropRPC(rpc *RPC, p peer.ID) {
return
}
now := time.Now().UnixNano()
evt := &pb.TraceEvent{
Type: pb.TraceEvent_DROP_RPC.Enum(),
PeerID: []byte(t.pid),
Timestamp: &now,
DropRPC: &pb.TraceEvent_DropRPC{
SendTo: []byte(p),
Meta: t.traceRPCMeta(rpc),
},
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_DROP_RPC.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// DropRPC: &pb.TraceEvent_DropRPC{
// SendTo: []byte(p),
// Meta: t.traceRPCMeta(rpc),
// },
// }
t.tracer.Trace(evt)
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) UndeliverableMessage(msg *Message) {
@ -336,6 +336,24 @@ func (t *pubsubTracer) UndeliverableMessage(msg *Message) {
for _, tr := range t.raw {
tr.UndeliverableMessage(msg)
}
if t.tracer == nil {
return
}
// now := time.Now().UnixNano()
// evt := &pb.TraceEvent{
// Type: pb.TraceEvent_UNDELIVERABLE_MESSAGE.Enum(),
// PeerID: []byte(t.pid),
// Timestamp: &now,
// UndeliverableMessage: &pb.TraceEvent_UndeliverableMessage{
// MessageID: []byte(t.idGen.ID(msg)),
// Bitmask: msg.Bitmask,
// ReceivedFrom: []byte(msg.ReceivedFrom),
// },
// }
// t.tracer.Trace(evt)
}
func (t *pubsubTracer) traceRPCMeta(rpc *RPC) *pb.TraceEvent_RPCMeta {

View File

@ -11,24 +11,22 @@ import (
"testing"
"time"
"google.golang.org/protobuf/proto"
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/libp2p/go-msgio/protoio"
"github.com/libp2p/go-msgio"
)
func testWithTracer(t *testing.T, tracer EventTracer) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 20)
hosts := getDefaultHosts(t, 20)
psubs := getBlossomSubs(ctx, hosts,
WithMessageIdFn(func(pmsg *pb.Message) []byte { return pmsg.Data }),
WithEventTracer(tracer),
// to bootstrap from star topology
WithPeerExchange(true),
@ -49,7 +47,7 @@ func testWithTracer(t *testing.T, tracer EventTracer) {
// add a validator that rejects some messages to exercise those code paths in the tracer
for _, ps := range psubs {
ps.RegisterBitmaskValidator([]byte{0x7e, 57}, func(ctx context.Context, p peer.ID, msg *Message) bool {
ps.RegisterBitmaskValidator([]byte{0x01, 0x00}, func(ctx context.Context, p peer.ID, msg *Message) bool {
if string(msg.Data) == "invalid!" {
return false
} else {
@ -79,8 +77,14 @@ func testWithTracer(t *testing.T, tracer EventTracer) {
// build the mesh
var subs []*Subscription
var bitmasks []*Bitmask
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0x7e, 0x57})
b, err := ps.Join([]byte{0x01, 0x00})
if err != nil {
t.Fatal(err)
}
bitmasks = append(bitmasks, b...)
sub, err := ps.Subscribe([]byte{0x01, 0x00})
if err != nil {
t.Fatal(err)
}
@ -91,8 +95,8 @@ func testWithTracer(t *testing.T, tracer EventTracer) {
return
}
}
}(sub)
subs = append(subs, sub)
}(sub[0])
subs = append(subs, sub...)
}
// wait for the mesh to build
@ -101,10 +105,14 @@ func testWithTracer(t *testing.T, tracer EventTracer) {
// publish some messages
for i := 0; i < 20; i++ {
if i%7 == 0 {
psubs[i].Publish([]byte{0x7e, 0x57}, []byte("invalid!"))
bitmasks[i].Publish(ctx, bitmasks[i].bitmask, []byte("invalid!"))
} else {
msg := []byte(fmt.Sprintf("message %d", i))
psubs[i].Publish([]byte{0x7e, 0x57}, msg)
if i%9 == 0 {
bitmasks[i].Publish(ctx, bitmasks[i].bitmask, []byte("dupe"))
} else {
msg := []byte(fmt.Sprintf("message %d", i))
bitmasks[i].Publish(ctx, bitmasks[i].bitmask, msg)
}
}
}
@ -125,7 +133,6 @@ type traceStats struct {
}
func (t *traceStats) process(evt *pb.TraceEvent) {
// fmt.Printf("process event %s\n", evt.GetType())
switch evt.GetType() {
case pb.TraceEvent_PUBLISH_MESSAGE:
t.publish++
@ -244,10 +251,16 @@ func TestPBTracer(t *testing.T) {
}
defer f.Close()
r := protoio.NewDelimitedReader(f, 1<<20)
r := msgio.NewVarintReaderSize(f, DefaultMaxMessageSize)
for {
evt.Reset()
err := r.ReadMsg(&evt)
v, err := r.ReadMsg()
if err != nil {
break
}
err = proto.Unmarshal(v, &evt)
if err != nil {
break
}
@ -271,12 +284,13 @@ func (mrt *mockRemoteTracer) handleStream(s network.Stream) {
panic(err)
}
r := protoio.NewDelimitedReader(gzr, 1<<24)
r := msgio.NewVarintReader(gzr)
var batch pb.TraceEventBatch
for {
batch.Reset()
err := r.ReadMsg(&batch)
v, err := r.ReadMsg()
if err != nil {
if err != io.EOF {
s.Reset()
@ -284,6 +298,11 @@ func (mrt *mockRemoteTracer) handleStream(s network.Stream) {
return
}
err = proto.Unmarshal(v, &batch)
if err != nil {
break
}
mrt.mx.Lock()
for _, evt := range batch.GetBatch() {
mrt.ts.process(evt)
@ -302,10 +321,9 @@ func TestRemoteTracer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
h1 := bhost.NewBlankHost(swarmt.GenSwarm(t))
h2 := bhost.NewBlankHost(swarmt.GenSwarm(t))
defer h1.Close()
defer h2.Close()
hosts := getDefaultHosts(t, 2)
h1 := hosts[0]
h2 := hosts[1]
mrt := &mockRemoteTracer{}
h1.SetStreamHandler(RemoteTracerProtoID, mrt.handleStream)

View File

@ -9,6 +9,7 @@ import (
"sync"
"time"
"google.golang.org/protobuf/proto"
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
"github.com/libp2p/go-libp2p/core/host"
@ -16,8 +17,7 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-msgio/protoio"
"github.com/libp2p/go-msgio"
)
var TraceBufferSize = 1 << 16 // 64K ought to be enough for everyone; famous last words.
@ -48,9 +48,9 @@ type basicTracer struct {
func (t *basicTracer) Trace(evt *pb.TraceEvent) {
t.mx.Lock()
defer t.mx.Unlock()
if t.closed {
t.mx.Unlock()
return
}
@ -59,6 +59,7 @@ func (t *basicTracer) Trace(evt *pb.TraceEvent) {
} else {
t.buf = append(t.buf, evt)
}
t.mx.Unlock()
select {
case t.ch <- struct{}{}:
@ -68,11 +69,11 @@ func (t *basicTracer) Trace(evt *pb.TraceEvent) {
func (t *basicTracer) Close() {
t.mx.Lock()
defer t.mx.Unlock()
if !t.closed {
t.closed = true
close(t.ch)
}
t.mx.Unlock()
}
// JSONTracer is a tracer that writes events to a file, encoded in ndjson.
@ -160,7 +161,6 @@ func OpenPBTracer(file string, flags int, perm os.FileMode) (*PBTracer, error) {
func (t *PBTracer) doWrite() {
var buf []*pb.TraceEvent
w := protoio.NewDelimitedWriter(t.w)
for {
_, ok := <-t.ch
@ -170,11 +170,20 @@ func (t *PBTracer) doWrite() {
buf = tmp
t.mx.Unlock()
w := msgio.NewVarintWriter(t.w)
for i, evt := range buf {
err := w.WriteMsg(evt)
out, err := proto.Marshal(evt)
if err != nil {
log.Warnf("error writing event trace: %s", err.Error())
continue
}
err = w.WriteMsg(out)
if err != nil {
log.Warnf("error writing event trace: %s", err.Error())
}
buf[i] = nil
}
@ -187,7 +196,7 @@ func (t *PBTracer) doWrite() {
var _ EventTracer = (*PBTracer)(nil)
const RemoteTracerProtoID = protocol.ID("/libp2p/pubsub/tracer/1.0.0")
const RemoteTracerProtoID = protocol.ID("/libp2p/pubsub/tracer/2.0.0")
// RemoteTracer is a tracer that sends trace events to a remote peer
type RemoteTracer struct {
@ -217,7 +226,7 @@ func (t *RemoteTracer) doWrite() {
var batch pb.TraceEventBatch
gzipW := gzip.NewWriter(s)
w := protoio.NewDelimitedWriter(gzipW)
w := msgio.NewVarintWriter(gzipW)
for {
_, ok := <-t.ch
@ -235,6 +244,7 @@ func (t *RemoteTracer) doWrite() {
tmp := t.buf
t.buf = buf[:0]
buf = tmp
var out []byte
t.mx.Unlock()
if len(buf) == 0 {
@ -242,8 +252,13 @@ func (t *RemoteTracer) doWrite() {
}
batch.Batch = buf
out, err = proto.Marshal(&batch)
if err != nil {
log.Debugf("error marshaling trace event batch: %s", err)
goto end
}
err = w.WriteMsg(&batch)
err = w.WriteMsg(out)
if err != nil {
log.Debugf("error writing trace event batch: %s", err)
goto end
@ -251,7 +266,7 @@ func (t *RemoteTracer) doWrite() {
err = gzipW.Flush()
if err != nil {
log.Debugf("error flushin gzip stream: %s", err)
log.Debugf("error flushing gzip stream: %s", err)
goto end
}

View File

@ -146,17 +146,18 @@ func (v *validation) AddValidator(req *addValReq) {
}
v.mx.Lock()
defer v.mx.Unlock()
bitmask := val.bitmask
_, ok := v.bitmaskVals[string(bitmask)]
if ok {
v.mx.Unlock()
req.resp <- fmt.Errorf("duplicate validator for bitmask %s", bitmask)
return
}
v.bitmaskVals[string(bitmask)] = val
v.mx.Unlock()
req.resp <- nil
}
@ -213,15 +214,16 @@ func (v *validation) makeValidator(req *addValReq) (*validatorImpl, error) {
// RemoveValidator removes an existing validator
func (v *validation) RemoveValidator(req *rmValReq) {
v.mx.Lock()
defer v.mx.Unlock()
bitmask := req.bitmask
_, ok := v.bitmaskVals[string(bitmask)]
if ok {
delete(v.bitmaskVals, string(bitmask))
v.mx.Unlock()
req.resp <- nil
} else {
v.mx.Unlock()
req.resp <- fmt.Errorf("no validator for bitmask %s", bitmask)
}
}
@ -262,7 +264,6 @@ func (v *validation) Push(src peer.ID, msg *Message) bool {
// getValidators returns all validators that apply to a given message
func (v *validation) getValidators(msg *Message) []*validatorImpl {
v.mx.Lock()
defer v.mx.Unlock()
var vals []*validatorImpl
vals = append(vals, v.defaultVals...)
@ -271,10 +272,13 @@ func (v *validation) getValidators(msg *Message) []*validatorImpl {
val, ok := v.bitmaskVals[string(bitmask)]
if !ok {
v.mx.Unlock()
return vals
}
return append(vals, val)
impls := append(vals, val)
v.mx.Unlock()
return impls
}
// validateWorker is an active goroutine performing inline validation
@ -293,7 +297,7 @@ func (v *validation) validateWorker() {
func (v *validation) validate(vals []*validatorImpl, src peer.ID, msg *Message, synchronous bool) error {
// If signature verification is enabled, but signing is disabled,
// the Signature is required to be nil upon receiving the message in PubSub.pushMsg.
if msg.Signature != nil {
if msg.Signature != nil && v.p.signPolicy&msgVerification != 0 {
if !v.validateSignature(msg) {
log.Debugf("message signature validation failed; dropping message from %s", src)
v.tracer.RejectMessage(msg, RejectInvalidSignature)
@ -413,7 +417,6 @@ func (v *validation) validateBitmask(vals []*validatorImpl, src peer.ID, msg *Me
}
ctx, cancel := context.WithCancel(v.p.ctx)
defer cancel()
rch := make(chan ValidationResult, len(vals))
rcount := 0
@ -433,6 +436,7 @@ func (v *validation) validateBitmask(vals []*validatorImpl, src peer.ID, msg *Me
rch <- validationThrottled
}
}
cancel()
result := ValidationAccept
loop:
@ -472,14 +476,10 @@ func (v *validation) validateSingleBitmask(val *validatorImpl, src peer.ID, msg
func (val *validatorImpl) validateMsg(ctx context.Context, src peer.ID, msg *Message) ValidationResult {
start := time.Now()
defer func() {
log.Debugf("validation done; took %s", time.Since(start))
}()
var cancel func() = nil
if val.validateTimeout > 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, val.validateTimeout)
defer cancel()
}
r := val.validate(ctx, src, msg)
@ -489,10 +489,18 @@ func (val *validatorImpl) validateMsg(ctx context.Context, src peer.ID, msg *Mes
case ValidationReject:
fallthrough
case ValidationIgnore:
log.Debugf("validation done; took %s", time.Since(start))
if cancel != nil {
cancel()
}
return r
default:
log.Warnf("Unexpected result from validator: %d; ignoring message", r)
log.Debugf("validation done; took %s", time.Since(start))
if cancel != nil {
cancel()
}
return ValidationIgnore
}
}

View File

@ -72,11 +72,11 @@ func (v *BasicSeqnoValidator) validate(ctx context.Context, _ peer.ID, m *Messag
// get the nonce and compare again with an exclusive lock before commiting (cf concurrent validation)
v.mx.Lock()
defer v.mx.Unlock()
nonceBytes, err = v.meta.Get(ctx, p)
if err != nil {
log.Warn("error retrieving peer nonce: %s", err)
v.mx.Unlock()
return ValidationIgnore
}
@ -85,6 +85,7 @@ func (v *BasicSeqnoValidator) validate(ctx context.Context, _ peer.ID, m *Messag
}
if seqno <= nonce {
v.mx.Unlock()
return ValidationIgnore
}
@ -96,6 +97,6 @@ func (v *BasicSeqnoValidator) validate(ctx context.Context, _ peer.ID, m *Messag
if err != nil {
log.Warn("error storing peer nonce: %s", err)
}
v.mx.Unlock()
return ValidationAccept
}

View File

@ -20,6 +20,18 @@ import (
pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
)
func getBlossomSubsWithOptionC(ctx context.Context, hs []host.Host, cons ...func(int) Option) []*PubSub {
var psubs []*PubSub
for _, h := range hs {
var opts []Option
for i, c := range cons {
opts = append(opts, c(i))
}
psubs = append(psubs, getBlossomSub(ctx, h, opts...))
}
return psubs
}
var rng *rand.Rand
func init() {
@ -38,8 +50,8 @@ func testBasicSeqnoValidator(t *testing.T, ttl time.Duration) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 20)
psubs := getPubsubsWithOptionC(ctx, hosts,
hosts := getDefaultHosts(t, 20)
psubs := getBlossomSubsWithOptionC(ctx, hosts,
func(i int) Option {
return WithDefaultValidator(NewBasicSeqnoValidator(newMockPeerMetadataStore()))
},
@ -49,13 +61,19 @@ func testBasicSeqnoValidator(t *testing.T, ttl time.Duration) {
)
var msgs []*Subscription
var bitmasks []*Bitmask
for _, ps := range psubs {
subch, err := ps.Subscribe([]byte{0xf0, 0x0b, 0xa1, 0x20})
b, err := ps.Join([]byte{0x00, 0x01})
if err != nil {
t.Fatal(err)
}
bitmasks = append(bitmasks, b...)
subch, err := ps.Subscribe([]byte{0x00, 0x01})
if err != nil {
t.Fatal(err)
}
msgs = append(msgs, subch)
msgs = append(msgs, subch...)
}
// connectAll(t, hosts)
@ -68,7 +86,7 @@ func testBasicSeqnoValidator(t *testing.T, ttl time.Duration) {
owner := rng.Intn(len(psubs))
psubs[owner].Publish([]byte{0xf0, 0x0b, 0xa1, 0x20}, msg)
bitmasks[owner].Publish(ctx, bitmasks[owner].bitmask, msg)
for _, sub := range msgs {
got, err := sub.Next(ctx)
@ -86,8 +104,8 @@ func TestBasicSeqnoValidatorReplay(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 20)
psubs := getPubsubsWithOptionC(ctx, hosts[:19],
hosts := getDefaultHosts(t, 20)
psubs := getBlossomSubsWithOptionC(ctx, hosts[:19],
func(i int) Option {
return WithDefaultValidator(NewBasicSeqnoValidator(newMockPeerMetadataStore()))
},
@ -98,13 +116,19 @@ func TestBasicSeqnoValidatorReplay(t *testing.T) {
_ = newReplayActor(t, ctx, hosts[19])
var msgs []*Subscription
var bitmasks []*Bitmask
for _, ps := range psubs {
subch, err := ps.Subscribe([]byte{0xf0, 0x0b, 0xa1, 0x20})
b, err := ps.Join([]byte{0x00, 0x01})
if err != nil {
t.Fatal(err)
}
bitmasks = append(bitmasks, b...)
subch, err := ps.Subscribe([]byte{0x00, 0x01})
if err != nil {
t.Fatal(err)
}
msgs = append(msgs, subch)
msgs = append(msgs, subch...)
}
sparseConnect(t, hosts)
@ -116,7 +140,7 @@ func TestBasicSeqnoValidatorReplay(t *testing.T) {
owner := rng.Intn(len(psubs))
psubs[owner].Publish([]byte{0xf0, 0x0b, 0xa1, 0x20}, msg)
bitmasks[owner].Publish(ctx, bitmasks[owner].bitmask, msg)
for _, sub := range msgs {
got, err := sub.Next(ctx)
@ -169,7 +193,7 @@ type replayActor struct {
func newReplayActor(t *testing.T, ctx context.Context, h host.Host) *replayActor {
replay := &replayActor{t: t, ctx: ctx, h: h, out: make(map[peer.ID]network.Stream)}
h.SetStreamHandler(FloodSubID, replay.handleStream)
h.SetStreamHandler(BlossomSubID_v2, replay.handleStream)
h.Network().Notify(&network.NotifyBundle{ConnectedF: replay.connected})
return replay
}
@ -246,7 +270,7 @@ func (r *replayActor) replay(msg *pb.Message) {
var peers []peer.ID
r.mx.Lock()
for p, _ := range r.out {
for p := range r.out {
if rng.Intn(2) > 0 {
peers = append(peers, p)
}
@ -262,7 +286,7 @@ func (r *replayActor) replay(msg *pb.Message) {
}
func (r *replayActor) handleConnected(p peer.ID) {
s, err := r.h.NewStream(r.ctx, p, FloodSubID)
s, err := r.h.NewStream(r.ctx, p, BlossomSubID_v2)
if err != nil {
r.t.Logf("replay: error opening stream: %s", err)
return

View File

@ -15,8 +15,8 @@ func TestRegisterUnregisterValidator(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 1)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 1)
psubs := getBlossomSubs(ctx, hosts)
err := psubs[0].RegisterBitmaskValidator([]byte{0xf0, 0x00}, func(context.Context, peer.ID, *Message) bool {
return true
@ -40,10 +40,10 @@ func TestRegisterValidatorEx(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 3)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 3)
psubs := getBlossomSubs(ctx, hosts)
err := psubs[0].RegisterBitmaskValidator([]byte{0x7e, 0x57},
err := psubs[0].RegisterBitmaskValidator([]byte{0x01, 0x00},
Validator(func(context.Context, peer.ID, *Message) bool {
return true
}))
@ -51,7 +51,7 @@ func TestRegisterValidatorEx(t *testing.T) {
t.Fatal(err)
}
err = psubs[1].RegisterBitmaskValidator([]byte{0x7e, 0x57},
err = psubs[1].RegisterBitmaskValidator([]byte{0x01, 0x00},
ValidatorEx(func(context.Context, peer.ID, *Message) ValidationResult {
return ValidationAccept
}))
@ -59,7 +59,7 @@ func TestRegisterValidatorEx(t *testing.T) {
t.Fatal(err)
}
err = psubs[2].RegisterBitmaskValidator([]byte{0x7e, 0x57}, "bogus")
err = psubs[2].RegisterBitmaskValidator([]byte{0x01, 0x00}, "bogus")
if err == nil {
t.Fatal("expected error")
}
@ -69,11 +69,11 @@ func TestValidate(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 2)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
psubs := getBlossomSubs(ctx, hosts)
connect(t, hosts[0], hosts[1])
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
err := psubs[1].RegisterBitmaskValidator(bitmask, func(ctx context.Context, from peer.ID, msg *Message) bool {
return !bytes.Contains(msg.Data, []byte("illegal"))
@ -82,6 +82,11 @@ func TestValidate(t *testing.T) {
t.Fatal(err)
}
b, err := psubs[0].Join(bitmask)
if err != nil {
t.Fatal(err)
}
sub, err := psubs[1].Subscribe(bitmask)
if err != nil {
t.Fatal(err)
@ -100,13 +105,13 @@ func TestValidate(t *testing.T) {
}
for _, tc := range msgs {
err := psubs[0].Publish(bitmask, tc.msg)
err := b[0].Publish(ctx, b[0].bitmask, tc.msg)
if err != nil {
t.Fatal(err)
}
select {
case msg := <-sub.ch:
case msg := <-sub[0].ch:
if !tc.validates {
t.Log(msg)
t.Error("expected message validation to filter out the message")
@ -123,10 +128,10 @@ func TestValidate2(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 1)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 1)
psubs := getBlossomSubs(ctx, hosts)
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
err := psubs[0].RegisterBitmaskValidator(bitmask, func(ctx context.Context, from peer.ID, msg *Message) bool {
return !bytes.Contains(msg.Data, []byte("illegal"))
@ -145,8 +150,13 @@ func TestValidate2(t *testing.T) {
{msg: []byte("but subversive actors will use leetspeek to spread 1ll3g4l content"), validates: true},
}
b, err := psubs[0].Join(bitmask)
if err != nil {
t.Fatal(err)
}
for _, tc := range msgs {
err := psubs[0].Publish(bitmask, tc.msg)
err := b[0].Publish(ctx, b[0].bitmask, tc.msg)
if tc.validates {
if err != nil {
t.Fatal(err)
@ -183,7 +193,7 @@ func TestValidateOverload(t *testing.T) {
{msg: []byte("still, all good"), validates: true},
{msg: []byte("this is getting boring"), validates: true},
{msg: []byte([]byte{0xf0, 0x00}), validates: true},
{msg: []byte([]byte{0xf0, 0x0b, 0xa1, 0x20}), validates: true},
{msg: []byte([]byte{0x00, 0x01}), validates: true},
{msg: []byte("foofoo"), validates: true},
{msg: []byte("barfoo"), validates: true},
{msg: []byte("oh no!"), validates: false},
@ -201,11 +211,11 @@ func TestValidateOverload(t *testing.T) {
for tci, tc := range tcs {
t.Run(fmt.Sprintf("%d", tci), func(t *testing.T) {
hosts := getNetHosts(t, ctx, 2)
psubs := getPubsubs(ctx, hosts)
hosts := getDefaultHosts(t, 2)
psubs := getBlossomSubs(ctx, hosts)
connect(t, hosts[0], hosts[1])
bitmask := []byte{0xf0, 0x0b, 0xa1, 0x20}
bitmask := []byte{0x00, 0x01}
block := make(chan struct{})
@ -232,13 +242,17 @@ func TestValidateOverload(t *testing.T) {
}
p := psubs[0]
b, err := p.Join(bitmask)
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
for _, tmsg := range tc.msgs {
select {
case msg := <-sub.ch:
case msg := <-sub[0].ch:
if !tmsg.validates {
t.Log(msg)
t.Error("expected message validation to drop the message because all validator goroutines are taken")
@ -253,7 +267,7 @@ func TestValidateOverload(t *testing.T) {
}()
for _, tmsg := range tc.msgs {
err := p.Publish(bitmask, tmsg.msg)
err := b[0].Publish(ctx, b[0].bitmask, tmsg.msg)
if err != nil {
t.Fatal(err)
}
@ -273,8 +287,8 @@ func TestValidateAssortedOptions(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hosts := getNetHosts(t, ctx, 10)
psubs := getPubsubs(ctx, hosts,
hosts := getDefaultHosts(t, 10)
psubs := getBlossomSubs(ctx, hosts,
WithValidateQueueSize(10),
WithValidateThrottle(10),
WithValidateWorkers(10))
@ -282,7 +296,7 @@ func TestValidateAssortedOptions(t *testing.T) {
sparseConnect(t, hosts)
for _, psub := range psubs {
err := psub.RegisterBitmaskValidator([]byte{0xff, 0x00, 0x00, 0x00},
err := psub.RegisterBitmaskValidator([]byte{0x00, 0x80, 0x00, 0x00},
func(context.Context, peer.ID, *Message) bool {
return true
},
@ -291,7 +305,7 @@ func TestValidateAssortedOptions(t *testing.T) {
t.Fatal(err)
}
err = psub.RegisterBitmaskValidator([]byte{0x00, 0xff, 0x00, 0x00},
err = psub.RegisterBitmaskValidator([]byte{0x00, 0x20, 0x00, 0x00},
func(context.Context, peer.ID, *Message) bool {
return true
},
@ -302,31 +316,44 @@ func TestValidateAssortedOptions(t *testing.T) {
}
var subs1, subs2 []*Subscription
var bitmasks1, bitmasks2 []*Bitmask
for _, ps := range psubs {
sub, err := ps.Subscribe([]byte{0xff, 0x00, 0x00, 0x00})
b, err := ps.Join([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs1 = append(subs1, sub)
bitmasks1 = append(bitmasks1, b...)
sub, err = ps.Subscribe([]byte{0x00, 0xff, 0x00, 0x00})
b, err = ps.Join([]byte{0x00, 0x04, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs2 = append(subs2, sub)
bitmasks2 = append(bitmasks2, b...)
sub, err := ps.Subscribe([]byte{0x00, 0x80, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs1 = append(subs1, sub...)
sub, err = ps.Subscribe([]byte{0x00, 0x04, 0x00, 0x00})
if err != nil {
t.Fatal(err)
}
subs2 = append(subs2, sub...)
}
time.Sleep(time.Second)
for i := 0; i < 10; i++ {
msg := []byte(fmt.Sprintf("message %d", i))
msg := []byte(fmt.Sprintf("message1 %d", i))
psubs[i].Publish([]byte{0xff, 0x00, 0x00, 0x00}, msg)
bitmasks1[i].Publish(ctx, bitmasks1[i].bitmask, msg)
for _, sub := range subs1 {
assertReceive(t, sub, msg)
}
msg = []byte(fmt.Sprintf("message2 %d", i))
psubs[i].Publish([]byte{0x00, 0xff, 0x00, 0x00}, msg)
bitmasks2[i].Publish(ctx, bitmasks2[i].bitmask, msg)
for _, sub := range subs2 {
assertReceive(t, sub, msg)
}

View File

@ -0,0 +1,15 @@
# CODEOWNERS
# default owner is the libp2p team
*.go @libp2p/go-libp2p-maintainers @guillaumemichel
/pb/ @libp2p/go-libp2p-maintainers @guillaumemichel
# dual is an application for IPFS
/dual/ @libp2p/kubo-maintainers @guillaumemichel
# fullrt is IPFS specific
/fullrt/ @libp2p/kubo-maintainers @guillaumemichel
# providers describe the IPFS specific providers
/providers/ @libp2p/kubo-maintainers @guillaumemichel
# records are IPFS specific
/records.go @libp2p/kubo-maintainers @guillaumemichel
/records_test.go @libp2p/kubo-maintainers @guillaumemichel

21
go-libp2p-kad-dht/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Protocol Labs, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,51 @@
# go-libp2p-kad-dht
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io)
[![GoDoc](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht?status.svg)](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
> A Go implementation of [libp2p Kademlia DHT specification](https://github.com/libp2p/specs/tree/master/kad-dht)
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Optimizations](#optimizations)
- [Contribute](#contribute)
- [Maintainers](#maintainers)
- [License](#license)
## Install
```sh
go get github.com/libp2p/go-libp2p-kad-dht
```
## Optimizations
Client-side optimizations are described in [optimizations.md](./optimizations.md)
## Usage
Go to https://godoc.org/github.com/libp2p/go-libp2p-kad-dht.
## Contribute
Contributions welcome. Please check out [the issues](https://github.com/libp2p/go-libp2p-kad-dht/issues).
Check out our [contributing document](https://github.com/libp2p/community/blob/master/CONTRIBUTE.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to libp2p are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
<!-- Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. -->
## Maintainers
- [@ipfs/kubo-maintainers](https://github.com/orgs/ipfs/teams/kubo-maintainers)
- [@libp2p/go-libp2p-maintainers](https://github.com/orgs/libp2p/teams/go-libp2p-maintainers)
- [@guillaumemichel](https://github.com/guillaumemichel)
See [CODEOWNERS](./CODEOWNERS).
## License
[MIT](LICENSE) © Protocol Labs Inc.

View File

@ -0,0 +1,266 @@
package crawler
import (
"context"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
logging "github.com/ipfs/go-log/v2"
//lint:ignore SA1019 TODO migrate away from gogo pb
"github.com/libp2p/go-msgio/protoio"
pb "github.com/libp2p/go-libp2p-kad-dht/pb"
kbucket "github.com/libp2p/go-libp2p-kbucket"
)
var (
logger = logging.Logger("dht-crawler")
_ Crawler = (*DefaultCrawler)(nil)
)
type (
// Crawler connects to hosts in the DHT to track routing tables of peers.
Crawler interface {
// Run crawls the DHT starting from the startingPeers, and calls either handleSuccess or handleFail depending on whether a peer was successfully contacted or not.
Run(ctx context.Context, startingPeers []*peer.AddrInfo, handleSuccess HandleQueryResult, handleFail HandleQueryFail)
}
// DefaultCrawler provides a default implementation of Crawler.
DefaultCrawler struct {
parallelism int
connectTimeout time.Duration
host host.Host
dhtRPC *pb.ProtocolMessenger
dialAddressExtendDur time.Duration
}
)
// NewDefaultCrawler creates a new DefaultCrawler
func NewDefaultCrawler(host host.Host, opts ...Option) (*DefaultCrawler, error) {
o := new(options)
if err := defaults(o); err != nil {
return nil, err
}
for _, opt := range opts {
if err := opt(o); err != nil {
return nil, err
}
}
pm, err := pb.NewProtocolMessenger(&messageSender{h: host, protocols: o.protocols, timeout: o.perMsgTimeout})
if err != nil {
return nil, err
}
return &DefaultCrawler{
parallelism: o.parallelism,
connectTimeout: o.connectTimeout,
host: host,
dhtRPC: pm,
dialAddressExtendDur: o.dialAddressExtendDur,
}, nil
}
// MessageSender handles sending wire protocol messages to a given peer
type messageSender struct {
h host.Host
protocols []protocol.ID
timeout time.Duration
}
// SendRequest sends a peer a message and waits for its response
func (ms *messageSender) SendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {
s, err := ms.h.NewStream(ctx, p, ms.protocols...)
if err != nil {
return nil, err
}
w := protoio.NewDelimitedWriter(s)
if err := w.WriteMsg(pmes); err != nil {
return nil, err
}
r := protoio.NewDelimitedReader(s, network.MessageSizeMax)
tctx, cancel := context.WithTimeout(ctx, ms.timeout)
defer cancel()
defer func() { _ = s.Close() }()
msg := new(pb.Message)
if err := ctxReadMsg(tctx, r, msg); err != nil {
_ = s.Reset()
return nil, err
}
return msg, nil
}
func ctxReadMsg(ctx context.Context, rc protoio.ReadCloser, mes *pb.Message) error {
errc := make(chan error, 1)
go func(r protoio.ReadCloser) {
defer close(errc)
err := r.ReadMsg(mes)
errc <- err
}(rc)
select {
case err := <-errc:
return err
case <-ctx.Done():
return ctx.Err()
}
}
// SendMessage sends a peer a message without waiting on a response
func (ms *messageSender) SendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error {
s, err := ms.h.NewStream(ctx, p, ms.protocols...)
if err != nil {
return err
}
defer func() { _ = s.Close() }()
w := protoio.NewDelimitedWriter(s)
return w.WriteMsg(pmes)
}
// HandleQueryResult is a callback on successful peer query
type HandleQueryResult func(p peer.ID, rtPeers []*peer.AddrInfo)
// HandleQueryFail is a callback on failed peer query
type HandleQueryFail func(p peer.ID, err error)
// Run crawls dht peers from an initial seed of `startingPeers`
func (c *DefaultCrawler) Run(ctx context.Context, startingPeers []*peer.AddrInfo, handleSuccess HandleQueryResult, handleFail HandleQueryFail) {
jobs := make(chan peer.ID, 1)
results := make(chan *queryResult, 1)
// Start worker goroutines
var wg sync.WaitGroup
wg.Add(c.parallelism)
for i := 0; i < c.parallelism; i++ {
go func() {
defer wg.Done()
for p := range jobs {
res := c.queryPeer(ctx, p)
results <- res
}
}()
}
defer wg.Wait()
defer close(jobs)
var toDial []*peer.AddrInfo
peersSeen := make(map[peer.ID]struct{})
numSkipped := 0
for _, ai := range startingPeers {
extendAddrs := c.host.Peerstore().Addrs(ai.ID)
if len(ai.Addrs) > 0 {
extendAddrs = append(extendAddrs, ai.Addrs...)
c.host.Peerstore().AddAddrs(ai.ID, extendAddrs, c.dialAddressExtendDur)
}
if len(extendAddrs) == 0 {
numSkipped++
continue
}
toDial = append(toDial, ai)
peersSeen[ai.ID] = struct{}{}
}
if numSkipped > 0 {
logger.Infof("%d starting peers were skipped due to lack of addresses. Starting crawl with %d peers", numSkipped, len(toDial))
}
numQueried := 0
outstanding := 0
for len(toDial) > 0 || outstanding > 0 {
var jobCh chan peer.ID
var nextPeerID peer.ID
if len(toDial) > 0 {
jobCh = jobs
nextPeerID = toDial[0].ID
}
select {
case res := <-results:
if len(res.data) > 0 {
logger.Debugf("peer %v had %d peers", res.peer, len(res.data))
rtPeers := make([]*peer.AddrInfo, 0, len(res.data))
for p, ai := range res.data {
c.host.Peerstore().AddAddrs(p, ai.Addrs, c.dialAddressExtendDur)
if _, ok := peersSeen[p]; !ok {
peersSeen[p] = struct{}{}
toDial = append(toDial, ai)
}
rtPeers = append(rtPeers, ai)
}
if handleSuccess != nil {
handleSuccess(res.peer, rtPeers)
}
} else if handleFail != nil {
handleFail(res.peer, res.err)
}
outstanding--
case jobCh <- nextPeerID:
outstanding++
numQueried++
toDial = toDial[1:]
logger.Debugf("starting %d out of %d", numQueried, len(peersSeen))
}
}
}
type queryResult struct {
peer peer.ID
data map[peer.ID]*peer.AddrInfo
err error
}
func (c *DefaultCrawler) queryPeer(ctx context.Context, nextPeer peer.ID) *queryResult {
tmpRT, err := kbucket.NewRoutingTable(20, kbucket.ConvertPeerID(nextPeer), time.Hour, c.host.Peerstore(), time.Hour, nil)
if err != nil {
logger.Errorf("error creating rt for peer %v : %v", nextPeer, err)
return &queryResult{nextPeer, nil, err}
}
connCtx, cancel := context.WithTimeout(ctx, c.connectTimeout)
defer cancel()
err = c.host.Connect(connCtx, peer.AddrInfo{ID: nextPeer})
if err != nil {
logger.Debugf("could not connect to peer %v: %v", nextPeer, err)
return &queryResult{nextPeer, nil, err}
}
localPeers := make(map[peer.ID]*peer.AddrInfo)
var retErr error
for cpl := 0; cpl <= 15; cpl++ {
generatePeer, err := tmpRT.GenRandPeerID(uint(cpl))
if err != nil {
panic(err)
}
peers, err := c.dhtRPC.GetClosestPeers(ctx, nextPeer, generatePeer)
if err != nil {
logger.Debugf("error finding data on peer %v with cpl %d : %v", nextPeer, cpl, err)
retErr = err
break
}
for _, ai := range peers {
if _, ok := localPeers[ai.ID]; !ok {
localPeers[ai.ID] = ai
}
}
}
if retErr != nil {
return &queryResult{nextPeer, nil, retErr}
}
return &queryResult{nextPeer, localPeers, retErr}
}

View File

@ -0,0 +1,72 @@
package crawler
import (
"time"
"github.com/libp2p/go-libp2p/core/protocol"
)
// Option DHT Crawler option type.
type Option func(*options) error
type options struct {
protocols []protocol.ID
parallelism int
connectTimeout time.Duration
perMsgTimeout time.Duration
dialAddressExtendDur time.Duration
}
// defaults are the default crawler options. This option will be automatically
// prepended to any options you pass to the crawler constructor.
var defaults = func(o *options) error {
o.protocols = []protocol.ID{"/ipfs/kad/1.0.0"}
o.parallelism = 1000
o.connectTimeout = time.Second * 5
o.perMsgTimeout = time.Second * 5
o.dialAddressExtendDur = time.Minute * 30
return nil
}
// WithProtocols defines the ordered set of protocols the crawler will use to talk to other nodes
func WithProtocols(protocols []protocol.ID) Option {
return func(o *options) error {
o.protocols = append([]protocol.ID{}, protocols...)
return nil
}
}
// WithParallelism defines the number of queries that can be issued in parallel
func WithParallelism(parallelism int) Option {
return func(o *options) error {
o.parallelism = parallelism
return nil
}
}
// WithMsgTimeout defines the amount of time a single DHT message is allowed to take before it's deemed failed
func WithMsgTimeout(timeout time.Duration) Option {
return func(o *options) error {
o.perMsgTimeout = timeout
return nil
}
}
// WithConnectTimeout defines the time for peer connection before timing out
func WithConnectTimeout(timeout time.Duration) Option {
return func(o *options) error {
o.connectTimeout = timeout
return nil
}
}
// WithDialAddrExtendDuration sets the duration by which the TTL of dialed address in peer store are
// extended.
// Defaults to 30 minutes if unset.
func WithDialAddrExtendDuration(ext time.Duration) Option {
return func(o *options) error {
o.dialAddressExtendDur = ext
return nil
}
}

942
go-libp2p-kad-dht/dht.go Normal file
View File

@ -0,0 +1,942 @@
package dht
import (
"context"
"fmt"
"math"
"math/rand"
"sync"
"time"
"github.com/libp2p/go-libp2p-routing-helpers/tracing"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/routing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/libp2p/go-libp2p-kad-dht/internal"
dhtcfg "github.com/libp2p/go-libp2p-kad-dht/internal/config"
"github.com/libp2p/go-libp2p-kad-dht/internal/net"
"github.com/libp2p/go-libp2p-kad-dht/metrics"
"github.com/libp2p/go-libp2p-kad-dht/netsize"
pb "github.com/libp2p/go-libp2p-kad-dht/pb"
"github.com/libp2p/go-libp2p-kad-dht/providers"
"github.com/libp2p/go-libp2p-kad-dht/rtrefresh"
kb "github.com/libp2p/go-libp2p-kbucket"
"github.com/libp2p/go-libp2p-kbucket/peerdiversity"
record "github.com/libp2p/go-libp2p-record"
recpb "github.com/libp2p/go-libp2p-record/pb"
"github.com/gogo/protobuf/proto"
ds "github.com/ipfs/go-datastore"
logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-base32"
ma "github.com/multiformats/go-multiaddr"
"go.opencensus.io/tag"
"go.uber.org/multierr"
"go.uber.org/zap"
)
const tracer = tracing.Tracer("go-libp2p-kad-dht")
const dhtName = "IpfsDHT"
var (
logger = logging.Logger("dht")
baseLogger = logger.Desugar()
rtFreezeTimeout = 1 * time.Minute
)
const (
// BaseConnMgrScore is the base of the score set on the connection
// manager "kbucket" tag. It is added with the common prefix length
// between two peer IDs.
baseConnMgrScore = 5
)
type mode int
const (
modeServer mode = iota + 1
modeClient
)
const (
kad1 protocol.ID = "/kad/1.0.0"
)
const (
kbucketTag = "kbucket"
protectedBuckets = 2
)
// IpfsDHT is an implementation of Kademlia with S/Kademlia modifications.
// It is used to implement the base Routing module.
type IpfsDHT struct {
host host.Host // the network services we need
self peer.ID // Local peer (yourself)
selfKey kb.ID
peerstore peerstore.Peerstore // Peer Registry
datastore ds.Datastore // Local data
routingTable *kb.RoutingTable // Array of routing tables for differently distanced nodes
// providerStore stores & manages the provider records for this Dht peer.
providerStore providers.ProviderStore
// manages Routing Table refresh
rtRefreshManager *rtrefresh.RtRefreshManager
birth time.Time // When this peer started up
Validator record.Validator
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
protoMessenger *pb.ProtocolMessenger
msgSender pb.MessageSenderWithDisconnect
stripedPutLocks [256]sync.Mutex
// DHT protocols we query with. We'll only add peers to our routing
// table if they speak these protocols.
protocols []protocol.ID
// DHT protocols we can respond to.
serverProtocols []protocol.ID
auto ModeOpt
mode mode
modeLk sync.Mutex
bucketSize int
alpha int // The concurrency parameter per path
beta int // The number of peers closest to a target that must have responded for a query path to terminate
queryPeerFilter QueryFilterFunc
routingTablePeerFilter RouteTableFilterFunc
rtPeerDiversityFilter peerdiversity.PeerIPGroupFilter
autoRefresh bool
// timeout for the lookupCheck operation
lookupCheckTimeout time.Duration
// number of concurrent lookupCheck operations
lookupCheckCapacity int
lookupChecksLk sync.Mutex
// A function returning a set of bootstrap peers to fallback on if all other attempts to fix
// the routing table fail (or, e.g., this is the first time this node is
// connecting to the network).
bootstrapPeers func() []peer.AddrInfo
maxRecordAge time.Duration
// Allows disabling dht subsystems. These should _only_ be set on
// "forked" DHTs (e.g., DHTs with custom protocols and/or private
// networks).
enableProviders, enableValues bool
disableFixLowPeers bool
fixLowPeersChan chan struct{}
addPeerToRTChan chan peer.ID
refreshFinishedCh chan struct{}
rtFreezeTimeout time.Duration
// network size estimator
nsEstimator *netsize.Estimator
enableOptProv bool
// a bound channel to limit asynchronicity of in-flight ADD_PROVIDER RPCs
optProvJobsPool chan struct{}
// configuration variables for tests
testAddressUpdateProcessing bool
// addrFilter is used to filter the addresses we put into the peer store.
// Mostly used to filter out localhost and local addresses.
addrFilter func([]ma.Multiaddr) []ma.Multiaddr
}
// Assert that IPFS assumptions about interfaces aren't broken. These aren't a
// guarantee, but we can use them to aid refactoring.
var (
_ routing.ContentRouting = (*IpfsDHT)(nil)
_ routing.Routing = (*IpfsDHT)(nil)
_ routing.PeerRouting = (*IpfsDHT)(nil)
_ routing.PubKeyFetcher = (*IpfsDHT)(nil)
_ routing.ValueStore = (*IpfsDHT)(nil)
)
// New creates a new DHT with the specified host and options.
// Please note that being connected to a DHT peer does not necessarily imply that it's also in the DHT Routing Table.
// If the Routing Table has more than "minRTRefreshThreshold" peers, we consider a peer as a Routing Table candidate ONLY when
// we successfully get a query response from it OR if it send us a query.
func New(ctx context.Context, h host.Host, options ...Option) (*IpfsDHT, error) {
var cfg dhtcfg.Config
if err := cfg.Apply(append([]Option{dhtcfg.Defaults}, options...)...); err != nil {
return nil, err
}
if err := cfg.ApplyFallbacks(h); err != nil {
return nil, err
}
if err := cfg.Validate(); err != nil {
return nil, err
}
dht, err := makeDHT(h, cfg)
if err != nil {
return nil, fmt.Errorf("failed to create DHT, err=%s", err)
}
dht.autoRefresh = cfg.RoutingTable.AutoRefresh
dht.maxRecordAge = cfg.MaxRecordAge
dht.enableProviders = cfg.EnableProviders
dht.enableValues = cfg.EnableValues
dht.disableFixLowPeers = cfg.DisableFixLowPeers
dht.Validator = cfg.Validator
dht.msgSender = net.NewMessageSenderImpl(h, dht.protocols)
dht.protoMessenger, err = pb.NewProtocolMessenger(dht.msgSender)
if err != nil {
return nil, err
}
dht.testAddressUpdateProcessing = cfg.TestAddressUpdateProcessing
dht.auto = cfg.Mode
switch cfg.Mode {
case ModeAuto, ModeClient:
dht.mode = modeClient
case ModeAutoServer, ModeServer:
dht.mode = modeServer
default:
return nil, fmt.Errorf("invalid dht mode %d", cfg.Mode)
}
if dht.mode == modeServer {
if err := dht.moveToServerMode(); err != nil {
return nil, err
}
}
// register for event bus and network notifications
if err := dht.startNetworkSubscriber(); err != nil {
return nil, err
}
// go-routine to make sure we ALWAYS have RT peer addresses in the peerstore
// since RT membership is decoupled from connectivity
go dht.persistRTPeersInPeerStore()
dht.rtPeerLoop()
// Fill routing table with currently connected peers that are DHT servers
for _, p := range dht.host.Network().Peers() {
dht.peerFound(p)
}
dht.rtRefreshManager.Start()
// listens to the fix low peers chan and tries to fix the Routing Table
if !dht.disableFixLowPeers {
dht.runFixLowPeersLoop()
}
return dht, nil
}
// NewDHT creates a new DHT object with the given peer as the 'local' host.
// IpfsDHT's initialized with this function will respond to DHT requests,
// whereas IpfsDHT's initialized with NewDHTClient will not.
func NewDHT(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
dht, err := New(ctx, h, Datastore(dstore))
if err != nil {
panic(err)
}
return dht
}
// NewDHTClient creates a new DHT object with the given peer as the 'local'
// host. IpfsDHT clients initialized with this function will not respond to DHT
// requests. If you need a peer to respond to DHT requests, use NewDHT instead.
func NewDHTClient(ctx context.Context, h host.Host, dstore ds.Batching) *IpfsDHT {
dht, err := New(ctx, h, Datastore(dstore), Mode(ModeClient))
if err != nil {
panic(err)
}
return dht
}
func makeDHT(h host.Host, cfg dhtcfg.Config) (*IpfsDHT, error) {
var protocols, serverProtocols []protocol.ID
v1proto := cfg.ProtocolPrefix + kad1
if cfg.V1ProtocolOverride != "" {
v1proto = cfg.V1ProtocolOverride
}
protocols = []protocol.ID{v1proto}
serverProtocols = []protocol.ID{v1proto}
dht := &IpfsDHT{
datastore: cfg.Datastore,
self: h.ID(),
selfKey: kb.ConvertPeerID(h.ID()),
peerstore: h.Peerstore(),
host: h,
birth: time.Now(),
protocols: protocols,
serverProtocols: serverProtocols,
bucketSize: cfg.BucketSize,
alpha: cfg.Concurrency,
beta: cfg.Resiliency,
lookupCheckCapacity: cfg.LookupCheckConcurrency,
queryPeerFilter: cfg.QueryPeerFilter,
routingTablePeerFilter: cfg.RoutingTable.PeerFilter,
rtPeerDiversityFilter: cfg.RoutingTable.DiversityFilter,
addrFilter: cfg.AddressFilter,
fixLowPeersChan: make(chan struct{}, 1),
addPeerToRTChan: make(chan peer.ID),
refreshFinishedCh: make(chan struct{}),
enableOptProv: cfg.EnableOptimisticProvide,
optProvJobsPool: nil,
}
var maxLastSuccessfulOutboundThreshold time.Duration
// The threshold is calculated based on the expected amount of time that should pass before we
// query a peer as part of our refresh cycle.
// To grok the Math Wizardy that produced these exact equations, please be patient as a document explaining it will
// be published soon.
if cfg.Concurrency < cfg.BucketSize { // (alpha < K)
l1 := math.Log(float64(1) / float64(cfg.BucketSize)) // (Log(1/K))
l2 := math.Log(float64(1) - (float64(cfg.Concurrency) / float64(cfg.BucketSize))) // Log(1 - (alpha / K))
maxLastSuccessfulOutboundThreshold = time.Duration(l1 / l2 * float64(cfg.RoutingTable.RefreshInterval))
} else {
maxLastSuccessfulOutboundThreshold = cfg.RoutingTable.RefreshInterval
}
// construct routing table
// use twice the theoritical usefulness threhold to keep older peers around longer
rt, err := makeRoutingTable(dht, cfg, 2*maxLastSuccessfulOutboundThreshold)
if err != nil {
return nil, fmt.Errorf("failed to construct routing table,err=%s", err)
}
dht.routingTable = rt
dht.bootstrapPeers = cfg.BootstrapPeers
dht.lookupCheckTimeout = cfg.RoutingTable.RefreshQueryTimeout
// init network size estimator
dht.nsEstimator = netsize.NewEstimator(h.ID(), rt, cfg.BucketSize)
if dht.enableOptProv {
dht.optProvJobsPool = make(chan struct{}, cfg.OptimisticProvideJobsPoolSize)
}
// rt refresh manager
dht.rtRefreshManager, err = makeRtRefreshManager(dht, cfg, maxLastSuccessfulOutboundThreshold)
if err != nil {
return nil, fmt.Errorf("failed to construct RT Refresh Manager,err=%s", err)
}
// create a tagged context derived from the original context
// the DHT context should be done when the process is closed
dht.ctx, dht.cancel = context.WithCancel(dht.newContextWithLocalTags(context.Background()))
if cfg.ProviderStore != nil {
dht.providerStore = cfg.ProviderStore
} else {
dht.providerStore, err = providers.NewProviderManager(h.ID(), dht.peerstore, cfg.Datastore)
if err != nil {
return nil, fmt.Errorf("initializing default provider manager (%v)", err)
}
}
dht.rtFreezeTimeout = rtFreezeTimeout
return dht, nil
}
// lookupCheck performs a lookup request to a remote peer.ID, verifying that it is able to
// answer it correctly
func (dht *IpfsDHT) lookupCheck(ctx context.Context, p peer.ID) error {
// lookup request to p requesting for its own peer.ID
peerids, err := dht.protoMessenger.GetClosestPeers(ctx, p, p)
// p is expected to return at least 1 peer id, unless our routing table has
// less than bucketSize peers, in which case we aren't picky about who we
// add to the routing table.
if err == nil && len(peerids) == 0 && dht.routingTable.Size() >= dht.bucketSize {
return fmt.Errorf("peer %s failed to return its closest peers, got %d", p, len(peerids))
}
return err
}
func makeRtRefreshManager(dht *IpfsDHT, cfg dhtcfg.Config, maxLastSuccessfulOutboundThreshold time.Duration) (*rtrefresh.RtRefreshManager, error) {
keyGenFnc := func(cpl uint) (string, error) {
p, err := dht.routingTable.GenRandPeerID(cpl)
return string(p), err
}
queryFnc := func(ctx context.Context, key string) error {
_, err := dht.GetClosestPeers(ctx, key)
return err
}
r, err := rtrefresh.NewRtRefreshManager(
dht.host, dht.routingTable, cfg.RoutingTable.AutoRefresh,
keyGenFnc,
queryFnc,
dht.lookupCheck,
cfg.RoutingTable.RefreshQueryTimeout,
cfg.RoutingTable.RefreshInterval,
maxLastSuccessfulOutboundThreshold,
dht.refreshFinishedCh)
return r, err
}
func makeRoutingTable(dht *IpfsDHT, cfg dhtcfg.Config, maxLastSuccessfulOutboundThreshold time.Duration) (*kb.RoutingTable, error) {
// make a Routing Table Diversity Filter
var filter *peerdiversity.Filter
if dht.rtPeerDiversityFilter != nil {
df, err := peerdiversity.NewFilter(dht.rtPeerDiversityFilter, "rt/diversity", func(p peer.ID) int {
return kb.CommonPrefixLen(dht.selfKey, kb.ConvertPeerID(p))
})
if err != nil {
return nil, fmt.Errorf("failed to construct peer diversity filter: %w", err)
}
filter = df
}
rt, err := kb.NewRoutingTable(cfg.BucketSize, dht.selfKey, time.Minute, dht.host.Peerstore(), maxLastSuccessfulOutboundThreshold, filter)
if err != nil {
return nil, err
}
cmgr := dht.host.ConnManager()
rt.PeerAdded = func(p peer.ID) {
commonPrefixLen := kb.CommonPrefixLen(dht.selfKey, kb.ConvertPeerID(p))
if commonPrefixLen < protectedBuckets {
cmgr.Protect(p, kbucketTag)
} else {
cmgr.TagPeer(p, kbucketTag, baseConnMgrScore)
}
}
rt.PeerRemoved = func(p peer.ID) {
cmgr.Unprotect(p, kbucketTag)
cmgr.UntagPeer(p, kbucketTag)
// try to fix the RT
dht.fixRTIfNeeded()
}
return rt, err
}
// ProviderStore returns the provider storage object for storing and retrieving provider records.
func (dht *IpfsDHT) ProviderStore() providers.ProviderStore {
return dht.providerStore
}
// GetRoutingTableDiversityStats returns the diversity stats for the Routing Table.
func (dht *IpfsDHT) GetRoutingTableDiversityStats() []peerdiversity.CplDiversityStats {
return dht.routingTable.GetDiversityStats()
}
// Mode allows introspection of the operation mode of the DHT
func (dht *IpfsDHT) Mode() ModeOpt {
return dht.auto
}
// runFixLowPeersLoop manages simultaneous requests to fixLowPeers
func (dht *IpfsDHT) runFixLowPeersLoop() {
dht.wg.Add(1)
go func() {
defer dht.wg.Done()
dht.fixLowPeers()
ticker := time.NewTicker(periodicBootstrapInterval)
defer ticker.Stop()
for {
select {
case <-dht.fixLowPeersChan:
case <-ticker.C:
case <-dht.ctx.Done():
return
}
dht.fixLowPeers()
}
}()
}
// fixLowPeers tries to get more peers into the routing table if we're below the threshold
func (dht *IpfsDHT) fixLowPeers() {
if dht.routingTable.Size() > minRTRefreshThreshold {
return
}
// we try to add all peers we are connected to to the Routing Table
// in case they aren't already there.
for _, p := range dht.host.Network().Peers() {
dht.peerFound(p)
}
// TODO Active Bootstrapping
// We should first use non-bootstrap peers we knew of from previous
// snapshots of the Routing Table before we connect to the bootstrappers.
// See https://github.com/libp2p/go-libp2p-kad-dht/issues/387.
if dht.routingTable.Size() == 0 && dht.bootstrapPeers != nil {
bootstrapPeers := dht.bootstrapPeers()
if len(bootstrapPeers) == 0 {
// No point in continuing, we have no peers!
return
}
found := 0
for _, i := range rand.Perm(len(bootstrapPeers)) {
ai := bootstrapPeers[i]
err := dht.Host().Connect(dht.ctx, ai)
if err == nil {
found++
} else {
logger.Warnw("failed to bootstrap", "peer", ai.ID, "error", err)
}
// Wait for two bootstrap peers, or try them all.
//
// Why two? In theory, one should be enough
// normally. However, if the network were to
// restart and everyone connected to just one
// bootstrapper, we'll end up with a mostly
// partitioned network.
//
// So we always bootstrap with two random peers.
if found == maxNBoostrappers {
break
}
}
}
// if we still don't have peers in our routing table(probably because Identify hasn't completed),
// there is no point in triggering a Refresh.
if dht.routingTable.Size() == 0 {
return
}
if dht.autoRefresh {
dht.rtRefreshManager.RefreshNoWait()
}
}
// TODO This is hacky, horrible and the programmer needs to have his mother called a hamster.
// SHOULD be removed once https://github.com/libp2p/go-libp2p/issues/800 goes in.
func (dht *IpfsDHT) persistRTPeersInPeerStore() {
tickr := time.NewTicker(peerstore.RecentlyConnectedAddrTTL / 3)
defer tickr.Stop()
for {
select {
case <-tickr.C:
ps := dht.routingTable.ListPeers()
for _, p := range ps {
dht.peerstore.UpdateAddrs(p, peerstore.RecentlyConnectedAddrTTL, peerstore.RecentlyConnectedAddrTTL)
}
case <-dht.ctx.Done():
return
}
}
}
// getLocal attempts to retrieve the value from the datastore.
//
// returns nil, nil when either nothing is found or the value found doesn't properly validate.
// returns nil, some_error when there's a *datastore* error (i.e., something goes very wrong)
func (dht *IpfsDHT) getLocal(ctx context.Context, key string) (*recpb.Record, error) {
logger.Debugw("finding value in datastore", "key", internal.LoggableRecordKeyString(key))
rec, err := dht.getRecordFromDatastore(ctx, mkDsKey(key))
if err != nil {
logger.Warnw("get local failed", "key", internal.LoggableRecordKeyString(key), "error", err)
return nil, err
}
// Double check the key. Can't hurt.
if rec != nil && string(rec.GetKey()) != key {
logger.Errorw("BUG: found a DHT record that didn't match it's key", "expected", internal.LoggableRecordKeyString(key), "got", rec.GetKey())
return nil, nil
}
return rec, nil
}
// putLocal stores the key value pair in the datastore
func (dht *IpfsDHT) putLocal(ctx context.Context, key string, rec *recpb.Record) error {
data, err := proto.Marshal(rec)
if err != nil {
logger.Warnw("failed to put marshal record for local put", "error", err, "key", internal.LoggableRecordKeyString(key))
return err
}
return dht.datastore.Put(ctx, mkDsKey(key), data)
}
func (dht *IpfsDHT) rtPeerLoop() {
dht.wg.Add(1)
go func() {
defer dht.wg.Done()
var bootstrapCount uint
var isBootsrapping bool
var timerCh <-chan time.Time
for {
select {
case <-timerCh:
dht.routingTable.MarkAllPeersIrreplaceable()
case p := <-dht.addPeerToRTChan:
if dht.routingTable.Size() == 0 {
isBootsrapping = true
bootstrapCount = 0
timerCh = nil
}
// queryPeer set to true as we only try to add queried peers to the RT
newlyAdded, err := dht.routingTable.TryAddPeer(p, true, isBootsrapping)
if err != nil {
// peer not added.
continue
}
if newlyAdded {
// peer was added to the RT, it can now be fixed if needed.
dht.fixRTIfNeeded()
} else {
// the peer is already in our RT, but we just successfully queried it and so let's give it a
// bump on the query time so we don't ping it too soon for a liveliness check.
dht.routingTable.UpdateLastSuccessfulOutboundQueryAt(p, time.Now())
}
case <-dht.refreshFinishedCh:
bootstrapCount = bootstrapCount + 1
if bootstrapCount == 2 {
timerCh = time.NewTimer(dht.rtFreezeTimeout).C
}
old := isBootsrapping
isBootsrapping = false
if old {
dht.rtRefreshManager.RefreshNoWait()
}
case <-dht.ctx.Done():
return
}
}
}()
}
// peerFound verifies whether the found peer advertises DHT protocols
// and probe it to make sure it answers DHT queries as expected. If
// it fails to answer, it isn't added to the routingTable.
func (dht *IpfsDHT) peerFound(p peer.ID) {
// if the peer is already in the routing table or the appropriate bucket is
// already full, don't try to add the new peer.ID
if !dht.routingTable.UsefulNewPeer(p) {
return
}
// verify whether the remote peer advertises the right dht protocol
b, err := dht.validRTPeer(p)
if err != nil {
logger.Errorw("failed to validate if peer is a DHT peer", "peer", p, "error", err)
} else if b {
// check if the maximal number of concurrent lookup checks is reached
dht.lookupChecksLk.Lock()
if dht.lookupCheckCapacity == 0 {
dht.lookupChecksLk.Unlock()
// drop the new peer.ID if the maximal number of concurrent lookup
// checks is reached
return
}
dht.lookupCheckCapacity--
dht.lookupChecksLk.Unlock()
go func() {
livelinessCtx, cancel := context.WithTimeout(dht.ctx, dht.lookupCheckTimeout)
defer cancel()
// performing a FIND_NODE query
err := dht.lookupCheck(livelinessCtx, p)
dht.lookupChecksLk.Lock()
dht.lookupCheckCapacity++
dht.lookupChecksLk.Unlock()
if err != nil {
logger.Debugw("connected peer not answering DHT request as expected", "peer", p, "error", err)
return
}
// if the FIND_NODE succeeded, the peer is considered as valid
dht.validPeerFound(p)
}()
}
}
// validPeerFound signals the routingTable that we've found a peer that
// supports the DHT protocol, and just answered correctly to a DHT FindPeers
func (dht *IpfsDHT) validPeerFound(p peer.ID) {
if c := baseLogger.Check(zap.DebugLevel, "peer found"); c != nil {
c.Write(zap.String("peer", p.String()))
}
select {
case dht.addPeerToRTChan <- p:
case <-dht.ctx.Done():
return
}
}
// peerStoppedDHT signals the routing table that a peer is unable to responsd to DHT queries anymore.
func (dht *IpfsDHT) peerStoppedDHT(p peer.ID) {
logger.Debugw("peer stopped dht", "peer", p)
// A peer that does not support the DHT protocol is dead for us.
// There's no point in talking to anymore till it starts supporting the DHT protocol again.
dht.routingTable.RemovePeer(p)
}
func (dht *IpfsDHT) fixRTIfNeeded() {
select {
case dht.fixLowPeersChan <- struct{}{}:
default:
}
}
// FindLocal looks for a peer with a given ID connected to this dht and returns the peer and the table it was found in.
func (dht *IpfsDHT) FindLocal(ctx context.Context, id peer.ID) peer.AddrInfo {
_, span := internal.StartSpan(ctx, "IpfsDHT.FindLocal", trace.WithAttributes(attribute.Stringer("PeerID", id)))
defer span.End()
switch dht.host.Network().Connectedness(id) {
case network.Connected, network.CanConnect:
return dht.peerstore.PeerInfo(id)
default:
return peer.AddrInfo{}
}
}
// nearestPeersToQuery returns the routing tables closest peers.
func (dht *IpfsDHT) nearestPeersToQuery(pmes *pb.Message, count int) []peer.ID {
closer := dht.routingTable.NearestPeers(kb.ConvertKey(string(pmes.GetKey())), count)
return closer
}
// betterPeersToQuery returns nearestPeersToQuery with some additional filtering
func (dht *IpfsDHT) betterPeersToQuery(pmes *pb.Message, from peer.ID, count int) []peer.ID {
closer := dht.nearestPeersToQuery(pmes, count)
// no node? nil
if closer == nil {
logger.Infow("no closer peers to send", from)
return nil
}
filtered := make([]peer.ID, 0, len(closer))
for _, clp := range closer {
// == to self? thats bad
if clp == dht.self {
logger.Error("BUG betterPeersToQuery: attempted to return self! this shouldn't happen...")
return nil
}
// Dont send a peer back themselves
if clp == from {
continue
}
filtered = append(filtered, clp)
}
// ok seems like closer nodes
return filtered
}
func (dht *IpfsDHT) setMode(m mode) error {
dht.modeLk.Lock()
defer dht.modeLk.Unlock()
if m == dht.mode {
return nil
}
switch m {
case modeServer:
return dht.moveToServerMode()
case modeClient:
return dht.moveToClientMode()
default:
return fmt.Errorf("unrecognized dht mode: %d", m)
}
}
// moveToServerMode advertises (via libp2p identify updates) that we are able to respond to DHT queries and sets the appropriate stream handlers.
// Note: We may support responding to queries with protocols aside from our primary ones in order to support
// interoperability with older versions of the DHT protocol.
func (dht *IpfsDHT) moveToServerMode() error {
dht.mode = modeServer
for _, p := range dht.serverProtocols {
dht.host.SetStreamHandler(p, dht.handleNewStream)
}
return nil
}
// moveToClientMode stops advertising (and rescinds advertisements via libp2p identify updates) that we are able to
// respond to DHT queries and removes the appropriate stream handlers. We also kill all inbound streams that were
// utilizing the handled protocols.
// Note: We may support responding to queries with protocols aside from our primary ones in order to support
// interoperability with older versions of the DHT protocol.
func (dht *IpfsDHT) moveToClientMode() error {
dht.mode = modeClient
for _, p := range dht.serverProtocols {
dht.host.RemoveStreamHandler(p)
}
pset := make(map[protocol.ID]bool)
for _, p := range dht.serverProtocols {
pset[p] = true
}
for _, c := range dht.host.Network().Conns() {
for _, s := range c.GetStreams() {
if pset[s.Protocol()] {
if s.Stat().Direction == network.DirInbound {
_ = s.Reset()
}
}
}
}
return nil
}
func (dht *IpfsDHT) getMode() mode {
dht.modeLk.Lock()
defer dht.modeLk.Unlock()
return dht.mode
}
// Context returns the DHT's context.
func (dht *IpfsDHT) Context() context.Context {
return dht.ctx
}
// RoutingTable returns the DHT's routingTable.
func (dht *IpfsDHT) RoutingTable() *kb.RoutingTable {
return dht.routingTable
}
// Close calls Process Close.
func (dht *IpfsDHT) Close() error {
dht.cancel()
dht.wg.Wait()
var wg sync.WaitGroup
closes := [...]func() error{
dht.rtRefreshManager.Close,
dht.providerStore.Close,
}
var errors [len(closes)]error
wg.Add(len(errors))
for i, c := range closes {
go func(i int, c func() error) {
defer wg.Done()
errors[i] = c()
}(i, c)
}
wg.Wait()
return multierr.Combine(errors[:]...)
}
func mkDsKey(s string) ds.Key {
return ds.NewKey(base32.RawStdEncoding.EncodeToString([]byte(s)))
}
// PeerID returns the DHT node's Peer ID.
func (dht *IpfsDHT) PeerID() peer.ID {
return dht.self
}
// PeerKey returns a DHT key, converted from the DHT node's Peer ID.
func (dht *IpfsDHT) PeerKey() []byte {
return kb.ConvertPeerID(dht.self)
}
// Host returns the libp2p host this DHT is operating with.
func (dht *IpfsDHT) Host() host.Host {
return dht.host
}
// Ping sends a ping message to the passed peer and waits for a response.
func (dht *IpfsDHT) Ping(ctx context.Context, p peer.ID) error {
ctx, span := internal.StartSpan(ctx, "IpfsDHT.Ping", trace.WithAttributes(attribute.Stringer("PeerID", p)))
defer span.End()
return dht.protoMessenger.Ping(ctx, p)
}
// NetworkSize returns the most recent estimation of the DHT network size.
// EXPERIMENTAL: We do not provide any guarantees that this method will
// continue to exist in the codebase. Use it at your own risk.
func (dht *IpfsDHT) NetworkSize() (int32, error) {
return dht.nsEstimator.NetworkSize()
}
// newContextWithLocalTags returns a new context.Context with the InstanceID and
// PeerID keys populated. It will also take any extra tags that need adding to
// the context as tag.Mutators.
func (dht *IpfsDHT) newContextWithLocalTags(ctx context.Context, extraTags ...tag.Mutator) context.Context {
extraTags = append(
extraTags,
tag.Upsert(metrics.KeyPeerID, dht.self.String()),
tag.Upsert(metrics.KeyInstanceID, fmt.Sprintf("%p", dht)),
)
ctx, _ = tag.New(
ctx,
extraTags...,
) // ignoring error as it is unrelated to the actual function of this code.
return ctx
}
func (dht *IpfsDHT) maybeAddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
// Don't add addresses for self or our connected peers. We have better ones.
if p == dht.self || dht.host.Network().Connectedness(p) == network.Connected {
return
}
dht.peerstore.AddAddrs(p, dht.filterAddrs(addrs), ttl)
}
func (dht *IpfsDHT) filterAddrs(addrs []ma.Multiaddr) []ma.Multiaddr {
if f := dht.addrFilter; f != nil {
return f(addrs)
}
return addrs
}

View File

@ -0,0 +1,84 @@
package dht
import (
"context"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
)
// DefaultBootstrapPeers is a set of public DHT bootstrap peers provided by libp2p.
var DefaultBootstrapPeers []multiaddr.Multiaddr
// Minimum number of peers in the routing table. If we drop below this and we
// see a new peer, we trigger a bootstrap round.
var minRTRefreshThreshold = 10
const (
periodicBootstrapInterval = 2 * time.Minute
maxNBoostrappers = 2
)
func init() {
for _, s := range []string{
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
"/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", // mars.i.ipfs.io
} {
ma, err := multiaddr.NewMultiaddr(s)
if err != nil {
panic(err)
}
DefaultBootstrapPeers = append(DefaultBootstrapPeers, ma)
}
}
// GetDefaultBootstrapPeerAddrInfos returns the peer.AddrInfos for the default
// bootstrap peers so we can use these for initializing the DHT by passing these to the
// BootstrapPeers(...) option.
func GetDefaultBootstrapPeerAddrInfos() []peer.AddrInfo {
ds := make([]peer.AddrInfo, 0, len(DefaultBootstrapPeers))
for i := range DefaultBootstrapPeers {
info, err := peer.AddrInfoFromP2pAddr(DefaultBootstrapPeers[i])
if err != nil {
logger.Errorw("failed to convert bootstrapper address to peer addr info", "address",
DefaultBootstrapPeers[i].String(), err, "err")
continue
}
ds = append(ds, *info)
}
return ds
}
// Bootstrap tells the DHT to get into a bootstrapped state satisfying the
// IpfsRouter interface.
func (dht *IpfsDHT) Bootstrap(ctx context.Context) (err error) {
_, end := tracer.Bootstrap(dhtName, ctx)
defer func() { end(err) }()
dht.fixRTIfNeeded()
dht.rtRefreshManager.RefreshNoWait()
return nil
}
// RefreshRoutingTable tells the DHT to refresh it's routing tables.
//
// The returned channel will block until the refresh finishes, then yield the
// error and close. The channel is buffered and safe to ignore.
func (dht *IpfsDHT) RefreshRoutingTable() <-chan error {
return dht.rtRefreshManager.Refresh(false)
}
// ForceRefresh acts like RefreshRoutingTable but forces the DHT to refresh all
// buckets in the Routing Table irrespective of when they were last refreshed.
//
// The returned channel will block until the refresh finishes, then yield the
// error and close. The channel is buffered and safe to ignore.
func (dht *IpfsDHT) ForceRefresh() <-chan error {
return dht.rtRefreshManager.Refresh(true)
}

View File

@ -0,0 +1,201 @@
package dht
import (
"context"
"testing"
"time"
kb "github.com/libp2p/go-libp2p-kbucket"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require"
)
func TestSelfWalkOnAddressChange(t *testing.T) {
ctx := context.Background()
// create three DHT instances with auto refresh disabled
d1 := setupDHT(ctx, t, false, DisableAutoRefresh(), forceAddressUpdateProcessing(t))
d2 := setupDHT(ctx, t, false, DisableAutoRefresh())
d3 := setupDHT(ctx, t, false, DisableAutoRefresh())
var connectedTo *IpfsDHT
// connect d1 to whoever is "further"
if kb.CommonPrefixLen(kb.ConvertPeerID(d1.self), kb.ConvertPeerID(d2.self)) <=
kb.CommonPrefixLen(kb.ConvertPeerID(d1.self), kb.ConvertPeerID(d3.self)) {
connect(t, ctx, d1, d3)
connectedTo = d3
} else {
connect(t, ctx, d1, d2)
connectedTo = d2
}
// then connect d2 AND d3
connect(t, ctx, d2, d3)
// d1 should have ONLY 1 peer in it's RT
waitForWellFormedTables(t, []*IpfsDHT{d1}, 1, 1, 2*time.Second)
require.Equal(t, connectedTo.self, d1.routingTable.ListPeers()[0])
// now emit the address change event
em, err := d1.host.EventBus().Emitter(&event.EvtLocalAddressesUpdated{})
require.NoError(t, err)
require.NoError(t, em.Emit(event.EvtLocalAddressesUpdated{}))
waitForWellFormedTables(t, []*IpfsDHT{d1}, 2, 2, 2*time.Second)
// it should now have both peers in the RT
ps := d1.routingTable.ListPeers()
require.Contains(t, ps, d2.self)
require.Contains(t, ps, d3.self)
}
func TestDefaultBootstrappers(t *testing.T) {
ds := GetDefaultBootstrapPeerAddrInfos()
require.NotEmpty(t, ds)
require.Len(t, ds, len(DefaultBootstrapPeers))
dfmap := make(map[peer.ID]peer.AddrInfo)
for _, p := range DefaultBootstrapPeers {
info, err := peer.AddrInfoFromP2pAddr(p)
require.NoError(t, err)
dfmap[info.ID] = *info
}
for _, p := range ds {
inf, ok := dfmap[p.ID]
require.True(t, ok)
require.ElementsMatch(t, p.Addrs, inf.Addrs)
delete(dfmap, p.ID)
}
require.Empty(t, dfmap)
}
func TestBootstrappersReplacable(t *testing.T) {
old := rtFreezeTimeout
rtFreezeTimeout = 100 * time.Millisecond
defer func() {
rtFreezeTimeout = old
}()
ctx := context.Background()
d := setupDHT(ctx, t, false, disableFixLowPeersRoutine(t), BucketSize(2))
defer d.host.Close()
defer d.Close()
var d1 *IpfsDHT
var d2 *IpfsDHT
// d1 & d2 have a cpl of 0
for {
d1 = setupDHT(ctx, t, false, disableFixLowPeersRoutine(t))
if kb.CommonPrefixLen(d.selfKey, d1.selfKey) == 0 {
break
}
}
for {
d2 = setupDHT(ctx, t, false, disableFixLowPeersRoutine(t))
if kb.CommonPrefixLen(d.selfKey, d2.selfKey) == 0 {
break
}
}
defer d1.host.Close()
defer d1.Close()
defer d2.host.Close()
defer d2.Close()
connect(t, ctx, d, d1)
connect(t, ctx, d, d2)
require.Len(t, d.routingTable.ListPeers(), 2)
// d3 & d4 with cpl=0 will go in as d1 & d2 are replacable.
var d3 *IpfsDHT
var d4 *IpfsDHT
for {
d3 = setupDHT(ctx, t, false, disableFixLowPeersRoutine(t))
if kb.CommonPrefixLen(d.selfKey, d3.selfKey) == 0 {
break
}
}
for {
d4 = setupDHT(ctx, t, false, disableFixLowPeersRoutine(t))
if kb.CommonPrefixLen(d.selfKey, d4.selfKey) == 0 {
break
}
}
defer d3.host.Close()
defer d3.Close()
defer d4.host.Close()
defer d4.Close()
connect(t, ctx, d, d3)
connect(t, ctx, d, d4)
require.Len(t, d.routingTable.ListPeers(), 2)
require.Contains(t, d.routingTable.ListPeers(), d3.self)
require.Contains(t, d.routingTable.ListPeers(), d4.self)
// do couple of refreshes and wait for the Routing Table to be "frozen".
<-d.RefreshRoutingTable()
<-d.RefreshRoutingTable()
time.Sleep(1 * time.Second)
// adding d5 fails because RT is frozen
var d5 *IpfsDHT
for {
d5 = setupDHT(ctx, t, false, disableFixLowPeersRoutine(t))
if kb.CommonPrefixLen(d.selfKey, d5.selfKey) == 0 {
break
}
}
defer d5.host.Close()
defer d5.Close()
connectNoSync(t, ctx, d, d5)
time.Sleep(500 * time.Millisecond)
require.Len(t, d.routingTable.ListPeers(), 2)
require.Contains(t, d.routingTable.ListPeers(), d3.self)
require.Contains(t, d.routingTable.ListPeers(), d4.self)
// Let's empty the routing table
for _, p := range d.routingTable.ListPeers() {
d.routingTable.RemovePeer(p)
}
require.Len(t, d.routingTable.ListPeers(), 0)
// adding d1 & d2 works now because there is space in the Routing Table
require.NoError(t, d.host.Network().ClosePeer(d1.self))
require.NoError(t, d.host.Network().ClosePeer(d2.self))
connect(t, ctx, d, d1)
connect(t, ctx, d, d2)
require.Len(t, d.routingTable.ListPeers(), 2)
require.Contains(t, d.routingTable.ListPeers(), d1.self)
require.Contains(t, d.routingTable.ListPeers(), d2.self)
// adding d3 & d4 also works because the RT is not frozen.
require.NoError(t, d.host.Network().ClosePeer(d3.self))
require.NoError(t, d.host.Network().ClosePeer(d4.self))
connect(t, ctx, d, d3)
connect(t, ctx, d, d4)
require.Len(t, d.routingTable.ListPeers(), 2)
require.Contains(t, d.routingTable.ListPeers(), d3.self)
require.Contains(t, d.routingTable.ListPeers(), d4.self)
// run refreshes and freeze the RT
<-d.RefreshRoutingTable()
<-d.RefreshRoutingTable()
time.Sleep(1 * time.Second)
// cant add d1 & d5 because RT is frozen.
require.NoError(t, d.host.Network().ClosePeer(d1.self))
require.NoError(t, d.host.Network().ClosePeer(d5.self))
connectNoSync(t, ctx, d, d1)
connectNoSync(t, ctx, d, d5)
d.peerFound(d5.self)
d.peerFound(d1.self)
time.Sleep(1 * time.Second)
require.Len(t, d.routingTable.ListPeers(), 2)
require.Contains(t, d.routingTable.ListPeers(), d3.self)
require.Contains(t, d.routingTable.ListPeers(), d4.self)
}

View File

@ -0,0 +1,243 @@
package dht
import (
"bytes"
"net"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/google/gopacket/routing"
netroute "github.com/libp2p/go-netroute"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
dhtcfg "github.com/libp2p/go-libp2p-kad-dht/internal/config"
)
// QueryFilterFunc is a filter applied when considering peers to dial when querying
type QueryFilterFunc = dhtcfg.QueryFilterFunc
// RouteTableFilterFunc is a filter applied when considering connections to keep in
// the local route table.
type RouteTableFilterFunc = dhtcfg.RouteTableFilterFunc
var publicCIDR6 = "2000::/3"
var public6 *net.IPNet
func init() {
_, public6, _ = net.ParseCIDR(publicCIDR6)
}
// isPublicAddr follows the logic of manet.IsPublicAddr, except it uses
// a stricter definition of "public" for ipv6: namely "is it in 2000::/3"?
func isPublicAddr(a ma.Multiaddr) bool {
ip, err := manet.ToIP(a)
if err != nil {
return false
}
if ip.To4() != nil {
return !inAddrRange(ip, manet.Private4) && !inAddrRange(ip, manet.Unroutable4)
}
return public6.Contains(ip)
}
// isPrivateAddr follows the logic of manet.IsPrivateAddr, except that
// it uses a stricter definition of "public" for ipv6
func isPrivateAddr(a ma.Multiaddr) bool {
ip, err := manet.ToIP(a)
if err != nil {
return false
}
if ip.To4() != nil {
return inAddrRange(ip, manet.Private4)
}
return !public6.Contains(ip) && !inAddrRange(ip, manet.Unroutable6)
}
// PublicQueryFilter returns true if the peer is suspected of being publicly accessible
func PublicQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
if len(ai.Addrs) == 0 {
return false
}
var hasPublicAddr bool
for _, a := range ai.Addrs {
if !isRelayAddr(a) && isPublicAddr(a) {
hasPublicAddr = true
}
}
return hasPublicAddr
}
type hasHost interface {
Host() host.Host
}
var _ QueryFilterFunc = PublicQueryFilter
// PublicRoutingTableFilter allows a peer to be added to the routing table if the connections to that peer indicate
// that it is on a public network
func PublicRoutingTableFilter(dht interface{}, p peer.ID) bool {
d := dht.(hasHost)
conns := d.Host().Network().ConnsToPeer(p)
if len(conns) == 0 {
return false
}
// Do we have a public address for this peer?
id := conns[0].RemotePeer()
known := d.Host().Peerstore().PeerInfo(id)
for _, a := range known.Addrs {
if !isRelayAddr(a) && isPublicAddr(a) {
return true
}
}
return false
}
var _ RouteTableFilterFunc = PublicRoutingTableFilter
// PrivateQueryFilter doens't currently restrict which peers we are willing to query from the local DHT.
func PrivateQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
return len(ai.Addrs) > 0
}
var _ QueryFilterFunc = PrivateQueryFilter
// We call this very frequently but routes can technically change at runtime.
// Cache it for two minutes.
const routerCacheTime = 2 * time.Minute
var routerCache struct {
sync.RWMutex
router routing.Router
expires time.Time
}
func getCachedRouter() routing.Router {
routerCache.RLock()
router := routerCache.router
expires := routerCache.expires
routerCache.RUnlock()
if time.Now().Before(expires) {
return router
}
routerCache.Lock()
defer routerCache.Unlock()
now := time.Now()
if now.Before(routerCache.expires) {
return router
}
routerCache.router, _ = netroute.New()
routerCache.expires = now.Add(routerCacheTime)
return router
}
// PrivateRoutingTableFilter allows a peer to be added to the routing table if the connections to that peer indicate
// that it is on a private network
func PrivateRoutingTableFilter(dht interface{}, p peer.ID) bool {
d := dht.(hasHost)
conns := d.Host().Network().ConnsToPeer(p)
return privRTFilter(d, conns)
}
func privRTFilter(dht interface{}, conns []network.Conn) bool {
d := dht.(hasHost)
h := d.Host()
router := getCachedRouter()
myAdvertisedIPs := make([]net.IP, 0)
for _, a := range h.Addrs() {
if isPublicAddr(a) && !isRelayAddr(a) {
ip, err := manet.ToIP(a)
if err != nil {
continue
}
myAdvertisedIPs = append(myAdvertisedIPs, ip)
}
}
for _, c := range conns {
ra := c.RemoteMultiaddr()
if isPrivateAddr(ra) && !isRelayAddr(ra) {
return true
}
if isPublicAddr(ra) {
ip, err := manet.ToIP(ra)
if err != nil {
continue
}
// if the ip is the same as one of the local host's public advertised IPs - then consider it local
for _, i := range myAdvertisedIPs {
if i.Equal(ip) {
return true
}
if ip.To4() == nil {
if i.To4() == nil && isEUI(ip) && sameV6Net(i, ip) {
return true
}
}
}
// if there's no gateway - a direct host in the OS routing table - then consider it local
// This is relevant in particular to ipv6 networks where the addresses may all be public,
// but the nodes are aware of direct links between each other.
if router != nil {
_, gw, _, err := router.Route(ip)
if gw == nil && err == nil {
return true
}
}
}
}
return false
}
var _ RouteTableFilterFunc = PrivateRoutingTableFilter
func isEUI(ip net.IP) bool {
// per rfc 2373
return len(ip) == net.IPv6len && ip[11] == 0xff && ip[12] == 0xfe
}
func sameV6Net(a, b net.IP) bool {
//lint:ignore SA1021 We're comparing only parts of the IP address here.
return len(a) == net.IPv6len && len(b) == net.IPv6len && bytes.Equal(a[0:8], b[0:8]) //nolint
}
func isRelayAddr(a ma.Multiaddr) bool {
found := false
ma.ForEach(a, func(c ma.Component, e error) bool {
if e != nil {
return false
}
found = c.Protocol().Code == ma.P_CIRCUIT
return !found
})
return found
}
func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool {
for _, ipnet := range ipnets {
if ipnet.Contains(ip) {
return true
}
}
return false
}

View File

@ -0,0 +1,79 @@
package dht
import (
"context"
"net"
"sync/atomic"
"testing"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
func TestIsRelay(t *testing.T) {
a, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5002/p2p/QmdPU7PfRyKehdrP5A3WqmjyD6bhVpU1mLGKppa2FjGDjZ/p2p-circuit/p2p/QmVT6GYwjeeAF5TR485Yc58S3xRF5EFsZ5YAF4VcP3URHt")
if !isRelayAddr(a) {
t.Fatalf("thought %s was not a relay", a)
}
a, _ = ma.NewMultiaddr("/p2p-circuit/p2p/QmVT6GYwjeeAF5TR485Yc58S3xRF5EFsZ5YAF4VcP3URHt")
if !isRelayAddr(a) {
t.Fatalf("thought %s was not a relay", a)
}
a, _ = ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5002/p2p/QmdPU7PfRyKehdrP5A3WqmjyD6bhVpU1mLGKppa2FjGDjZ")
if isRelayAddr(a) {
t.Fatalf("thought %s was a relay", a)
}
}
type mockConn struct {
local peer.AddrInfo
remote peer.AddrInfo
isClosed atomic.Bool
}
var _ network.Conn = (*mockConn)(nil)
func (m *mockConn) ID() string { return "0" }
func (m *mockConn) Close() error {
m.isClosed.Store(true)
return nil
}
func (m *mockConn) NewStream(context.Context) (network.Stream, error) { return nil, nil }
func (m *mockConn) GetStreams() []network.Stream { return []network.Stream{} }
func (m *mockConn) Stat() network.ConnStats {
return network.ConnStats{Stats: network.Stats{Direction: network.DirOutbound}}
}
func (m *mockConn) Scope() network.ConnScope { return &network.NullScope{} }
func (m *mockConn) LocalMultiaddr() ma.Multiaddr { return m.local.Addrs[0] }
func (m *mockConn) RemoteMultiaddr() ma.Multiaddr { return m.remote.Addrs[0] }
func (m *mockConn) LocalPeer() peer.ID { return m.local.ID }
func (m *mockConn) LocalPrivateKey() ic.PrivKey { return nil }
func (m *mockConn) RemotePeer() peer.ID { return m.remote.ID }
func (m *mockConn) RemotePublicKey() ic.PubKey { return nil }
func (m *mockConn) ConnState() network.ConnectionState { return network.ConnectionState{} }
func (m *mockConn) IsClosed() bool { return m.isClosed.Load() }
func TestFilterCaching(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := setupDHT(ctx, t, true)
remote, _ := manet.FromIP(net.IPv4(8, 8, 8, 8))
if privRTFilter(d, []network.Conn{&mockConn{
local: d.Host().Peerstore().PeerInfo(d.Host().ID()),
remote: peer.AddrInfo{ID: "", Addrs: []ma.Multiaddr{remote}},
}}) {
t.Fatal("filter should prevent public remote peers.")
}
r1 := getCachedRouter()
r2 := getCachedRouter()
if r1 != r2 {
t.Fatal("router should be returned multiple times.")
}
}

View File

@ -0,0 +1,166 @@
package dht
import (
"io"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p-kad-dht/internal/net"
"github.com/libp2p/go-libp2p-kad-dht/metrics"
pb "github.com/libp2p/go-libp2p-kad-dht/pb"
"github.com/libp2p/go-msgio"
"go.opencensus.io/stats"
"go.opencensus.io/tag"
"go.uber.org/zap"
)
var dhtStreamIdleTimeout = 1 * time.Minute
// ErrReadTimeout is an error that occurs when no message is read within the timeout period.
var ErrReadTimeout = net.ErrReadTimeout
// handleNewStream implements the network.StreamHandler
func (dht *IpfsDHT) handleNewStream(s network.Stream) {
if dht.handleNewMessage(s) {
// If we exited without error, close gracefully.
_ = s.Close()
} else {
// otherwise, send an error.
_ = s.Reset()
}
}
// Returns true on orderly completion of writes (so we can Close the stream).
func (dht *IpfsDHT) handleNewMessage(s network.Stream) bool {
ctx := dht.ctx
r := msgio.NewVarintReaderSize(s, network.MessageSizeMax)
mPeer := s.Conn().RemotePeer()
timer := time.AfterFunc(dhtStreamIdleTimeout, func() { _ = s.Reset() })
defer timer.Stop()
for {
if dht.getMode() != modeServer {
logger.Debugf("ignoring incoming dht message while not in server mode")
return false
}
var req pb.Message
msgbytes, err := r.ReadMsg()
msgLen := len(msgbytes)
if err != nil {
r.ReleaseMsg(msgbytes)
if err == io.EOF {
return true
}
// This string test is necessary because there isn't a single stream reset error
// instance in use.
if c := baseLogger.Check(zap.DebugLevel, "error reading message"); c != nil && err.Error() != "stream reset" {
c.Write(zap.String("from", mPeer.String()),
zap.Error(err))
}
if msgLen > 0 {
_ = stats.RecordWithTags(ctx,
[]tag.Mutator{tag.Upsert(metrics.KeyMessageType, "UNKNOWN")},
metrics.ReceivedMessages.M(1),
metrics.ReceivedMessageErrors.M(1),
metrics.ReceivedBytes.M(int64(msgLen)),
)
}
return false
}
err = req.Unmarshal(msgbytes)
r.ReleaseMsg(msgbytes)
if err != nil {
if c := baseLogger.Check(zap.DebugLevel, "error unmarshaling message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Error(err))
}
_ = stats.RecordWithTags(ctx,
[]tag.Mutator{tag.Upsert(metrics.KeyMessageType, "UNKNOWN")},
metrics.ReceivedMessages.M(1),
metrics.ReceivedMessageErrors.M(1),
metrics.ReceivedBytes.M(int64(msgLen)),
)
return false
}
timer.Reset(dhtStreamIdleTimeout)
startTime := time.Now()
ctx, _ := tag.New(ctx,
tag.Upsert(metrics.KeyMessageType, req.GetType().String()),
)
stats.Record(ctx,
metrics.ReceivedMessages.M(1),
metrics.ReceivedBytes.M(int64(msgLen)),
)
handler := dht.handlerForMsgType(req.GetType())
if handler == nil {
stats.Record(ctx, metrics.ReceivedMessageErrors.M(1))
if c := baseLogger.Check(zap.DebugLevel, "can't handle received message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())))
}
return false
}
if c := baseLogger.Check(zap.DebugLevel, "handling message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())),
zap.Binary("key", req.GetKey()))
}
resp, err := handler(ctx, mPeer, &req)
if err != nil {
stats.Record(ctx, metrics.ReceivedMessageErrors.M(1))
if c := baseLogger.Check(zap.DebugLevel, "error handling message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())),
zap.Binary("key", req.GetKey()),
zap.Error(err))
}
return false
}
if c := baseLogger.Check(zap.DebugLevel, "handled message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())),
zap.Binary("key", req.GetKey()),
zap.Duration("time", time.Since(startTime)))
}
if resp == nil {
continue
}
// send out response msg
err = net.WriteMsg(s, resp)
if err != nil {
stats.Record(ctx, metrics.ReceivedMessageErrors.M(1))
if c := baseLogger.Check(zap.DebugLevel, "error writing response"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())),
zap.Binary("key", req.GetKey()),
zap.Error(err))
}
return false
}
elapsedTime := time.Since(startTime)
if c := baseLogger.Check(zap.DebugLevel, "responded to message"); c != nil {
c.Write(zap.String("from", mPeer.String()),
zap.Int32("type", int32(req.GetType())),
zap.Binary("key", req.GetKey()),
zap.Duration("time", elapsedTime))
}
latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
stats.Record(ctx, metrics.InboundRequestLatency.M(latencyMillis))
}
}

View File

@ -0,0 +1,358 @@
package dht
import (
"fmt"
"testing"
"time"
dhtcfg "github.com/libp2p/go-libp2p-kad-dht/internal/config"
"github.com/libp2p/go-libp2p-kad-dht/providers"
"github.com/libp2p/go-libp2p-kbucket/peerdiversity"
record "github.com/libp2p/go-libp2p-record"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
ds "github.com/ipfs/go-datastore"
ma "github.com/multiformats/go-multiaddr"
)
// ModeOpt describes what mode the dht should operate in
type ModeOpt = dhtcfg.ModeOpt
const (
// ModeAuto utilizes EvtLocalReachabilityChanged events sent over the event bus to dynamically switch the DHT
// between Client and Server modes based on network conditions
ModeAuto ModeOpt = iota
// ModeClient operates the DHT as a client only, it cannot respond to incoming queries
ModeClient
// ModeServer operates the DHT as a server, it can both send and respond to queries
ModeServer
// ModeAutoServer operates in the same way as ModeAuto, but acts as a server when reachability is unknown
ModeAutoServer
)
// DefaultPrefix is the application specific prefix attached to all DHT protocols by default.
const DefaultPrefix protocol.ID = "/ipfs"
type Option = dhtcfg.Option
// ProviderStore sets the provider storage manager.
func ProviderStore(ps providers.ProviderStore) Option {
return func(c *dhtcfg.Config) error {
c.ProviderStore = ps
return nil
}
}
// RoutingTableLatencyTolerance sets the maximum acceptable latency for peers
// in the routing table's cluster.
func RoutingTableLatencyTolerance(latency time.Duration) Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.LatencyTolerance = latency
return nil
}
}
// RoutingTableRefreshQueryTimeout sets the timeout for routing table refresh
// queries.
func RoutingTableRefreshQueryTimeout(timeout time.Duration) Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.RefreshQueryTimeout = timeout
return nil
}
}
// RoutingTableRefreshPeriod sets the period for refreshing buckets in the
// routing table. The DHT will refresh buckets every period by:
//
// 1. First searching for nearby peers to figure out how many buckets we should try to fill.
// 1. Then searching for a random key in each bucket that hasn't been queried in
// the last refresh period.
func RoutingTableRefreshPeriod(period time.Duration) Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.RefreshInterval = period
return nil
}
}
// Datastore configures the DHT to use the specified datastore.
//
// Defaults to an in-memory (temporary) map.
func Datastore(ds ds.Batching) Option {
return func(c *dhtcfg.Config) error {
c.Datastore = ds
return nil
}
}
// Mode configures which mode the DHT operates in (Client, Server, Auto).
//
// Defaults to ModeAuto.
func Mode(m ModeOpt) Option {
return func(c *dhtcfg.Config) error {
c.Mode = m
return nil
}
}
// Validator configures the DHT to use the specified validator.
//
// Defaults to a namespaced validator that can validate both public key (under the "pk"
// namespace) and IPNS records (under the "ipns" namespace). Setting the validator
// implies that the user wants to control the validators and therefore the default
// public key and IPNS validators will not be added.
func Validator(v record.Validator) Option {
return func(c *dhtcfg.Config) error {
c.Validator = v
c.ValidatorChanged = true
return nil
}
}
// NamespacedValidator adds a validator namespaced under `ns`. This option fails
// if the DHT is not using a `record.NamespacedValidator` as its validator (it
// uses one by default but this can be overridden with the `Validator` option).
// Adding a namespaced validator without changing the `Validator` will result in
// adding a new validator in addition to the default public key and IPNS validators.
// The "pk" and "ipns" namespaces cannot be overridden here unless a new `Validator`
// has been set first.
//
// Example: Given a validator registered as `NamespacedValidator("ipns",
// myValidator)`, all records with keys starting with `/ipns/` will be validated
// with `myValidator`.
func NamespacedValidator(ns string, v record.Validator) Option {
return func(c *dhtcfg.Config) error {
nsval, ok := c.Validator.(record.NamespacedValidator)
if !ok {
return fmt.Errorf("can only add namespaced validators to a NamespacedValidator")
}
nsval[ns] = v
return nil
}
}
// ProtocolPrefix sets an application specific prefix to be attached to all DHT protocols. For example,
// /myapp/kad/1.0.0 instead of /ipfs/kad/1.0.0. Prefix should be of the form /myapp.
//
// Defaults to dht.DefaultPrefix
func ProtocolPrefix(prefix protocol.ID) Option {
return func(c *dhtcfg.Config) error {
c.ProtocolPrefix = prefix
return nil
}
}
// ProtocolExtension adds an application specific protocol to the DHT protocol. For example,
// /ipfs/lan/kad/1.0.0 instead of /ipfs/kad/1.0.0. extension should be of the form /lan.
func ProtocolExtension(ext protocol.ID) Option {
return func(c *dhtcfg.Config) error {
c.ProtocolPrefix += ext
return nil
}
}
// V1ProtocolOverride overrides the protocolID used for /kad/1.0.0 with another. This is an
// advanced feature, and should only be used to handle legacy networks that have not been
// using protocolIDs of the form /app/kad/1.0.0.
//
// This option will override and ignore the ProtocolPrefix and ProtocolExtension options
func V1ProtocolOverride(proto protocol.ID) Option {
return func(c *dhtcfg.Config) error {
c.V1ProtocolOverride = proto
return nil
}
}
// BucketSize configures the bucket size (k in the Kademlia paper) of the routing table.
//
// The default value is 20.
func BucketSize(bucketSize int) Option {
return func(c *dhtcfg.Config) error {
c.BucketSize = bucketSize
return nil
}
}
// Concurrency configures the number of concurrent requests (alpha in the Kademlia paper) for a given query path.
//
// The default value is 10.
func Concurrency(alpha int) Option {
return func(c *dhtcfg.Config) error {
c.Concurrency = alpha
return nil
}
}
// Resiliency configures the number of peers closest to a target that must have responded in order for a given query
// path to complete.
//
// The default value is 3.
func Resiliency(beta int) Option {
return func(c *dhtcfg.Config) error {
c.Resiliency = beta
return nil
}
}
// LookupInterval configures maximal number of go routines that can be used to
// perform a lookup check operation, before adding a new node to the routing table.
func LookupCheckConcurrency(n int) Option {
return func(c *dhtcfg.Config) error {
c.LookupCheckConcurrency = n
return nil
}
}
// MaxRecordAge specifies the maximum time that any node will hold onto a record ("PutValue record")
// from the time its received. This does not apply to any other forms of validity that
// the record may contain.
// For example, a record may contain an ipns entry with an EOL saying its valid
// until the year 2020 (a great time in the future). For that record to stick around
// it must be rebroadcasted more frequently than once every 'MaxRecordAge'
func MaxRecordAge(maxAge time.Duration) Option {
return func(c *dhtcfg.Config) error {
c.MaxRecordAge = maxAge
return nil
}
}
// DisableAutoRefresh completely disables 'auto-refresh' on the DHT routing
// table. This means that we will neither refresh the routing table periodically
// nor when the routing table size goes below the minimum threshold.
func DisableAutoRefresh() Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.AutoRefresh = false
return nil
}
}
// DisableProviders disables storing and retrieving provider records.
//
// Defaults to enabled.
//
// WARNING: do not change this unless you're using a forked DHT (i.e., a private
// network and/or distinct DHT protocols with the `Protocols` option).
func DisableProviders() Option {
return func(c *dhtcfg.Config) error {
c.EnableProviders = false
return nil
}
}
// DisableValues disables storing and retrieving value records (including
// public keys).
//
// Defaults to enabled.
//
// WARNING: do not change this unless you're using a forked DHT (i.e., a private
// network and/or distinct DHT protocols with the `Protocols` option).
func DisableValues() Option {
return func(c *dhtcfg.Config) error {
c.EnableValues = false
return nil
}
}
// QueryFilter sets a function that approves which peers may be dialed in a query
func QueryFilter(filter QueryFilterFunc) Option {
return func(c *dhtcfg.Config) error {
c.QueryPeerFilter = filter
return nil
}
}
// RoutingTableFilter sets a function that approves which peers may be added to the routing table. The host should
// already have at least one connection to the peer under consideration.
func RoutingTableFilter(filter RouteTableFilterFunc) Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.PeerFilter = filter
return nil
}
}
// BootstrapPeers configures the bootstrapping nodes that we will connect to to seed
// and refresh our Routing Table if it becomes empty.
func BootstrapPeers(bootstrappers ...peer.AddrInfo) Option {
return func(c *dhtcfg.Config) error {
c.BootstrapPeers = func() []peer.AddrInfo {
return bootstrappers
}
return nil
}
}
// BootstrapPeersFunc configures the function that returns the bootstrapping nodes that we will
// connect to to seed and refresh our Routing Table if it becomes empty.
func BootstrapPeersFunc(getBootstrapPeers func() []peer.AddrInfo) Option {
return func(c *dhtcfg.Config) error {
c.BootstrapPeers = getBootstrapPeers
return nil
}
}
// RoutingTablePeerDiversityFilter configures the implementation of the `PeerIPGroupFilter` that will be used
// to construct the diversity filter for the Routing Table.
// Please see the docs for `peerdiversity.PeerIPGroupFilter` AND `peerdiversity.Filter` for more details.
func RoutingTablePeerDiversityFilter(pg peerdiversity.PeerIPGroupFilter) Option {
return func(c *dhtcfg.Config) error {
c.RoutingTable.DiversityFilter = pg
return nil
}
}
// disableFixLowPeersRoutine disables the "fixLowPeers" routine in the DHT.
// This is ONLY for tests.
func disableFixLowPeersRoutine(t *testing.T) Option {
return func(c *dhtcfg.Config) error {
c.DisableFixLowPeers = true
return nil
}
}
// forceAddressUpdateProcessing forces the DHT to handle changes to the hosts addresses.
// This occurs even when AutoRefresh has been disabled.
// This is ONLY for tests.
func forceAddressUpdateProcessing(t *testing.T) Option {
return func(c *dhtcfg.Config) error {
c.TestAddressUpdateProcessing = true
return nil
}
}
// EnableOptimisticProvide enables an optimization that skips the last hops of the provide process.
// This works by using the network size estimator (which uses the keyspace density of queries)
// to optimistically send ADD_PROVIDER requests when we most likely have found the last hop.
// It will also run some ADD_PROVIDER requests asynchronously in the background after returning,
// this allows to optimistically return earlier if some threshold number of RPCs have succeeded.
// The number of background/in-flight queries can be configured with the OptimisticProvideJobsPoolSize
// option.
//
// EXPERIMENTAL: This is an experimental option and might be removed in the future. Use at your own risk.
func EnableOptimisticProvide() Option {
return func(c *dhtcfg.Config) error {
c.EnableOptimisticProvide = true
return nil
}
}
// OptimisticProvideJobsPoolSize allows to configure the asynchronicity limit for in-flight ADD_PROVIDER RPCs.
// It makes sense to set it to a multiple of optProvReturnRatio * BucketSize. Check the description of
// EnableOptimisticProvide for more details.
//
// EXPERIMENTAL: This is an experimental option and might be removed in the future. Use at your own risk.
func OptimisticProvideJobsPoolSize(size int) Option {
return func(c *dhtcfg.Config) error {
c.OptimisticProvideJobsPoolSize = size
return nil
}
}
// AddressFilter allows to configure the address filtering function.
// This function is run before addresses are added to the peerstore.
// It is most useful to avoid adding localhost / local addresses.
func AddressFilter(f func([]ma.Multiaddr) []ma.Multiaddr) Option {
return func(c *dhtcfg.Config) error {
c.AddressFilter = f
return nil
}
}

File diff suppressed because it is too large Load Diff

3
go-libp2p-kad-dht/doc.go Normal file
View File

@ -0,0 +1,3 @@
// Package dht implements a distributed hash table that satisfies the ipfs routing
// interface. This DHT is modeled after kademlia with S/Kademlia modifications.
package dht

View File

@ -0,0 +1,394 @@
// Package dual provides an implementation of a split or "dual" dht, where two parallel instances
// are maintained for the global internet and the local LAN respectively.
package dual
import (
"context"
"fmt"
"sync"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p-kad-dht/internal"
"github.com/libp2p/go-libp2p-routing-helpers/tracing"
"github.com/ipfs/go-cid"
kb "github.com/libp2p/go-libp2p-kbucket"
"github.com/libp2p/go-libp2p-kbucket/peerdiversity"
helper "github.com/libp2p/go-libp2p-routing-helpers"
ci "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/routing"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/hashicorp/go-multierror"
)
const tracer = tracing.Tracer("go-libp2p-kad-dht/dual")
const dualName = "Dual"
// DHT implements the routing interface to provide two concrete DHT implementationts for use
// in IPFS that are used to support both global network users and disjoint LAN usecases.
type DHT struct {
WAN *dht.IpfsDHT
LAN *dht.IpfsDHT
}
// LanExtension is used to differentiate local protocol requests from those on the WAN DHT.
const LanExtension protocol.ID = "/lan"
// Assert that IPFS assumptions about interfaces aren't broken. These aren't a
// guarantee, but we can use them to aid refactoring.
var (
_ routing.ContentRouting = (*DHT)(nil)
_ routing.Routing = (*DHT)(nil)
_ routing.PeerRouting = (*DHT)(nil)
_ routing.PubKeyFetcher = (*DHT)(nil)
_ routing.ValueStore = (*DHT)(nil)
)
var (
maxPrefixCountPerCpl = 2
maxPrefixCount = 3
)
type config struct {
wan, lan []dht.Option
}
func (cfg *config) apply(opts ...Option) error {
for i, o := range opts {
if err := o(cfg); err != nil {
return fmt.Errorf("dual dht option %d failed: %w", i, err)
}
}
return nil
}
// Option is an option used to configure the Dual DHT.
type Option func(*config) error
// WanDHTOption applies the given DHT options to the WAN DHT.
func WanDHTOption(opts ...dht.Option) Option {
return func(c *config) error {
c.wan = append(c.wan, opts...)
return nil
}
}
// LanDHTOption applies the given DHT options to the LAN DHT.
func LanDHTOption(opts ...dht.Option) Option {
return func(c *config) error {
c.lan = append(c.lan, opts...)
return nil
}
}
// DHTOption applies the given DHT options to both the WAN and the LAN DHTs.
func DHTOption(opts ...dht.Option) Option {
return func(c *config) error {
c.lan = append(c.lan, opts...)
c.wan = append(c.wan, opts...)
return nil
}
}
// New creates a new DualDHT instance. Options provided are forwarded on to the two concrete
// IpfsDHT internal constructions, modulo additional options used by the Dual DHT to enforce
// the LAN-vs-WAN distinction.
// Note: query or routing table functional options provided as arguments to this function
// will be overriden by this constructor.
func New(ctx context.Context, h host.Host, options ...Option) (*DHT, error) {
var cfg config
err := cfg.apply(
WanDHTOption(
dht.QueryFilter(dht.PublicQueryFilter),
dht.RoutingTableFilter(dht.PublicRoutingTableFilter),
dht.RoutingTablePeerDiversityFilter(dht.NewRTPeerDiversityFilter(h, maxPrefixCountPerCpl, maxPrefixCount)),
// filter out all private addresses
dht.AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool { is, err := manet.IsPublicAddr(a); return is && err == nil })
}),
),
)
if err != nil {
return nil, err
}
err = cfg.apply(
LanDHTOption(
dht.ProtocolExtension(LanExtension),
dht.QueryFilter(dht.PrivateQueryFilter),
dht.RoutingTableFilter(dht.PrivateRoutingTableFilter),
// filter out localhost IP addresses
dht.AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool { return !manet.IsIPLoopback(a) })
}),
),
)
if err != nil {
return nil, err
}
err = cfg.apply(options...)
if err != nil {
return nil, err
}
wan, err := dht.New(ctx, h, cfg.wan...)
if err != nil {
return nil, err
}
// Unless overridden by user supplied options, the LAN DHT should default
// to 'AutoServer' mode.
if wan.Mode() != dht.ModeClient {
cfg.lan = append(cfg.lan, dht.Mode(dht.ModeServer))
}
lan, err := dht.New(ctx, h, cfg.lan...)
if err != nil {
return nil, err
}
impl := DHT{wan, lan}
return &impl, nil
}
// Close closes the DHT context.
func (dht *DHT) Close() error {
return combineErrors(dht.WAN.Close(), dht.LAN.Close())
}
// WANActive returns true when the WAN DHT is active (has peers).
func (dht *DHT) WANActive() bool {
return dht.WAN.RoutingTable().Size() > 0
}
// Provide adds the given cid to the content routing system.
func (dht *DHT) Provide(ctx context.Context, key cid.Cid, announce bool) (err error) {
ctx, end := tracer.Provide(dualName, ctx, key, announce)
defer func() { end(err) }()
if dht.WANActive() {
return dht.WAN.Provide(ctx, key, announce)
}
return dht.LAN.Provide(ctx, key, announce)
}
// GetRoutingTableDiversityStats fetches the Routing Table Diversity Stats.
func (dht *DHT) GetRoutingTableDiversityStats() []peerdiversity.CplDiversityStats {
if dht.WANActive() {
return dht.WAN.GetRoutingTableDiversityStats()
}
return nil
}
// FindProvidersAsync searches for peers who are able to provide a given key
func (dht *DHT) FindProvidersAsync(ctx context.Context, key cid.Cid, count int) (ch <-chan peer.AddrInfo) {
ctx, end := tracer.FindProvidersAsync(dualName, ctx, key, count)
defer func() { ch = end(ch, nil) }()
reqCtx, cancel := context.WithCancel(ctx)
outCh := make(chan peer.AddrInfo)
// Register for and merge query events if we care about them.
subCtx := reqCtx
var evtCh <-chan *routing.QueryEvent
if routing.SubscribesToQueryEvents(ctx) {
subCtx, evtCh = routing.RegisterForQueryEvents(reqCtx)
}
subCtx, span := internal.StartSpan(subCtx, "Dual.worker")
wanCh := dht.WAN.FindProvidersAsync(subCtx, key, count)
lanCh := dht.LAN.FindProvidersAsync(subCtx, key, count)
zeroCount := (count == 0)
go func() {
defer span.End()
defer cancel()
defer close(outCh)
found := make(map[peer.ID]struct{}, count)
var pi peer.AddrInfo
var qEv *routing.QueryEvent
for (zeroCount || count > 0) && (wanCh != nil || lanCh != nil) {
var ok bool
select {
case qEv, ok = <-evtCh:
if !ok {
evtCh = nil
} else if qEv != nil && qEv.Type != routing.QueryError {
routing.PublishQueryEvent(reqCtx, qEv)
}
continue
case pi, ok = <-wanCh:
if !ok {
span.AddEvent("wan finished")
wanCh = nil
continue
}
case pi, ok = <-lanCh:
if !ok {
span.AddEvent("lan finished")
lanCh = nil
continue
}
}
// already found
if _, ok = found[pi.ID]; ok {
continue
}
select {
case outCh <- pi:
found[pi.ID] = struct{}{}
count--
case <-ctx.Done():
return
}
}
if qEv != nil && qEv.Type == routing.QueryError && len(found) == 0 {
routing.PublishQueryEvent(reqCtx, qEv)
}
}()
return outCh
}
// FindPeer searches for a peer with given ID
// Note: with signed peer records, we can change this to short circuit once either DHT returns.
func (dht *DHT) FindPeer(ctx context.Context, pid peer.ID) (pi peer.AddrInfo, err error) {
ctx, end := tracer.FindPeer(dualName, ctx, pid)
defer func() { end(pi, err) }()
var wg sync.WaitGroup
wg.Add(2)
var wanInfo, lanInfo peer.AddrInfo
var wanErr, lanErr error
go func() {
defer wg.Done()
wanInfo, wanErr = dht.WAN.FindPeer(ctx, pid)
}()
go func() {
defer wg.Done()
lanInfo, lanErr = dht.LAN.FindPeer(ctx, pid)
}()
wg.Wait()
// Combine addresses. Try to avoid doing unnecessary work while we're at
// it. Note: We're ignoring the errors for now as many of our DHT
// commands can return both a result and an error.
ai := peer.AddrInfo{ID: pid}
if len(wanInfo.Addrs) == 0 {
ai.Addrs = lanInfo.Addrs
} else if len(lanInfo.Addrs) == 0 {
ai.Addrs = wanInfo.Addrs
} else {
// combine addresses
deduped := make(map[string]ma.Multiaddr, len(wanInfo.Addrs)+len(lanInfo.Addrs))
for _, addr := range wanInfo.Addrs {
deduped[string(addr.Bytes())] = addr
}
for _, addr := range lanInfo.Addrs {
deduped[string(addr.Bytes())] = addr
}
ai.Addrs = make([]ma.Multiaddr, 0, len(deduped))
for _, addr := range deduped {
ai.Addrs = append(ai.Addrs, addr)
}
}
// If one of the commands succeeded, don't return an error.
if wanErr == nil || lanErr == nil {
return ai, nil
}
// Otherwise, return what we have _and_ return the error.
return ai, combineErrors(wanErr, lanErr)
}
func combineErrors(erra, errb error) error {
// if the errors are the same, just return one.
if erra == errb {
return erra
}
// If one of the errors is a kb lookup failure (no peers in routing
// table), return the other.
if erra == kb.ErrLookupFailure {
return errb
} else if errb == kb.ErrLookupFailure {
return erra
}
return multierror.Append(erra, errb).ErrorOrNil()
}
// Bootstrap allows callers to hint to the routing system to get into a
// Boostrapped state and remain there.
func (dht *DHT) Bootstrap(ctx context.Context) (err error) {
ctx, end := tracer.Bootstrap(dualName, ctx)
defer func() { end(err) }()
erra := dht.WAN.Bootstrap(ctx)
errb := dht.LAN.Bootstrap(ctx)
return combineErrors(erra, errb)
}
// PutValue adds value corresponding to given Key.
func (dht *DHT) PutValue(ctx context.Context, key string, val []byte, opts ...routing.Option) (err error) {
ctx, end := tracer.PutValue(dualName, ctx, key, val, opts...)
defer func() { end(err) }()
if dht.WANActive() {
return dht.WAN.PutValue(ctx, key, val, opts...)
}
return dht.LAN.PutValue(ctx, key, val, opts...)
}
// GetValue searches for the value corresponding to given Key.
func (d *DHT) GetValue(ctx context.Context, key string, opts ...routing.Option) (result []byte, err error) {
ctx, end := tracer.GetValue(dualName, ctx, key, opts...)
defer func() { end(result, err) }()
lanCtx, cancelLan := context.WithCancel(ctx)
defer cancelLan()
var (
lanVal []byte
lanErr error
lanWaiter sync.WaitGroup
)
lanWaiter.Add(1)
go func() {
defer lanWaiter.Done()
lanVal, lanErr = d.LAN.GetValue(lanCtx, key, opts...)
}()
wanVal, wanErr := d.WAN.GetValue(ctx, key, opts...)
if wanErr == nil {
cancelLan()
}
lanWaiter.Wait()
if wanErr == nil {
return wanVal, nil
}
if lanErr == nil {
return lanVal, nil
}
return nil, combineErrors(wanErr, lanErr)
}
// SearchValue searches for better values from this value
func (dht *DHT) SearchValue(ctx context.Context, key string, opts ...routing.Option) (ch <-chan []byte, err error) {
ctx, end := tracer.SearchValue(dualName, ctx, key, opts...)
defer func() { ch, err = end(ch, err) }()
p := helper.Parallel{Routers: []routing.Routing{dht.WAN, dht.LAN}, Validator: dht.WAN.Validator}
return p.SearchValue(ctx, key, opts...)
}
// GetPublicKey returns the public key for the given peer.
func (dht *DHT) GetPublicKey(ctx context.Context, pid peer.ID) (ci.PubKey, error) {
p := helper.Parallel{Routers: []routing.Routing{dht.WAN, dht.LAN}, Validator: dht.WAN.Validator}
return p.GetPublicKey(ctx, pid)
}

View File

@ -0,0 +1,399 @@
package dual
import (
"context"
"testing"
"time"
u "github.com/ipfs/boxo/util"
"github.com/ipfs/go-cid"
dht "github.com/libp2p/go-libp2p-kad-dht"
test "github.com/libp2p/go-libp2p-kad-dht/internal/testing"
record "github.com/libp2p/go-libp2p-record"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
peerstore "github.com/libp2p/go-libp2p/core/peerstore"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/multiformats/go-multiaddr"
"github.com/stretchr/testify/require"
)
var wancid, lancid cid.Cid
func init() {
wancid = cid.NewCidV1(cid.DagCBOR, u.Hash([]byte("wan cid -- value")))
lancid = cid.NewCidV1(cid.DagCBOR, u.Hash([]byte("lan cid -- value")))
}
type blankValidator struct{}
func (blankValidator) Validate(_ string, _ []byte) error { return nil }
func (blankValidator) Select(_ string, _ [][]byte) (int, error) { return 0, nil }
type customRtHelper struct {
allow peer.ID
}
func MkFilterForPeer() (func(_ interface{}, p peer.ID) bool, *customRtHelper) {
helper := customRtHelper{}
type hasHost interface {
Host() host.Host
}
f := func(dht interface{}, p peer.ID) bool {
d := dht.(hasHost)
conns := d.Host().Network().ConnsToPeer(p)
for _, c := range conns {
if c.RemotePeer() == helper.allow {
return true
}
}
return false
}
return f, &helper
}
func setupDHTWithFilters(ctx context.Context, t *testing.T, options ...dht.Option) (*DHT, []*customRtHelper) {
h, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
h.Start()
t.Cleanup(func() { h.Close() })
wanFilter, wanRef := MkFilterForPeer()
wanOpts := []dht.Option{
dht.NamespacedValidator("v", blankValidator{}),
dht.ProtocolPrefix("/test"),
dht.DisableAutoRefresh(),
dht.RoutingTableFilter(wanFilter),
}
wan, err := dht.New(ctx, h, wanOpts...)
require.NoError(t, err)
lanFilter, lanRef := MkFilterForPeer()
lanOpts := []dht.Option{
dht.NamespacedValidator("v", blankValidator{}),
dht.ProtocolPrefix("/test"),
dht.ProtocolExtension(LanExtension),
dht.DisableAutoRefresh(),
dht.RoutingTableFilter(lanFilter),
dht.Mode(dht.ModeServer),
}
lan, err := dht.New(ctx, h, lanOpts...)
require.NoError(t, err)
impl := DHT{wan, lan}
return &impl, []*customRtHelper{wanRef, lanRef}
}
func setupDHT(ctx context.Context, t *testing.T, options ...dht.Option) *DHT {
t.Helper()
host, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
host.Start()
t.Cleanup(func() { host.Close() })
baseOpts := []dht.Option{
dht.NamespacedValidator("v", blankValidator{}),
dht.ProtocolPrefix("/test"),
dht.DisableAutoRefresh(),
}
d, err := New(
ctx,
host,
append([]Option{DHTOption(baseOpts...)}, DHTOption(options...))...,
)
require.NoError(t, err)
return d
}
func connect(ctx context.Context, t *testing.T, a, b *dht.IpfsDHT) {
t.Helper()
bid := b.PeerID()
baddr := b.Host().Peerstore().Addrs(bid)
if len(baddr) == 0 {
t.Fatal("no addresses for connection.")
}
a.Host().Peerstore().AddAddrs(bid, baddr, peerstore.TempAddrTTL)
if err := a.Host().Connect(ctx, peer.AddrInfo{ID: bid}); err != nil {
t.Fatal(err)
}
wait(ctx, t, a, b)
}
func wait(ctx context.Context, t *testing.T, a, b *dht.IpfsDHT) {
t.Helper()
for a.RoutingTable().Find(b.PeerID()) == "" {
// fmt.Fprintf(os.Stderr, "%v\n", a.RoutingTable().GetPeerInfos())
select {
case <-ctx.Done():
t.Fatal(ctx.Err())
case <-time.After(time.Millisecond * 5):
}
}
}
func setupTier(ctx context.Context, t *testing.T) (*DHT, *dht.IpfsDHT, *dht.IpfsDHT) {
t.Helper()
baseOpts := []dht.Option{
dht.NamespacedValidator("v", blankValidator{}),
dht.ProtocolPrefix("/test"),
dht.DisableAutoRefresh(),
}
d, hlprs := setupDHTWithFilters(ctx, t)
whost, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
whost.Start()
t.Cleanup(func() { whost.Close() })
wan, err := dht.New(
ctx,
whost,
append(baseOpts, dht.Mode(dht.ModeServer))...,
)
if err != nil {
t.Fatal(err)
}
hlprs[0].allow = wan.PeerID()
connect(ctx, t, d.WAN, wan)
lhost, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
lhost.Start()
t.Cleanup(func() { lhost.Close() })
lan, err := dht.New(
ctx,
lhost,
append(baseOpts, dht.Mode(dht.ModeServer), dht.ProtocolExtension("/lan"))...,
)
if err != nil {
t.Fatal(err)
}
hlprs[1].allow = lan.PeerID()
connect(ctx, t, d.LAN, lan)
return d, wan, lan
}
func TestDualModes(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d := setupDHT(ctx, t)
defer d.Close()
if d.WAN.Mode() != dht.ModeAuto {
t.Fatal("wrong default mode for wan")
} else if d.LAN.Mode() != dht.ModeServer {
t.Fatal("wrong default mode for lan")
}
d2 := setupDHT(ctx, t, dht.Mode(dht.ModeClient))
defer d2.Close()
if d2.WAN.Mode() != dht.ModeClient ||
d2.LAN.Mode() != dht.ModeClient {
t.Fatal("wrong client mode operation")
}
}
func TestFindProviderAsync(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d, wan, lan := setupTier(ctx, t)
defer d.Close()
defer wan.Close()
defer lan.Close()
time.Sleep(5 * time.Millisecond)
if err := wan.Provide(ctx, wancid, false); err != nil {
t.Fatal(err)
}
if err := lan.Provide(ctx, lancid, true); err != nil {
t.Fatal(err)
}
wpc := d.FindProvidersAsync(ctx, wancid, 1)
select {
case p := <-wpc:
if p.ID != wan.PeerID() {
t.Fatal("wrong wan provider")
}
case <-ctx.Done():
t.Fatal("find provider timeout.")
}
lpc := d.FindProvidersAsync(ctx, lancid, 1)
select {
case p := <-lpc:
if p.ID != lan.PeerID() {
t.Fatal("wrong lan provider")
}
case <-ctx.Done():
t.Fatal("find provider timeout.")
}
}
func TestValueGetSet(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d, wan, lan := setupTier(ctx, t)
defer d.Close()
defer wan.Close()
defer lan.Close()
time.Sleep(5 * time.Millisecond)
err := d.PutValue(ctx, "/v/hello", []byte("valid"))
if err != nil {
t.Fatal(err)
}
val, err := wan.GetValue(ctx, "/v/hello")
if err != nil {
t.Fatal(err)
}
if string(val) != "valid" {
t.Fatal("failed to get expected string.")
}
_, err = lan.GetValue(ctx, "/v/hello")
if err == nil {
t.Fatal(err)
}
}
func TestSearchValue(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d, wan, lan := setupTier(ctx, t)
defer d.Close()
defer wan.Close()
defer lan.Close()
d.WAN.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
d.LAN.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
_ = wan.PutValue(ctx, "/v/hello", []byte("valid"))
valCh, err := d.SearchValue(ctx, "/v/hello", dht.Quorum(0))
if err != nil {
t.Fatal(err)
}
select {
case v := <-valCh:
if string(v) != "valid" {
t.Errorf("expected 'valid', got '%s'", string(v))
}
case <-ctx.Done():
t.Fatal(ctx.Err())
}
select {
case _, ok := <-valCh:
if ok {
t.Errorf("chan should close")
}
case <-ctx.Done():
t.Fatal(ctx.Err())
}
err = lan.PutValue(ctx, "/v/hello", []byte("newer"))
if err != nil {
t.Error(err)
}
valCh, err = d.SearchValue(ctx, "/v/hello", dht.Quorum(0))
if err != nil {
t.Fatal(err)
}
var lastVal []byte
for c := range valCh {
lastVal = c
}
if string(lastVal) != "newer" {
t.Fatal("incorrect best search value")
}
}
func TestGetPublicKey(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d, wan, lan := setupTier(ctx, t)
defer d.Close()
defer wan.Close()
defer lan.Close()
time.Sleep(5 * time.Millisecond)
pk, err := d.GetPublicKey(ctx, wan.PeerID())
if err != nil {
t.Fatal(err)
}
id, err := peer.IDFromPublicKey(pk)
if err != nil {
t.Fatal(err)
}
if id != wan.PeerID() {
t.Fatal("incorrect PK")
}
pk, err = d.GetPublicKey(ctx, lan.PeerID())
if err != nil {
t.Fatal(err)
}
id, err = peer.IDFromPublicKey(pk)
if err != nil {
t.Fatal(err)
}
if id != lan.PeerID() {
t.Fatal("incorrect PK")
}
}
func TestFindPeer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
d, wan, lan := setupTier(ctx, t)
defer d.Close()
defer wan.Close()
defer lan.Close()
time.Sleep(5 * time.Millisecond)
p, err := d.FindPeer(ctx, lan.PeerID())
if err != nil {
t.Fatal(err)
}
assertUniqueMultiaddrs(t, p.Addrs)
p, err = d.FindPeer(ctx, wan.PeerID())
if err != nil {
t.Fatal(err)
}
assertUniqueMultiaddrs(t, p.Addrs)
}
func assertUniqueMultiaddrs(t *testing.T, addrs []multiaddr.Multiaddr) {
set := make(map[string]bool)
for _, addr := range addrs {
if set[string(addr.Bytes())] {
t.Errorf("duplicate address %s", addr)
}
set[string(addr.Bytes())] = true
}
}

247
go-libp2p-kad-dht/events.go Normal file
View File

@ -0,0 +1,247 @@
package dht
import (
"context"
"encoding/json"
"sync"
"github.com/google/uuid"
kbucket "github.com/libp2p/go-libp2p-kbucket"
"github.com/libp2p/go-libp2p/core/peer"
)
// KeyKadID contains the Kademlia key in string and binary form.
type KeyKadID struct {
Key string
Kad kbucket.ID
}
// NewKeyKadID creates a KeyKadID from a string Kademlia ID.
func NewKeyKadID(k string) *KeyKadID {
return &KeyKadID{
Key: k,
Kad: kbucket.ConvertKey(k),
}
}
// PeerKadID contains a libp2p Peer ID and a binary Kademlia ID.
type PeerKadID struct {
Peer peer.ID
Kad kbucket.ID
}
// NewPeerKadID creates a PeerKadID from a libp2p Peer ID.
func NewPeerKadID(p peer.ID) *PeerKadID {
return &PeerKadID{
Peer: p,
Kad: kbucket.ConvertPeerID(p),
}
}
// NewPeerKadIDSlice creates a slice of PeerKadID from the passed slice of libp2p Peer IDs.
func NewPeerKadIDSlice(p []peer.ID) []*PeerKadID {
r := make([]*PeerKadID, len(p))
for i := range p {
r[i] = NewPeerKadID(p[i])
}
return r
}
// OptPeerKadID returns a pointer to a PeerKadID or nil if the passed Peer ID is it's default value.
func OptPeerKadID(p peer.ID) *PeerKadID {
if p == "" {
return nil
}
return NewPeerKadID(p)
}
// NewLookupEvent creates a LookupEvent automatically converting the node
// libp2p Peer ID to a PeerKadID and the string Kademlia key to a KeyKadID.
func NewLookupEvent(
node peer.ID,
id uuid.UUID,
key string,
request *LookupUpdateEvent,
response *LookupUpdateEvent,
terminate *LookupTerminateEvent,
) *LookupEvent {
return &LookupEvent{
Node: NewPeerKadID(node),
ID: id,
Key: NewKeyKadID(key),
Request: request,
Response: response,
Terminate: terminate,
}
}
// LookupEvent is emitted for every notable event that happens during a DHT lookup.
// LookupEvent supports JSON marshalling because all of its fields do, recursively.
type LookupEvent struct {
// Node is the ID of the node performing the lookup.
Node *PeerKadID
// ID is a unique identifier for the lookup instance.
ID uuid.UUID
// Key is the Kademlia key used as a lookup target.
Key *KeyKadID
// Request, if not nil, describes a state update event, associated with an outgoing query request.
Request *LookupUpdateEvent
// Response, if not nil, describes a state update event, associated with an outgoing query response.
Response *LookupUpdateEvent
// Terminate, if not nil, describe a termination event.
Terminate *LookupTerminateEvent
}
// NewLookupUpdateEvent creates a new lookup update event, automatically converting the passed peer IDs to peer Kad IDs.
func NewLookupUpdateEvent(
cause peer.ID,
source peer.ID,
heard []peer.ID,
waiting []peer.ID,
queried []peer.ID,
unreachable []peer.ID,
) *LookupUpdateEvent {
return &LookupUpdateEvent{
Cause: OptPeerKadID(cause),
Source: OptPeerKadID(source),
Heard: NewPeerKadIDSlice(heard),
Waiting: NewPeerKadIDSlice(waiting),
Queried: NewPeerKadIDSlice(queried),
Unreachable: NewPeerKadIDSlice(unreachable),
}
}
// LookupUpdateEvent describes a lookup state update event.
type LookupUpdateEvent struct {
// Cause is the peer whose response (or lack of response) caused the update event.
// If Cause is nil, this is the first update event in the lookup, caused by the seeding.
Cause *PeerKadID
// Source is the peer who informed us about the peer IDs in this update (below).
Source *PeerKadID
// Heard is a set of peers whose state in the lookup's peerset is being set to "heard".
Heard []*PeerKadID
// Waiting is a set of peers whose state in the lookup's peerset is being set to "waiting".
Waiting []*PeerKadID
// Queried is a set of peers whose state in the lookup's peerset is being set to "queried".
Queried []*PeerKadID
// Unreachable is a set of peers whose state in the lookup's peerset is being set to "unreachable".
Unreachable []*PeerKadID
}
// LookupTerminateEvent describes a lookup termination event.
type LookupTerminateEvent struct {
// Reason is the reason for lookup termination.
Reason LookupTerminationReason
}
// NewLookupTerminateEvent creates a new lookup termination event with a given reason.
func NewLookupTerminateEvent(reason LookupTerminationReason) *LookupTerminateEvent {
return &LookupTerminateEvent{Reason: reason}
}
// LookupTerminationReason captures reasons for terminating a lookup.
type LookupTerminationReason int
// MarshalJSON returns the JSON encoding of the passed lookup termination reason.
func (r LookupTerminationReason) MarshalJSON() ([]byte, error) {
return json.Marshal(r.String())
}
func (r LookupTerminationReason) String() string {
switch r {
case LookupStopped:
return "stopped"
case LookupCancelled:
return "cancelled"
case LookupStarvation:
return "starvation"
case LookupCompleted:
return "completed"
}
panic("unreachable")
}
const (
// LookupStopped indicates that the lookup was aborted by the user's stopFn.
LookupStopped LookupTerminationReason = iota
// LookupCancelled indicates that the lookup was aborted by the context.
LookupCancelled
// LookupStarvation indicates that the lookup terminated due to lack of unqueried peers.
LookupStarvation
// LookupCompleted indicates that the lookup terminated successfully, reaching the Kademlia end condition.
LookupCompleted
)
type routingLookupKey struct{}
// TODO: lookupEventChannel copies the implementation of eventChanel.
// The two should be refactored to use a common event channel implementation.
// A common implementation needs to rethink the signature of RegisterForEvents,
// because returning a typed channel cannot be made polymorphic without creating
// additional "adapter" channels. This will be easier to handle when Go
// introduces generics.
type lookupEventChannel struct {
mu sync.Mutex
ctx context.Context
ch chan<- *LookupEvent
}
// waitThenClose is spawned in a goroutine when the channel is registered. This
// safely cleans up the channel when the context has been canceled.
func (e *lookupEventChannel) waitThenClose() {
<-e.ctx.Done()
e.mu.Lock()
close(e.ch)
// 1. Signals that we're done.
// 2. Frees memory (in case we end up hanging on to this for a while).
e.ch = nil
e.mu.Unlock()
}
// send sends an event on the event channel, aborting if either the passed or
// the internal context expire.
func (e *lookupEventChannel) send(ctx context.Context, ev *LookupEvent) {
e.mu.Lock()
// Closed.
if e.ch == nil {
e.mu.Unlock()
return
}
// in case the passed context is unrelated, wait on both.
select {
case e.ch <- ev:
case <-e.ctx.Done():
case <-ctx.Done():
}
e.mu.Unlock()
}
// RegisterForLookupEvents registers a lookup event channel with the given context.
// The returned context can be passed to DHT queries to receive lookup events on
// the returned channels.
//
// The passed context MUST be canceled when the caller is no longer interested
// in query events.
func RegisterForLookupEvents(ctx context.Context) (context.Context, <-chan *LookupEvent) {
ch := make(chan *LookupEvent, LookupEventBufferSize)
ech := &lookupEventChannel{ch: ch, ctx: ctx}
go ech.waitThenClose()
return context.WithValue(ctx, routingLookupKey{}, ech), ch
}
// LookupEventBufferSize is the number of events to buffer.
var LookupEventBufferSize = 16
// PublishLookupEvent publishes a query event to the query event channel
// associated with the given context, if any.
func PublishLookupEvent(ctx context.Context, ev *LookupEvent) {
ich := ctx.Value(routingLookupKey{})
if ich == nil {
return
}
// We *want* to panic here.
ech := ich.(*lookupEventChannel)
ech.send(ctx, ev)
}

View File

@ -0,0 +1,48 @@
package dht
import (
"context"
"testing"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/stretchr/testify/require"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
)
func TestInvalidRemotePeers(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mn, err := mocknet.FullMeshLinked(5)
if err != nil {
t.Fatal(err)
}
defer mn.Close()
hosts := mn.Hosts()
os := []Option{testPrefix, DisableAutoRefresh(), Mode(ModeServer)}
d, err := New(ctx, hosts[0], os...)
if err != nil {
t.Fatal(err)
}
for _, proto := range d.serverProtocols {
// Hang on every request.
hosts[1].SetStreamHandler(proto, func(s network.Stream) {
defer s.Reset() // nolint
<-ctx.Done()
})
}
err = mn.ConnectAllButSelf()
if err != nil {
t.Fatal("failed to connect peers", err)
}
time.Sleep(100 * time.Millisecond)
// hosts[1] isn't added to the routing table because it isn't responding to
// the DHT request
require.Equal(t, 0, d.routingTable.Size())
}

Some files were not shown because too many files have changed in this diff Show More