diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake index 60760abab..1ffd537ba 100644 --- a/cmake/Hunter/config.cmake +++ b/cmake/Hunter/config.cmake @@ -15,27 +15,9 @@ # CMAKE_ARGS "CMAKE_VARIABLE=value" # ) -hunter_config( - soralog - VERSION 0.2.5 - URL https://github.com/qdrvm/soralog/archive/refs/tags/v0.2.5.tar.gz - SHA1 1dafdb9e1921b4069f9e1dad0d0acfae24166bd2 - KEEP_PACKAGE_SOURCES -) - hunter_config( ZLIB VERSION 1.3.0-p0 URL https://github.com/cpp-pm/zlib/archive/refs/tags/v1.3.0-p0.tar.gz SHA1 311ca59e20cbbfe9d9e05196c12c6ae109093987 ) - -hunter_config( - qtils - VERSION 0.1.0 - URL https://github.com/qdrvm/qtils/archive/refs/tags/v0.1.0.tar.gz - SHA1 acc28902af7dc5d74ac33d486ad2261906716f5e - CMAKE_ARGS - FORMAT_ERROR_WITH_FULLTYPE=ON - KEEP_PACKAGE_SOURCES -) diff --git a/cmake/Hunter/init.cmake b/cmake/Hunter/init.cmake index 6f8fc7f35..3e6981fab 100644 --- a/cmake/Hunter/init.cmake +++ b/cmake/Hunter/init.cmake @@ -31,7 +31,7 @@ set( include(${CMAKE_CURRENT_LIST_DIR}/HunterGate.cmake) HunterGate( - URL https://github.com/qdrvm/hunter/archive/refs/tags/v0.25.3-qdrvm28.tar.gz - SHA1 a4f1b0f42464e07790b7f90b783a822d71be6c6d + URL https://github.com/qdrvm/hunter/archive/86e53c752977bf5a5efb6590c26941ed3fec8187.zip + SHA1 7121f2cbf052cc6cb0a526a89e9b4aca5bf3cd54 LOCAL ) diff --git a/include/libp2p/basic/scheduler/manual_scheduler_backend.hpp b/include/libp2p/basic/scheduler/manual_scheduler_backend.hpp index 1a1ece46e..8fe9deff9 100644 --- a/include/libp2p/basic/scheduler/manual_scheduler_backend.hpp +++ b/include/libp2p/basic/scheduler/manual_scheduler_backend.hpp @@ -66,9 +66,9 @@ namespace libp2p::basic { } } - private: void callDeferred(); + private: /// Current time, set manually std::chrono::milliseconds current_clock_; diff --git a/include/libp2p/connection/stream_pair.hpp b/include/libp2p/connection/stream_pair.hpp new file mode 100644 index 000000000..74104abb0 --- /dev/null +++ b/include/libp2p/connection/stream_pair.hpp @@ -0,0 +1,24 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace libp2p::basic { + class Scheduler; +} // namespace libp2p::basic + +namespace libp2p::connection { + struct Stream; + + /** + * Create pair of connected bidirectional read-writers + * implementing `Stream` interface. + */ + std::pair, std::shared_ptr> streamPair( + std::shared_ptr post, PeerId peer1, PeerId peer2); +} // namespace libp2p::connection diff --git a/include/libp2p/peer/protocol.hpp b/include/libp2p/peer/protocol.hpp index 67fae1f25..b8b87561f 100644 --- a/include/libp2p/peer/protocol.hpp +++ b/include/libp2p/peer/protocol.hpp @@ -17,3 +17,7 @@ namespace libp2p::peer { std::string; } // namespace libp2p::peer + +namespace libp2p { + using peer::ProtocolName; +} // namespace libp2p diff --git a/include/libp2p/protocol/gossip/explicit_peers.hpp b/include/libp2p/protocol/gossip/explicit_peers.hpp new file mode 100644 index 000000000..43212ae04 --- /dev/null +++ b/include/libp2p/protocol/gossip/explicit_peers.hpp @@ -0,0 +1,18 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace libp2p::protocol::gossip { + class ExplicitPeers { + public: + bool contains(const PeerId &) const { + return false; + } + }; +} // namespace libp2p::protocol::gossip diff --git a/include/libp2p/protocol/gossip/gossip.hpp b/include/libp2p/protocol/gossip/gossip.hpp index 0b289db4d..d98328d97 100644 --- a/include/libp2p/protocol/gossip/gossip.hpp +++ b/include/libp2p/protocol/gossip/gossip.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,10 @@ #include #include #include +#include #include +#include +#include namespace libp2p { struct Host; @@ -41,8 +45,24 @@ namespace libp2p::protocol::gossip { struct Config { /// Network density factors for gossip meshes size_t D_min = 5; + size_t D_lazy = 6; + size_t D = 6; size_t D_max = 10; + /// Affects how peers are selected when pruning a mesh due to over + /// subscription. + /// + /// At least `retain_scores` of the retained peers will be high-scoring, + /// while the remainder + /// are chosen randomly (D_score in the spec, default is 4). + size_t retain_scores = 4; + + /// Affects how many peers we will emit gossip to at each heartbeat. + /// + /// We will send gossip to `gossip_factor * (total number of non-mesh + /// peers)`, or `gossip_lazy`, whichever is greater. The default is 0.25. + double gossip_factor = 0.25; + /// Ideal number of connected peers to support the network size_t ideal_connections_num = 100; @@ -50,27 +70,12 @@ namespace libp2p::protocol::gossip { /// incoming peers will be rejected size_t max_connections_num = 1000; - /// Forward messages to all subscribers not in mesh - /// (floodsub mode compatibility) - bool floodsub_forward_mode = false; - /// Forward local message to local subscribers bool echo_forward_mode = false; /// Read or write timeout per whole network operation std::chrono::milliseconds rw_timeout_msec{std::chrono::seconds(10)}; - /// Lifetime of a message in message cache - std::chrono::milliseconds message_cache_lifetime_msec{ - std::chrono::minutes(2)}; - - /// Topic's message seen cache lifetime - std::chrono::milliseconds seen_cache_lifetime_msec{ - message_cache_lifetime_msec * 3 / 4}; - - /// Topic's seen cache limit - unsigned seen_cache_limit = 100; - /// Heartbeat interval std::chrono::milliseconds heartbeat_interval_msec{1000}; @@ -86,11 +91,117 @@ namespace libp2p::protocol::gossip { /// Max RPC message size size_t max_message_size = 1 << 24; - /// Protocol version - std::string protocol_version = "/meshsub/1.0.0"; + /// Protocol versions + std::unordered_map protocol_versions{ + {"/floodsub/1.0.0", PeerKind::Floodsub}, + {"/meshsub/1.0.0", PeerKind::Gossipsub}, + {"/meshsub/1.1.0", PeerKind::Gossipsubv1_1}, + {"/meshsub/1.2.0", PeerKind::Gossipsubv1_2}, + }; /// Sign published messages bool sign_messages = false; + + /// Number of heartbeats to keep in the `memcache` + size_t history_length{5}; + + /// Number of past heartbeats to gossip about (default is 3). + size_t history_gossip{3}; + + /// Time to live for fanout peers (default is 60 seconds). + std::chrono::seconds fanout_ttl{60}; + + /// Duplicates are prevented by storing message id's of known messages in an + /// LRU time cache. This settings sets the time period that messages are + /// stored in the cache. Duplicates can be received if duplicate messages + /// are sent at a time greater than this setting apart. The default is 1 + /// minute. + std::chrono::seconds duplicate_cache_time{60}; + + /// Controls the backoff time for pruned peers. This is how long + /// a peer must wait before attempting to graft into our mesh again after + /// being pruned. When pruning a peer, we send them our value of + /// `prune_backoff` so they know the minimum time to wait. Peers running + /// older versions may not send a backoff time, so if we receive a prune + /// message without one, we will wait at least `prune_backoff` before + /// attempting to re-graft. The default is one minute. + std::chrono::seconds prune_backoff{60}; + + /// Controls the backoff time when unsubscribing from a topic. + /// + /// This is how long to wait before resubscribing to the topic. A short + /// backoff period in case of an unsubscribe event allows reaching a healthy + /// mesh in a more timely manner. The default is 10 seconds. + std::chrono::seconds unsubscribe_backoff{10}; + + /// Number of heartbeat slots considered as slack for backoffs. This + /// guarantees that we wait at least backoff_slack heartbeats after a + /// backoff is over before we try to graft. This solves problems occurring + /// through high latencies. In particular if `backoff_slack * + /// heartbeat_interval` is longer than any latencies between processing + /// prunes on our side and processing prunes on the receiving side this + /// guarantees that we get not punished for too early grafting. The default + /// is 1. + size_t backoff_slack = 1; + + /// Whether to do flood publishing or not. If enabled newly created messages + /// will always be + /// sent to all peers that are subscribed to the topic and have a good + /// enough score. The default is true. + bool flood_publish = true; + + /// If a GRAFT comes before `graft_flood_threshold` has elapsed since the + /// last PRUNE, then there is an extra score penalty applied to the peer + /// through P7. + std::chrono::milliseconds graft_flood_threshold = std::chrono::seconds{10}; + + /// Minimum number of outbound peers in the mesh network before adding more + /// (D_out in the spec). This value must be smaller or equal than `mesh_n / + /// 2` and smaller than `mesh_n_low`. The default is 2. + size_t mesh_outbound_min = 2; + + /// Number of heartbeat ticks that specify the interval in which + /// opportunistic grafting is applied. Every `opportunistic_graft_ticks` we + /// will attempt to select some high-scoring mesh peers to replace + /// lower-scoring ones, if the median score of our mesh peers falls below a + /// threshold (see + /// ). + /// The default is 60. + size_t opportunistic_graft_ticks = 60; + + /// The maximum number of new peers to graft to during opportunistic + /// grafting. The default is 2. + size_t opportunistic_graft_peers = 2; + + /// The maximum number of messages to include in an IHAVE message. + /// Also controls the maximum number of IHAVE ids we will accept and request + /// with IWANT from a peer within a heartbeat, to protect from IHAVE floods. + /// You should adjust this value from the default if your system is pushing + /// more than 5000 messages in GossipSubHistoryGossip heartbeats; with the + /// defaults this is 1666 messages/s. The default is 5000. + size_t max_ihave_length = 5000; + + /// Time to wait for a message requested through IWANT following an IHAVE + /// advertisement. If the message is not received within this window, a + /// broken promise is declared and the router may apply behavioural + /// penalties. The default is 3 seconds. + std::chrono::milliseconds iwant_followup_time = std::chrono::seconds{3}; + + /// The message size threshold for which IDONTWANT messages are sent. + /// Sending IDONTWANT messages for small messages can have a negative effect + /// to the overall traffic and CPU load. This acts as a lower bound cutoff + /// for the message size to which IDONTWANT won't be sent to peers. Only + /// works if the peers support Gossipsub1.2 (see + /// ) + /// default is 1kB + size_t idontwant_message_size_threshold = 1000; + + /// Send IDONTWANT messages after publishing message on gossip. This is an + /// optimisation to avoid bandwidth consumption by downloading the published + /// message over gossip. By default it is false. + bool idontwant_on_publish = false; + + ScoreConfig score; }; using TopicId = std::string; diff --git a/include/libp2p/protocol/gossip/peer_kind.hpp b/include/libp2p/protocol/gossip/peer_kind.hpp new file mode 100644 index 000000000..d0416f0de --- /dev/null +++ b/include/libp2p/protocol/gossip/peer_kind.hpp @@ -0,0 +1,19 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace libp2p::protocol::gossip { + enum class PeerKind : uint8_t { + NotSupported, + Floodsub, + Gossipsub, + Gossipsubv1_1, + Gossipsubv1_2, + }; +} // namespace libp2p::protocol::gossip diff --git a/include/libp2p/protocol/gossip/score.hpp b/include/libp2p/protocol/gossip/score.hpp new file mode 100644 index 000000000..ef699aa57 --- /dev/null +++ b/include/libp2p/protocol/gossip/score.hpp @@ -0,0 +1,340 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace libp2p::protocol::gossip { + using TopicId = std::string; + using MessageId = Bytes; +} // namespace libp2p::protocol::gossip + +namespace libp2p::protocol::gossip::score { + using Duration = std::chrono::milliseconds; + using Clock = std::chrono::steady_clock; + using Time = Clock::time_point; + + constexpr std::chrono::seconds kTimeCacheDuration{120}; + + struct DeliveryStatusUnknown {}; + struct DeliveryStatusValid { + Time time; + }; + struct DeliveryStatusInvalid {}; + struct DeliveryStatusIgnored {}; + using DeliveryStatus = std::variant; + + struct DeliveryRecord { + DeliveryStatus status; + Time first_seen; + std::unordered_set peers; + }; + + struct MeshActive { + Time graft_time; + Duration mesh_time; + }; + + struct TopicStats { + std::optional mesh_active; + double first_message_deliveries = 0; + bool mesh_message_deliveries_active = false; + double mesh_message_deliveries = 0; + double mesh_failure_penalty = 0; + double invalid_message_deliveries = 0; + }; + + struct PeerStats { + std::optional