Files
rippled/src/tests/libxrpl/csf/BasicNetwork.h
2026-07-23 21:38:21 +00:00

233 lines
6.6 KiB
C++

#pragma once
#include <csf/Digraph.h>
#include <csf/Scheduler.h>
#include <cassert>
namespace xrpl::test::csf {
/**
* Peer to peer network simulator.
*
* The network is formed from a set of Peer objects representing
* vertices and configurable connections representing edges.
* The caller is responsible for creating the Peer objects ahead
* of time.
*
* Peer objects cannot be destroyed once the BasicNetwork is
* constructed. To handle peers going online and offline,
* callers can simply disconnect all links and reconnect them
* later. Connections are directed, one end is the inbound
* Peer and the other is the outbound Peer.
*
* Peers may send messages along their connections. To simulate
* the effects of latency, these messages can be delayed by a
* configurable duration set when the link is established.
* Messages always arrive in the order they were sent on a
* particular connection.
*
* A message is modeled using a lambda function. The caller
* provides the code to execute upon delivery of the message.
* If a Peer is disconnected, all messages pending delivery
* at either end of the connection will not be delivered.
*
* When creating the Peer set, the caller needs to provide a
* Scheduler object for managing the timing and delivery
* of messages. After constructing the network, and establishing
* connections, the caller uses the scheduler's step* functions
* to drive messages through the network.
*
* The graph of peers and connections is internally represented
* using Digraph<Peer,BasicNetwork::LinkType>. Clients have
* const access to that graph to perform additional operations not
* directly provided by BasicNetwork.
*
* Peer Requirements:
*
* Peer should be a lightweight type, cheap to copy
* and/or move. A good candidate is a simple pointer to
* the underlying user defined type in the simulation.
*
* Expression Type Requirements
* ---------- ---- ------------
* P Peer
* u, v Values of type P
* P u(v) CopyConstructible
* u.~P() Destructible
* u == v bool EqualityComparable
* u < v bool LessThanComparable
* std::hash<P> class std::hash is defined for P
* ! u bool true if u is not-a-peer
*/
template <class Peer>
class BasicNetwork
{
using peer_type = Peer;
using clock_type = Scheduler::clock_type;
using duration = clock_type::duration;
using time_point = clock_type::time_point;
struct LinkType
{
bool inbound = false;
duration delay{};
time_point established;
LinkType() = default;
LinkType(bool inbound, duration delay, time_point established)
: inbound(inbound), delay(delay), established(established)
{
}
};
Scheduler& scheduler_;
Digraph<Peer, LinkType> links_;
public:
BasicNetwork(BasicNetwork const&) = delete;
BasicNetwork&
operator=(BasicNetwork const&) = delete;
BasicNetwork(Scheduler& s);
/**
* Connect two peers.
*
* The link is directed, with `from` establishing
* the outbound connection and `to` receiving the
* incoming connection.
*
* Preconditions:
*
* from != to (self connect disallowed).
*
* A link between from and to does not
* already exist (duplicates disallowed).
*
* Effects:
*
* Creates a link between from and to.
*
* @param `from` The source of the outgoing connection
* @param `to` The recipient of the incoming connection
* @param `delay` The time delay of all delivered messages
* @return `true` if a new connection was established
*/
bool
connect(Peer const& from, Peer const& to, duration const& delay = std::chrono::seconds{0});
/**
* Break a link.
*
* Effects:
*
* If a connection is present, both ends are
* disconnected.
*
* Any pending messages on the connection
* are discarded.
*
* @return `true` if a connection was broken.
*/
bool
disconnect(Peer const& peer1, Peer const& peer2);
/**
* Send a message to a peer.
*
* Preconditions:
*
* A link exists between from and to.
*
* Effects:
*
* If the link is not broken when the
* link's `delay` time has elapsed,
* the function will be invoked with
* no arguments.
*
* @note Its the caller's responsibility to
* ensure that the body of the function performs
* activity consistent with `from`'s receipt of
* a message from `to`.
*/
template <class Function>
void
send(Peer const& from, Peer const& to, Function&& f);
/**
* Return the range of active links.
*
* @return A random access range over Digraph::Edge instances
*/
auto
links(Peer const& from)
{
return links_.outEdges(from);
}
/**
* Return the underlying digraph
*/
[[nodiscard]] Digraph<Peer, LinkType> const&
graph() const
{
return links_;
}
};
//------------------------------------------------------------------------------
template <class Peer>
BasicNetwork<Peer>::BasicNetwork(Scheduler& s) : scheduler_(s)
{
}
template <class Peer>
inline bool
BasicNetwork<Peer>::connect(Peer const& from, Peer const& to, duration const& delay)
{
if (to == from)
return false;
time_point const now = scheduler_.now();
if (!links_.connect(from, to, LinkType{false, delay, now}))
return false;
auto const result = links_.connect(to, from, LinkType{true, delay, now});
(void)result;
assert(result);
return true;
}
template <class Peer>
inline bool
BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
{
if (!links_.disconnect(peer1, peer2))
return false;
bool const r = links_.disconnect(peer2, peer1);
(void)r;
assert(r);
return true;
}
template <class Peer>
template <class Function>
inline void
BasicNetwork<Peer>::send(Peer const& from, Peer const& to, Function&& f)
{
auto link = links_.edge(from, to);
if (!link)
return;
time_point const sent = scheduler_.now();
scheduler_.in(link->delay, [from, to, sent, f = std::forward<Function>(f), this] {
// only process if still connected and connection was
// not broken since the message was sent
if (auto l = links_.edge(from, to); l && l->established <= sent)
{
f();
}
});
}
} // namespace xrpl::test::csf