Skip to content

Commit 2cb182c

Browse files
authored
Merge pull request #92 from joaomlneto/pbft-reference
Add PBFT Reference Implementation, ByzzFuzz Scheduler, Parameterization via YML file
2 parents 010e0a4 + b3ce5a8 commit 2cb182c

File tree

155 files changed

+11522
-1011
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+11522
-1011
lines changed

README.md

+22-17
Original file line numberDiff line numberDiff line change
@@ -89,35 +89,38 @@ Reference: https://pnpm.io/installation
8989

9090
For other operating systems, please refer to the respective installation instructions.
9191

92-
## Building
92+
## Benchmark Suite
9393

94-
To install dependencies and build the benchmarking suite, run the following commands:
95-
96-
```
97-
./gradlew build
98-
(cd webui && pnpm install)
99-
```
100-
101-
## Running
102-
103-
To run the benchmarking suite, run the following command:
94+
To build and run the benchmarking suite, run the following command:
10495

10596
```
10697
./gradlew bootRun
10798
```
10899

109100
## Web Interface
110101

111-
To run the web interface, run the following command:
102+
The web UI is a simple React application (using NextJS/TypeScript) that allows you to interact with the simulator. It is
103+
a work in progress, but provides useful insights into the behavior of the protocols.
104+
105+
To build the web interface, run the following command **while the simulator is running**:
112106

113107
```
114-
(cd webui && pnpm run kubb:generate && pnpm run dev)
108+
(cd webui && pnpm install && pnpm run kubb:generate && pnpm run build)
115109
```
116110

117111
> [!NOTE]
118112
> The simulator must be running for `kubb:generate` to succeed.
119113
120-
The UI should then be available at http://localhost:3000.
114+
The above command will generate the necessary TypeScript bindings for the simulator and build the web interface. You
115+
only need to run it once.
116+
117+
Then, to start the web server, run the following command:
118+
119+
```
120+
(cd webui && pnpm run start)
121+
```
122+
123+
The UI should then be available at http://localhost:3000
121124

122125
## Running Benchmarks
123126

