mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-18 18:15:50 +00:00
Peerfinder work
This commit is contained in:
@@ -1654,6 +1654,7 @@
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\src\ripple\algorithm\api\CycledSet.h" />
|
||||
<ClInclude Include="..\..\src\ripple\algorithm\api\DiscreteClock.h" />
|
||||
<ClInclude Include="..\..\src\ripple\http\api\Handler.h" />
|
||||
<ClInclude Include="..\..\src\ripple\http\api\Server.h" />
|
||||
@@ -1686,6 +1687,7 @@
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LegacyEndpoint.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LegacyEndpointCache.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\Logic.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LogicType.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\PeerInfo.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\Slots.h" />
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\Source.h" />
|
||||
@@ -1739,7 +1741,6 @@
|
||||
<ClInclude Include="..\..\src\ripple\types\api\base_uint.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\Blob.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\ByteOrder.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\CycledSet.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\IdentifierStorage.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\IdentifierType.h" />
|
||||
<ClInclude Include="..\..\src\ripple\types\api\HashMaps.h" />
|
||||
|
||||
@@ -1104,9 +1104,6 @@
|
||||
<ClCompile Include="..\..\src\ripple\types\impl\JsonPropertyStream.cpp">
|
||||
<Filter>[1] Ripple\types\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple_app\main\BlackList.cpp">
|
||||
<Filter>[2] Old Ripple\ripple_app\main</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\resource\ripple_resource.cpp">
|
||||
<Filter>[1] Ripple\resource</Filter>
|
||||
</ClCompile>
|
||||
@@ -2232,9 +2229,6 @@
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LegacyEndpoint.h">
|
||||
<Filter>[1] Ripple\peerfinder\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\types\api\CycledSet.h">
|
||||
<Filter>[1] Ripple\types\api</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LegacyEndpointCache.h">
|
||||
<Filter>[1] Ripple\peerfinder\impl</Filter>
|
||||
</ClInclude>
|
||||
@@ -2250,9 +2244,6 @@
|
||||
<ClInclude Include="..\..\src\ripple\types\api\JsonPropertyStream.h">
|
||||
<Filter>[1] Ripple\types\api</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple_app\main\BlackList.h">
|
||||
<Filter>[2] Old Ripple\ripple_app\main</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\resource\ripple_resource.h">
|
||||
<Filter>[1] Ripple\resource</Filter>
|
||||
</ClInclude>
|
||||
@@ -2307,6 +2298,12 @@
|
||||
<ClInclude Include="..\..\src\ripple\resource\impl\LogicType.h">
|
||||
<Filter>[1] Ripple\resource\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\algorithm\api\CycledSet.h">
|
||||
<Filter>[1] Ripple\algorithm</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\peerfinder\impl\LogicType.h">
|
||||
<Filter>[1] Ripple\peerfinder\impl</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto">
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#ifndef RIPPLE_TYPES_CYCLEDSET_H_INCLUDED
|
||||
#define RIPPLE_TYPES_CYCLEDSET_H_INCLUDED
|
||||
|
||||
#include "beast/modules/beast_core/system/BeforeBoost.h"
|
||||
#include <boost/unordered_set.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Cycled set of unique keys.
|
||||
@@ -32,7 +32,7 @@ struct Endpoint
|
||||
int hops;
|
||||
uint32 incomingSlotsAvailable;
|
||||
uint32 incomingSlotsMax;
|
||||
uint32 uptimeMinutes;
|
||||
uint32 uptimeSeconds;
|
||||
std::string featureList;
|
||||
};
|
||||
|
||||
|
||||
@@ -90,11 +90,11 @@ public:
|
||||
entry.message.hops = std::min (entry.message.hops, message.hops);
|
||||
|
||||
// Copy the other fields based on uptime
|
||||
if (entry.message.uptimeMinutes < message.uptimeMinutes)
|
||||
if (entry.message.uptimeSeconds < message.uptimeSeconds)
|
||||
{
|
||||
entry.message.incomingSlotsAvailable = message.incomingSlotsAvailable;
|
||||
entry.message.incomingSlotsMax = message.incomingSlotsMax;
|
||||
entry.message.uptimeMinutes = message.uptimeMinutes;
|
||||
entry.message.uptimeSeconds = message.uptimeSeconds;
|
||||
entry.message.featureList = message.featureList;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Endpoint::Endpoint ()
|
||||
: hops (0)
|
||||
, incomingSlotsAvailable (0)
|
||||
, incomingSlotsMax (0)
|
||||
, uptimeMinutes (0)
|
||||
, uptimeSeconds (0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -77,10 +77,12 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
DiscreteClock <DiscreteTime> m_clock;
|
||||
Callback& m_callback;
|
||||
Store& m_store;
|
||||
Checker& m_checker;
|
||||
Journal m_journal;
|
||||
|
||||
Config m_config;
|
||||
|
||||
// The number of fixed peers that are currently connected
|
||||
@@ -106,20 +108,28 @@ public:
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
Logic (
|
||||
DiscreteClock <DiscreteTime> clock,
|
||||
Callback& callback,
|
||||
Store& store,
|
||||
Checker& checker,
|
||||
Journal journal)
|
||||
: m_callback (callback)
|
||||
: m_clock (clock)
|
||||
, m_callback (callback)
|
||||
, m_store (store)
|
||||
, m_checker (checker)
|
||||
, m_journal (journal)
|
||||
, m_fixedPeersConnected (0)
|
||||
, m_slots (clock)
|
||||
, m_cache (journal)
|
||||
, m_legacyCache (store, journal)
|
||||
{
|
||||
}
|
||||
|
||||
DiscreteTime get_now()
|
||||
{
|
||||
return m_clock();
|
||||
}
|
||||
|
||||
/** Stop the logic.
|
||||
This will cancel the current fetch and set the stopping flag
|
||||
to `true` to prevent further fetches.
|
||||
@@ -156,7 +166,7 @@ public:
|
||||
ep.hops = 0;
|
||||
ep.incomingSlotsAvailable = m_slots.inboundSlots;
|
||||
ep.incomingSlotsMax = m_slots.inboundSlotsMaximum;
|
||||
ep.uptimeMinutes = m_slots.uptimeMinutes();
|
||||
ep.uptimeSeconds = m_slots.uptimeSeconds();
|
||||
|
||||
return ep;
|
||||
}
|
||||
@@ -208,14 +218,6 @@ public:
|
||||
m_callback.connectPeerEndpoints (list);
|
||||
}
|
||||
|
||||
// Returns the number of seconds that have elapsed since some baseline
|
||||
// event.
|
||||
//
|
||||
virtual DiscreteTime get_now()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Logic
|
||||
@@ -259,7 +261,7 @@ public:
|
||||
//
|
||||
void cycleCache()
|
||||
{
|
||||
m_cache.cycle(get_now());
|
||||
m_cache.cycle (get_now());
|
||||
|
||||
for (Peers::iterator iter (m_peers.begin());
|
||||
iter != m_peers.end(); ++iter)
|
||||
|
||||
@@ -23,28 +23,32 @@
|
||||
namespace ripple {
|
||||
namespace PeerFinder {
|
||||
|
||||
/** Provides the Clock required by Logic's get_now().
|
||||
This allows the unit tests to provide its own manual clock.
|
||||
*/
|
||||
template <typename Clock>
|
||||
class LogicType : public Logic
|
||||
template <class DiscreteClockSourceType>
|
||||
class LogicType
|
||||
: private BaseFromMember <DiscreteClockSourceType>
|
||||
, public Logic
|
||||
{
|
||||
public:
|
||||
explicit LogicType (Callback& callback,
|
||||
Store& store,
|
||||
Checker& checker,
|
||||
Journal journal)
|
||||
: Logic (callback, store, checker, journal)
|
||||
typedef typename DiscreteClockSourceType::DiscreteClockType DiscreteClockType;
|
||||
|
||||
LogicType (
|
||||
Callback& callback,
|
||||
Store& store,
|
||||
Checker& checker,
|
||||
Journal journal)
|
||||
: Logic (
|
||||
BaseFromMember <DiscreteClockSourceType>::member(),
|
||||
callback,
|
||||
store,
|
||||
checker,
|
||||
journal)
|
||||
{
|
||||
}
|
||||
|
||||
DiscreteTime get_now ()
|
||||
DiscreteClockSourceType& get_clock()
|
||||
{
|
||||
return m_clock();
|
||||
return BaseFromMember <DiscreteClockSourceType>::member();
|
||||
}
|
||||
|
||||
private:
|
||||
Clock m_clock;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ public:
|
||||
map ["out_desired"] = m_logic.m_slots.outDesired;
|
||||
map ["in_avail"] = m_logic.m_slots.inboundSlots;
|
||||
map ["in_max"] = m_logic.m_slots.inboundSlotsMaximum;
|
||||
map ["minutes"] = m_logic.m_slots.uptimeMinutes();
|
||||
map ["uptime"] = m_logic.m_slots.uptimeSeconds();
|
||||
map ["round"] = m_logic.m_slots.roundUpwards();
|
||||
map ["cache"] = uint32(m_logic.m_cache.size());
|
||||
map ["legacy"] = uint32(m_logic.m_legacyCache.size());
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PEERFINDER_SIMPLEMONOTONICCLOCK_H_INCLUDED
|
||||
#define RIPPLE_PEERFINDER_SIMPLEMONOTONICCLOCK_H_INCLUDED
|
||||
|
||||
namespace ripple {
|
||||
namespace PeerFinder {
|
||||
|
||||
/** Monotonically increasing time value. */
|
||||
struct SimpleMonotonicClock
|
||||
{
|
||||
typedef int value_type;
|
||||
|
||||
value_type operator() () const
|
||||
{
|
||||
return value_type (RelativeTime::fromStartup().inSeconds());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -20,15 +20,17 @@
|
||||
namespace ripple {
|
||||
namespace PeerFinder {
|
||||
|
||||
Slots::Slots ()
|
||||
: startTime (0)
|
||||
Slots::Slots (DiscreteClock <DiscreteTime> clock, bool roundUpwards)
|
||||
: m_clock (clock)
|
||||
, m_startTime (0)
|
||||
, peerCount (0)
|
||||
, inboundCount (0)
|
||||
, outboundCount (0)
|
||||
, fixedCount (0)
|
||||
, outDesired (0)
|
||||
, inboundSlots (0)
|
||||
, inboundSlotsMaximum (0)
|
||||
, m_roundUpwards (Random::getSystemRandom().nextBool())
|
||||
, m_roundUpwards (roundUpwards)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -58,7 +60,7 @@ void Slots::update (Config const& config)
|
||||
void Slots::addPeer (Config const& config, bool inbound)
|
||||
{
|
||||
if (peerCount == 0)
|
||||
startTime = RelativeTime::fromStartup();
|
||||
m_startTime = m_clock();
|
||||
|
||||
++peerCount;
|
||||
if (inbound)
|
||||
@@ -71,24 +73,51 @@ void Slots::addPeer (Config const& config, bool inbound)
|
||||
|
||||
void Slots::dropPeer (Config const& config, bool inbound)
|
||||
{
|
||||
bool const wasConnected (connected ());
|
||||
|
||||
--peerCount;
|
||||
if (inbound)
|
||||
--inboundCount;
|
||||
else
|
||||
--outboundCount;
|
||||
|
||||
if (peerCount == 0)
|
||||
startTime = RelativeTime(0);
|
||||
if (wasConnected && ! connected())
|
||||
m_startTime = 0;
|
||||
|
||||
update (config);
|
||||
}
|
||||
|
||||
uint32 Slots::uptimeMinutes () const
|
||||
bool Slots::roundUpwards () const
|
||||
{
|
||||
if (startTime.isNotZero())
|
||||
return (RelativeTime::fromStartup()-startTime).inMinutes();
|
||||
return m_roundUpwards;
|
||||
}
|
||||
|
||||
bool Slots::connected () const
|
||||
{
|
||||
return (peerCount-fixedCount) >= Config::minOutCount;
|
||||
}
|
||||
|
||||
uint32 Slots::uptimeSeconds () const
|
||||
{
|
||||
if (m_startTime != 0)
|
||||
return m_clock() - m_startTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Slots::updateConnected ()
|
||||
{
|
||||
bool const wasConnected (m_startTime != 0);
|
||||
bool const isConnected (connected());
|
||||
|
||||
if (wasConnected && !isConnected)
|
||||
{
|
||||
m_startTime = 0;
|
||||
}
|
||||
else if (! wasConnected && isConnected)
|
||||
{
|
||||
m_startTime = m_clock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,14 @@ namespace PeerFinder {
|
||||
class Slots
|
||||
{
|
||||
public:
|
||||
Slots ();
|
||||
explicit Slots (
|
||||
DiscreteClock <DiscreteTime> clock,
|
||||
bool roundUpwards = Random::getSystemRandom().nextBool());
|
||||
|
||||
void update (Config const& config);
|
||||
void addPeer (Config const& config, bool inbound);
|
||||
void dropPeer (Config const& config, bool inbound);
|
||||
|
||||
// Most recent time when we went from 0 to 1 peers
|
||||
RelativeTime startTime;
|
||||
|
||||
// Current total of connected peers that have HELLOed
|
||||
int peerCount;
|
||||
|
||||
@@ -44,6 +43,10 @@ public:
|
||||
// The portion of peers which are outgoing connections
|
||||
int outboundCount;
|
||||
|
||||
// The portion of peers which are the fixed peers.
|
||||
// Fixed peers don't count towards connection limits.
|
||||
int fixedCount;
|
||||
|
||||
// The number of outgoing peer connections we want (calculated)
|
||||
int outDesired;
|
||||
|
||||
@@ -53,17 +56,25 @@ public:
|
||||
// The maximum number of incoming slots (calculated)
|
||||
int inboundSlotsMaximum;
|
||||
|
||||
// Returns the uptime in minutes
|
||||
// Returns `true` if we round fractional slot availability upwards
|
||||
bool roundUpwards () const;
|
||||
|
||||
// Returns `true` if we meet the criteria of
|
||||
// "connected to the network based on the current values of slots.
|
||||
//
|
||||
bool connected () const;
|
||||
|
||||
// Returns the uptime in seconds
|
||||
// Uptime is measured from the last we transitioned from not
|
||||
// being connected to the network, to being connected.
|
||||
//
|
||||
uint32 uptimeMinutes () const;
|
||||
|
||||
// Returns `true` if we round fractional slot availability upwards
|
||||
bool roundUpwards () const
|
||||
{ return m_roundUpwards; }
|
||||
uint32 uptimeSeconds () const;
|
||||
|
||||
private:
|
||||
void updateConnected();
|
||||
|
||||
DiscreteTime m_startTime;
|
||||
DiscreteClock <DiscreteTime> m_clock;
|
||||
bool m_roundUpwards;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,9 +23,78 @@ namespace PeerFinder {
|
||||
class PeerFinderTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
// Complete Logic used for tests
|
||||
//
|
||||
class TestLogic
|
||||
: public LogicType <ManualClock>
|
||||
, public Callback
|
||||
, public Store
|
||||
, public Checker
|
||||
{
|
||||
public:
|
||||
Journal m_journal;
|
||||
|
||||
explicit TestLogic (Journal journal)
|
||||
: LogicType <ManualClock> (*this, *this, *this, journal)
|
||||
, m_journal (journal)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Callback
|
||||
//
|
||||
|
||||
void sendPeerEndpoints (PeerID const& id,
|
||||
std::vector <Endpoint> const& endpoints)
|
||||
{
|
||||
}
|
||||
|
||||
void connectPeerEndpoints (std::vector <IPAddress> const& list)
|
||||
{
|
||||
}
|
||||
|
||||
void chargePeerLoadPenalty (PeerID const& id)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Store
|
||||
//
|
||||
|
||||
void loadLegacyEndpoints (std::vector <IPAddress>& list)
|
||||
{
|
||||
}
|
||||
|
||||
void updateLegacyEndpoints (std::vector <LegacyEndpoint const*> const& list)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Checker
|
||||
//
|
||||
|
||||
void cancel ()
|
||||
{
|
||||
}
|
||||
|
||||
void async_test (IPAddress const& address,
|
||||
AbstractHandler <void (Result)> handler)
|
||||
{
|
||||
Checker::Result result;
|
||||
result.address = address;
|
||||
result.canAccept = false;
|
||||
handler (result);
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
beginTestCase ("logic");
|
||||
|
||||
TestLogic logic (journal());
|
||||
|
||||
pass ();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
#include "ripple_peerfinder.h"
|
||||
|
||||
#include "../../ripple/types/ripple_types.h"
|
||||
#include "../../ripple/algorithm/api/CycledSet.h"
|
||||
#include "../../ripple/algorithm/api/DiscreteClock.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
@@ -42,7 +43,6 @@ namespace ripple {
|
||||
using namespace beast;
|
||||
}
|
||||
|
||||
#include "impl/SimpleMonotonicClock.h"
|
||||
#include "impl/PrivateTypes.h"
|
||||
# include "impl/Tuning.h"
|
||||
# include "impl/Checker.h"
|
||||
@@ -57,8 +57,8 @@ using namespace beast;
|
||||
# include "impl/LegacyEndpointCache.h"
|
||||
# include "impl/PeerInfo.h"
|
||||
#include "impl/StoreSqdb.h"
|
||||
# include "impl/LogicType.h"
|
||||
#include "impl/Logic.h"
|
||||
#include "impl/LogicType.h"
|
||||
|
||||
#include "impl/Checker.cpp"
|
||||
#include "impl/Config.cpp"
|
||||
|
||||
@@ -45,7 +45,6 @@ using namespace beast;
|
||||
}
|
||||
|
||||
#include "api/AgedHistory.h"
|
||||
#include "api/CycledSet.h"
|
||||
# include "api/Blob.h"
|
||||
# include "api/Base58.h"
|
||||
# include "api/ByteOrder.h"
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "beast/modules/beast_asio/beast_asio.h"
|
||||
#include "beast/modules/beast_sqdb/beast_sqdb.h"
|
||||
|
||||
#include "../algorithm/api/CycledSet.h"
|
||||
#include "../testoverlay/ripple_testoverlay.h" // for unit test
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -1752,8 +1752,8 @@ void PeerImp::recvEndpoints (protocol::TMEndpoints& packet)
|
||||
// maxSlots
|
||||
endpoint.incomingSlotsMax = tm.maxslots();
|
||||
|
||||
// uptimeMinutes
|
||||
endpoint.uptimeMinutes = tm.uptimeminutes();
|
||||
// uptimeSeconds
|
||||
endpoint.uptimeSeconds = tm.uptimeseconds();
|
||||
|
||||
endpoints.push_back (endpoint);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ public:
|
||||
tme.set_hops (ep.hops);
|
||||
tme.set_slots (ep.incomingSlotsAvailable);
|
||||
tme.set_maxslots (ep.incomingSlotsMax);
|
||||
tme.set_uptimeminutes (ep.uptimeMinutes);
|
||||
tme.set_uptimeseconds (ep.uptimeSeconds);
|
||||
tme.set_features (ep.featureList);
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ message TMEndpoint
|
||||
required uint32 hops = 2;
|
||||
required uint32 slots = 3; // the number of available incoming slots
|
||||
required uint32 maxSlots = 4; // the maximum number of incoming slots
|
||||
required uint32 uptimeMinutes = 5; // uptime in minutes
|
||||
required uint32 uptimeSeconds = 5; // uptime in seconds
|
||||
required string features = 6;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user