A Quantitative User-friendly Adaptive Networked Things Abstract Simulator.
QUANTAS is an abstract, round-based simulator for studying distributed algorithms without committing to a specific network stack or operating system. The platform lets you compose experiments by mixing algorithm implementations, network topologies, and stochastic message-delay/disruption models. Recent updates add first-class support for modelling Byzantine behaviour, allowing you to attach reusable fault strategies to subsets of simulated peers.
By default, QUANTAS targets Linux and uses g++ 9 or newer. Install it with:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install g++-9If you prefer Clang, build with make clang.
External libraries bundled in the repository:
- nlohmann/json for JSON parsing.
- BS-Thread-Pool for parallel round execution.
Clone the repository, pick the algorithm/input configuration you want to exercise, then build and run via the provided makefile.
- Select an input JSON file by setting
INPUTFILEin the rootmakefile(or override on the command line:make run INPUTFILE=quantas/ExamplePeer/ExampleInput.json). The chosen file also determines which algorithm sources are compiled. - Build:
make release
- Execute the configured experiment suite:
Use
make run
make debugfor an unoptimised build with extra assertions, ormake run_memory/make run_debugfor valgrind and gdb helpers.
A simulation is described by a JSON document with two top-level keys:
{
"algorithms": ["ExamplePeer/ExamplePeer.cpp"],
"experiments": [ { /* experiment description */ } ]
}algorithmslists the C++ translation units (relative toquantas/) that should be compiled into the executable. Each file registers at least one peer type withPeerRegistry.experimentsis an array of experiment objects. QUANTAS runs them sequentially.
Every experiment object can contain:
logFile: Output destination for metrics. Use a filename to create/append to that file, or"cout"to emit JSON metrics on stdout.threadCount: Desired worker threads for message delivery and computation. The runtime caps this at the number of peers.tests: Repeat count for the experiment (default 1). Each repetition re-initialises the topology and random seeds.rounds: Number of synchronous rounds to execute per test.distribution: Network/channel configuration (see below).topology: Initial network description (see below).parameters: Arbitrary JSON payload forwarded to the algorithm duringPeer::initParameters. Keys are algorithm-specific (examples listed later).
distribution controls how channels inject latency and faults. Supported keys (all optional):
type: One ofUNIFORM,POISSON, orONE.UNIFORM(default) selects a uniform integer delay in [minDelay,maxDelay];POISSONsamples a Poisson variate with meanavgDelay, clamped by the min/max bounds;ONEalways delivers in the next round.minDelay,maxDelay: Inclusive bounds for delivery delay (defaults to 1).avgDelay: Mean used by the Poisson model.dropProbability: Probability that an outbound packet is discarded instead of enqueued.duplicateProbability: Chance to duplicate an outbound packet (duplicates reuse the sampled delay).reorderProbability: Chance to shuffle the in-flight queue before delivery.maxMsgsRec: Per-round cap on the number of packets a channel will deliver.size: Maximum queue length per channel.
These properties are applied to every channel created when the topology is instantiated.
topology declares how peers are created and connected at the beginning of the experiment.
Required keys:
initialPeers: Number of peers to instantiate.initialPeerType: Symbol registered viaPeerRegistry::registerPeerTypethat identifies whichPeersubclass to construct (for examplePBFTPeer,RaftPeer,BitcoinPeer).type: One of:complete: Fully connected graph.star: Peer 0 connected to everyone.grid: 2D grid; requiresheightandwidth(height × width must equalinitialPeers).torus: Grid with wrap-around edges.chain: Linear chain.ring: Bidirectional ring.unidirectionalRing: Directed ring.userList: Custom adjacency; requires alistobject mapping peer indices (as strings) to arrays of neighbour indices.
Optional keys:
height,width: Dimensions for grid/torus generation.identifiers: Use"random"to shuffle public identifier assignment before wiring channels, providing a quick way to simulate random IDs.
Algorithms may mutate the topology after initialisation, but these settings define the starting graph.
parameters is forwarded verbatim to Peer::initParameters on the first peer. Use it to activate behaviour specific to each algorithm. Some existing patterns:
PBFTPeerexpectsbyzantine_countto decide how many replicas should run with equivocation faults.RaftPeerconsumes crash parameters such ascrash_count,crash_recovery_round, and message submission rates.- Proof-of-Work peers (Bitcoin/Ethereum) look for mining controls like
miner_count,parasiteLead, and difficulty knobs.
Feel free to embed nested objects or arrays if your algorithm benefits from richer configuration.
{
"logFile": "PBFTByzantine_04.txt",
"threadCount": 48,
"distribution": {
"type": "UNIFORM",
"maxDelay": 1,
"maxMsgsRec": 10
},
"topology": {
"type": "complete",
"initialPeers": 100,
"initialPeerType": "PBFTPeer"
},
"parameters": {
"byzantine_count": 4
},
"tests": 1,
"rounds": 1000
}All consensus-oriented peers derive from ByzantinePeer, which wraps the base Peer with a FaultManager. Faults are modular strategies that can intercept and alter peer actions by overriding any combination of:
onUnicastTo: customise direct sends to a specific neighbour.onSend: intercept broadcast/multicast/random-multicast traffic.onReceive: rewrite or drop inbound messages.onPerformComputation: skip or replace the algorithm’s normal round logic.
Bundled fault implementations live under quantas/Common/:
EquivocateFault(Common/equivocateFault.hpp): Splits a multicast across two quorum sets and injects conflicting payloads, enabling PBFT-style equivocation experiments.ParasiteFault(Common/ParasiteFault.hpp): Models selfish mining by intercepting Proof-of-Work block broadcasts, coordinating a private chain among collaborators, and releasing it once it outruns the public chain.
Attach faults from your algorithm’s initParameters. For instance, PBFTPeer reads parameters.byzantine_count and adds an EquivocateFault to the first n replicas. You can define bespoke attacks by subclassing Fault, overriding the relevant hook(s), and adding the instance through ByzantinePeer::addFault.
LogWriter aggregates per-test metrics into structured JSON records. Algorithms push values during execution (for example PBFT tracks throughput, latency, and faulty confirmations). Each experiment writes its metrics at the end of the run, either to stdout (logFile = "cout") or to the named log file.
make clangTo use the simulator inside Visual Studio:
- Create an empty solution.
- Add all
.cppand.hppfiles fromquantas/andquantas/Common/as existing items. - Set the command arguments for debugging to the desired input file path (for example
C:\Users\User\Documents\QUANTAS\quantas\ExamplePeer\ExampleInput.json). - Define the algorithm macro under Project → Properties → C/C++ → Preprocessor (e.g.,
EXAMPLE_PEER). - Ensure the C++ language standard is set to C++17 or newer.
QUANTAS is further described here: QUANTAS: Quantitative User-friendly Adaptable Networked Things Abstract Simulator
@article{oglio2022quantitative,
title={QUANTAS: Quantitative User-friendly Adaptable Networked Things Abstract Simulator},
author={Oglio, Joseph and Hood, Kendric and Nesterenko, Mikhail and Tixeuil, Sebastien},
journal={arXiv preprint arXiv:2205.04930},
year={2022}
}