@@ -126,12 +129,14 @@ We currently have the following protocols implemented:
126129
- ~~[PBFT](simulator/src/main/java/byzzbench/simulator/protocols/pbft/PbftReplica.java): The original PBFT protocol, as
127130
described in
128131
the [PBFT paper](https://www.microsoft.com/en-us/research/publication/practical-byzantine-fault-tolerance/);~~
129-
- [PBFT-Java](simulator/src/main/java/byzzbench/simulator/protocols/pbft_java/PbftReplica.java): A buggy version of
130-
PBFT,
131-
from the [PBFT-Java repository](https://github.com/caojohnny/pbft-java);
132+
- [PBFT-Java](simulator/src/main/java/byzzbench/simulator/protocols/pbft_java/PbftReplica.java): A buggy implementation
133+
of the PBFT protocol, ported from the [PBFT-Java repository](https://github.com/caojohnny/pbft-java);
132134
- [Fast-HotStuff](simulator/src/main/java/byzzbench/simulator/protocols/fasthotstuff/FastHotStuffReplica.java): An
133135
unsuccessful attempt at improving the design of HotStuff, as described in
134136
the [Fast-HotStuff paper](https://arxiv.org/abs/2010.11454);
137+
- [XRPL](simulator/src/main/java/byzzbench/simulator/protocols/xrpl/XRPLReplica.java): [XRP Ledger Consensus Protocol](https://xrpl.org/docs/concepts/consensus-protocol/consensus-research.html)
138+
implementation;
139+
- *Your protocol here?* :-)
135140

136141
## Documentation
137142

simulator/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
annotationProcessor("org.projectlombok:lombok")
3535
testImplementation("org.springframework.boot:spring-boot-starter-test")
3636
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
37+
implementation("org.springframework.boot:spring-boot-starter-validation")
3738

3839
//implementation("org.springdoc:springdoc-openapi-ui:1.8.0")
3940
// either API (just documentation) or API + UI (documentation + Swagger UI)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package byzzbench.simulator;
2+
3+
import org.springframework.beans.BeansException;
4+
import org.springframework.context.ApplicationContext;
5+
import org.springframework.context.ApplicationContextAware;
6+
import org.springframework.stereotype.Component;
7+
8+
@Component
9+
public class ApplicationContextUtils implements ApplicationContextAware {
10+
private static ApplicationContext ctx;
11+
12+
public static ApplicationContext getApplicationContext() {
13+
return ctx;
14+
}
15+
16+
@Override
17+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
18+
ctx = applicationContext;
19+
}
20+
}

simulator/src/main/java/byzzbench/simulator/BaseScenario.java

+115-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package byzzbench.simulator;
22

3-
import byzzbench.simulator.faults.HealNodeNetworkFault;
4-
import byzzbench.simulator.faults.IsolateProcessNetworkFault;
3+
import byzzbench.simulator.config.ByzzBenchConfig;
4+
import byzzbench.simulator.faults.Fault;
5+
import byzzbench.simulator.faults.FaultContext;
6+
import byzzbench.simulator.faults.factories.ByzzFuzzScenarioFaultFactory;
7+
import byzzbench.simulator.faults.faults.HealNodeNetworkFault;
8+
import byzzbench.simulator.faults.faults.IsolateProcessNetworkFault;
59
import byzzbench.simulator.schedule.Schedule;
610
import byzzbench.simulator.scheduler.Scheduler;
711
import byzzbench.simulator.state.AgreementPredicate;
@@ -30,98 +34,103 @@ public abstract class BaseScenario implements Scenario {
3034
*/
3135
@ToString.Exclude
3236
protected final transient Transport transport;
33-
3437
/**
35-
* The scheduler for the scenario.
38+
* The timekeeper for the scenario.
3639
*/
37-
protected final Scheduler scheduler;
38-
40+
protected final transient Timekeeper timekeeper;
3941
/**
40-
* Map of node id to the {@link Replica} object.
42+
* The scheduler for the scenario.
4143
*/
42-
@Getter(onMethod_ = {@Synchronized})
43-
@JsonIgnore
44-
private final NavigableMap<String, Replica> nodes = new TreeMap<>();
45-
44+
protected final Scheduler scheduler;
4645
/**
47-
* Map of client id to the {@link Client} object.
46+
* Map of node id to the {@link Node} object.
4847
*/
4948
@Getter(onMethod_ = {@Synchronized})
5049
@JsonIgnore
51-
private final NavigableMap<String, Client> clients = new TreeMap<>();
52-
50+
private final NavigableMap<String, Node> nodes = new TreeMap<>();
5351
/**
5452
* A unique identifier for the scenario.
5553
*/
5654
private final String id;
57-
5855
/**
5956
* The invariants that must be satisfied by the scenario at all times.
6057
*/
6158
private final List<ScenarioPredicate> invariants = List.of(new AgreementPredicate(), new LivenessPredicate());
62-
6359
/**
6460
* The schedule of events in order of delivery.
6561
*/
6662
@Getter(onMethod_ = {@Synchronized})
6763
@JsonIgnore
6864
private final Schedule schedule = Schedule.builder().scenario(this).build();
69-
7065
/**
7166
* The observers of this scenario.
7267
*/
7368
@JsonIgnore
7469
private final transient List<ScenarioObserver> observers = new java.util.ArrayList<>();
70+
/**
71+
* The termination condition for the scenario.
72+
*/
73+
protected ScenarioPredicate terminationCondition;
74+
/**
75+
* Pseudo-random number generator for the scenario.
76+
*/
77+
Random rand;
7578

7679
/**
7780
* Creates a new scenario with the given unique identifier and scheduler.
7881
*
79-
* @param id The unique identifier for the scenario.
82+
* @param id The unique identifier for the scenario.
8083
* @param scheduler The scheduler for the scenario.
8184
*/
8285
protected BaseScenario(String id, Scheduler scheduler) {
8386
this.id = id;
8487
this.scheduler = scheduler;
8588
this.transport = new Transport(this);
89+
this.timekeeper = new Timekeeper(this);
8690
this.setupScenario();
8791
this.addObserver(new AdobDistributedState());
8892
}
8993

9094
/**
9195
* Adds an observer to the scenario.
96+
*
9297
* @param observer The observer to add.
9398
*/
9499
public void addObserver(ScenarioObserver observer) {
95100
this.observers.add(observer);
96101
}
97102

98103
/**
99-
* Removes an observer from the scenario.
100-
* @param observer The observer to remove.
104+
* Removes all registered clients
101105
*/
102-
public void removeObserver(ScenarioObserver observer) {
103-
this.observers.remove(observer);
106+
private void removeAllClients() {
107+
this.nodes.entrySet().stream()
108+
.filter(entry -> entry.getValue() instanceof Client)
109+
.map(Map.Entry::getKey)
110+
.forEach(this.nodes::remove);
104111
}
105112

106113
/**
107114
* Sets the number of clients in the scenario.
115+
*
108116
* @param numClients The number of clients to set.
109117
*/
110118
protected void setNumClients(int numClients) {
111-
this.clients.clear();
119+
this.removeAllClients();
112120
for (int i = 0; i < numClients; i++) {
113121
String clientId = String.format("C%d", i);
114-
Client client = Client.builder().clientId(clientId).transport(this.transport).build();
122+
Client client = Client.builder().id(clientId).scenario(this).build();
115123
this.addClient(client);
116124
}
117125
}
118126

119127
/**
120128
* Adds a client to the scenario.
129+
*
121130
* @param client The client to add.
122131
*/
123132
public void addClient(Client client) {
124-
this.clients.put(client.getClientId(), client);
133+
this.nodes.put(client.getId(), client);
125134
// notify the observers
126135
this.observers.forEach(o -> o.onClientAdded(client));
127136
}
@@ -133,19 +142,23 @@ public void addClient(Client client) {
133142
*/
134143
public void addNode(Replica replica) {
135144
// add the node to the list of nodes
136-
getNodes().put(replica.getNodeId(), replica);
145+
getNodes().put(replica.getId(), replica);
137146

138147
// for each node, add a IsolateNodeFault and a HealNodeFault
139-
this.transport.addNetworkFault(new IsolateProcessNetworkFault(replica.getNodeId()));
140-
this.transport.addNetworkFault(new HealNodeNetworkFault(replica.getNodeId()));
148+
this.transport.addFault(new IsolateProcessNetworkFault(replica.getId()), false);
149+
this.transport.addFault(new HealNodeNetworkFault(replica.getId()), false);
141150

142151
// notify the observers
143152
this.observers.forEach(o -> o.onReplicaAdded(replica));
144153
}
145154

146155
@Override
147-
public synchronized Replica getNode(String replicaId) {
148-
return this.getNodes().get(replicaId);
156+
public synchronized Replica getNode(String nodeId) {
157+
Node node = this.getNodes().get(nodeId);
158+
if (!(node instanceof Replica replica)) {
159+
throw new IllegalArgumentException("Node with ID " + nodeId + " is not a replica.");
160+
}
161+
return replica;
149162
}
150163

151164
/**
@@ -154,8 +167,9 @@ public synchronized Replica getNode(String replicaId) {
154167
@Override
155168
public final void setupScenario() {
156169
this.setup();
157-
getClients().values().forEach(Client::initializeClient);
158-
getNodes().values().forEach(Replica::initialize);
170+
this.getClients().values().forEach(Client::initialize);
171+
this.getNodes().values().forEach(Node::initialize);
172+
this.scheduler.initializeScenario(this);
159173
}
160174

161175
public final void loadParameters(JsonNode parameters) {
@@ -170,11 +184,41 @@ public final void loadParameters(JsonNode parameters) {
170184
this.scheduler.loadParameters(schedulerParameters);
171185
}
172186

187+
if (parameters.has("faults")) {
188+
System.out.println("Faults: " + parameters.get("faults").toPrettyString());
189+
ByzzFuzzScenarioFaultFactory faultFactory = new ByzzFuzzScenarioFaultFactory();
190+
List<Fault> faults = faultFactory.generateFaults(new FaultContext(this));
191+
faults.forEach(fault -> this.transport.addFault(fault, true));
192+
}
193+
173194
this.loadScenarioParameters(parameters);
174195
}
175196

197+
public final void loadParameters(ByzzBenchConfig.ScenarioConfig config) {
198+
System.out.println("Loading parameters for scenario: " + config.getId());
199+
System.out.println("Loading parameters for scenario: " + config.getId());
200+
System.out.println("Loading parameters for scenario: " + config.getId());
201+
System.out.println("Loading parameters for scenario: " + config.getId());
202+
System.out.println("Loading parameters for scenario: " + config.getId());
203+
System.out.println("Loading parameters for scenario: " + config.getId());
204+
System.out.println("Loading parameters for scenario: " + config.getId());
205+
System.out.println("Loading parameters for scenario: " + config.getId());
206+
System.out.println("Loading parameters for scenario: " + config.getId());
207+
System.out.println("Loading parameters for scenario: " + config.getId());
208+
System.out.println("Loading parameters for scenario: " + config.getId());
209+
System.out.println("Loading parameters for scenario: " + config.getId());
210+
System.out.println("Loading parameters for scenario: " + config.getId());
211+
System.out.println("Loading parameters for scenario: " + config.getId());
212+
System.out.println("Loading parameters for scenario: " + config.getId());
213+
System.out.println("Loading parameters for scenario: " + config.getId());
214+
System.out.println("Loading parameters for scenario: " + config.getId());
215+
System.out.println("Loading parameters for scenario: " + config.getId());
216+
System.out.println("Loading parameters for scenario: " + config.getId());
217+
}
218+
176219
/**
177220
* Loads the parameters for the scenario from a JSON object.
221+
*
178222
* @param parameters The JSON object containing the parameters for the scenario.
179223
*/
180224
protected abstract void loadScenarioParameters(JsonNode parameters);
@@ -198,6 +242,7 @@ public final void runScenario() {
198242

199243
/**
200244
* Checks if the invariants are satisfied by the scenario in its current state.
245+
*
201246
* @return True if the invariants are satisfied, false otherwise.
202247
*/
203248
@Override
@@ -207,15 +252,53 @@ public final boolean invariantsHold() {
207252

208253
/**
209254
* Returns the invariants that are not satisfied by the scenario in its current state.
255+
*
210256
* @return The invariants that are not satisfied by the scenario in its current state.
211257
*/
212258
public final SortedSet<ScenarioPredicate> unsatisfiedInvariants() {
213259
return this.invariants.stream().filter(invariant -> !invariant.test(this))
214260
.collect(Collectors.toCollection(TreeSet::new));
215261
}
216262

263+
@Override
264+
public boolean isTerminated() {
265+
return this.terminationCondition.test(this);
266+
}
267+
217268
public final void finalizeSchedule() {
218269
this.getSchedule().finalizeSchedule(this.unsatisfiedInvariants());
219270
}
220271

272+
public void writeToFile() {
273+
274+
}
275+
276+
@Override
277+
public SortedSet<String> getNodeIds(Node node) {
278+
return new TreeSet<>(this.getNodes().keySet());
279+
}
280+
281+
@Override
282+
public NavigableMap<String, Client> getClients() {
283+
NavigableMap<String, Client> clients = new TreeMap<>();
284+
this.getNodes()
285+
.values()
286+
.stream()
287+
.filter(Client.class::isInstance)
288+
.map(Client.class::cast)
289+
.forEach(client -> clients.put(client.getId(), client));
290+
return clients;
291+
}
292+
293+
@Override
294+
public NavigableMap<String, Replica> getReplicas() {
295+
NavigableMap<String, Replica> replicas = new TreeMap<>();
296+
this.getNodes()
297+
.values()
298+
.stream()
299+
.filter(Replica.class::isInstance)
300+
.map(Replica.class::cast)
301+
.forEach(replica -> replicas.put(replica.getId(), replica));
302+
return replicas;
303+
}
221304
}

0 commit comments

Comments
 (0)