Skip to content

Commit 253eb28

Browse files
committed
Merge remote-tracking branch 'origin/main' into hBFT-fixed
2 parents 6706799 + 95f5d57 commit 253eb28

File tree

8 files changed

+187
-42
lines changed

8 files changed

+187
-42
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package byzzbench.simulator.faults.behaviors;
2+
3+
import byzzbench.simulator.faults.FaultBehavior;
4+
import byzzbench.simulator.faults.FaultContext;
5+
import byzzbench.simulator.transport.Event;
6+
import byzzbench.simulator.transport.MessageEvent;
7+
import byzzbench.simulator.transport.Router;
8+
import lombok.extern.java.Log;
9+
10+
import java.util.Collection;
11+
import java.util.Optional;
12+
13+
@Log
14+
public class ByzzFuzzDropMessageBehavior implements FaultBehavior {
15+
/**
16+
* Router helper to determine if two nodes are connected
17+
*/
18+
Router router = new Router();
19+
20+
/**
21+
* Memoized name of the fault
22+
*/
23+
String name;
24+
25+
/**
26+
* Create a new CreateNetworkPartitionsBehavior
27+
*
28+
* @param partitions A list of partitions to create
29+
*/
30+
public ByzzFuzzDropMessageBehavior(String[][] partitions) {
31+
for (String[] partition : partitions) {
32+
router.isolateNodes(partition);
33+
}
34+
}
35+
36+
/**
37+
* Create a new CreateNetworkPartitionsBehavior
38+
*
39+
* @param partition A single partition to create
40+
*/
41+
public ByzzFuzzDropMessageBehavior(String[] partition) {
42+
this(new String[][]{partition});
43+
}
44+
45+
/**
46+
* Create a new CreateNetworkPartitionsBehavior
47+
*
48+
* @param partition A single partition to create
49+
*/
50+
public ByzzFuzzDropMessageBehavior(Collection<String> partition) {
51+
this(partition.toArray(new String[0]));
52+
}
53+
54+
/**
55+
* Create a new CreateNetworkPartitionsBehavior
56+
*
57+
* @param partition The ID of the node to isolate
58+
*/
59+
public ByzzFuzzDropMessageBehavior(String partition) {
60+
this(new String[]{partition});
61+
}
62+
63+
@Override
64+
public String getId() {
65+
return "createnetworkpartitions-%s".formatted(this.router.getReversePartitionsMapping());
66+
}
67+
68+
@Override
69+
public String getName() {
70+
if (this.name == null) {
71+
// convert the array of arrays into a string
72+
String partitions = this.router.getReversePartitionsMapping().stream()
73+
.map(p -> "[" + String.join(",", p) + "]")
74+
.reduce("", (a, b) -> a + b);
75+
this.name = "Drop message based on partitions %s".formatted(partitions);
76+
}
77+
return this.name;
78+
}
79+
80+
@Override
81+
public void accept(FaultContext context) {
82+
Optional<Event> event = context.getEvent();
83+
84+
if (event.isEmpty()) {
85+
log.warning("No event to mutate");
86+
return;
87+
}
88+
89+
Event e = event.get();
90+
91+
if (!(e instanceof MessageEvent messageEvent)) {
92+
log.warning("Event is not a message event");
93+
return;
94+
}
95+
96+
String sender = messageEvent.getSenderId();
97+
String recipient = messageEvent.getRecipientId();
98+
99+
// if the sender and recipient are in the same partition, do nothing
100+
if (router.haveConnectivity(sender, recipient)) {
101+
return;
102+
}
103+
if(e.getStatus() == Event.Status.QUEUED) {
104+
// otherwise, drop the message: the sender and recipient are in different partitions
105+
context.getScenario().getTransport().dropEvent(e.getEventId());
106+
}
107+
}
108+
}

simulator/src/main/java/byzzbench/simulator/faults/faults/ByzzFuzzNetworkFault.java

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

33
import byzzbench.simulator.faults.BaseFault;
4-
import byzzbench.simulator.faults.behaviors.CreateNetworkPartitionsBehavior;
4+
import byzzbench.simulator.faults.behaviors.ByzzFuzzDropMessageBehavior;
55
import byzzbench.simulator.faults.predicates.MessageRoundPredicate;
66

77
import java.util.Set;
@@ -23,7 +23,7 @@ public ByzzFuzzNetworkFault(Set<String> partition, int round) {
2323
super(
2424
"byzzfuzznetworkfault-%d-%s".formatted(round, String.join("-", partition)),
2525
new MessageRoundPredicate(round),
26-
new CreateNetworkPartitionsBehavior(partition)
26+
new ByzzFuzzDropMessageBehavior(partition)
2727
);
2828
}
2929
}

simulator/src/main/java/byzzbench/simulator/scheduler/BaseScheduler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public List<ClientRequestEvent> getQueuedClientRequestEvents(Scenario scenario)
118118
* @param messageEvents The list of queued message events
119119
* @return The next message event
120120
*/
121-
public Event getNextMessageEvent(List<Event> messageEvents) {
121+
public <T extends Event> T getNextMessageEvent(List<T> messageEvents) {
122122
switch (config.getScheduler().getExecutionMode()) {
123123
case SYNC -> {
124124
return messageEvents.stream().min(Comparator.comparing(Event::getEventId)).orElseThrow();

simulator/src/main/java/byzzbench/simulator/scheduler/RandomScheduler.java

+16-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import java.util.List;
1616
import java.util.Optional;
1717
import java.util.Random;
18-
import java.util.stream.Collectors;
18+
import java.util.SortedSet;
1919

2020
/**
2121
* A scheduler that randomly selects events to deliver, drop, mutate or timeout.
@@ -25,11 +25,14 @@
2525
public class RandomScheduler extends BaseScheduler {
2626
private final Random random = new Random();
2727

28-
2928
public RandomScheduler(ByzzBenchConfig config, MessageMutatorService messageMutatorService) {
3029
super(config, messageMutatorService);
3130
}
3231

32+
public <T> T getRandomElement(List<T> list) {
33+
return list.get(random.nextInt(list.size()));
34+
}
35+
3336
@Override
3437
public String getId() {
3538
return "Random";
@@ -52,21 +55,24 @@ public synchronized Optional<EventDecision> scheduleNext(Scenario scenario) thro
5255
}
5356

5457
List<TimeoutEvent> timeoutEvents = this.getQueuedTimeoutEvents(scenario);
55-
List<Event> clientRequestEvents = availableEvents.stream().filter(ClientRequestEvent.class::isInstance).collect(Collectors.toList());
56-
List<Event> messageEvents = availableEvents.stream().filter(MessageEvent.class::isInstance).collect(Collectors.toList());
58+
List<Event> clientRequestEvents = availableEvents.stream().filter(ClientRequestEvent.class::isInstance).toList();
59+
List<MessageEvent> messageEvents = availableEvents.stream().filter(MessageEvent.class::isInstance).map(MessageEvent.class::cast).toList();
60+
61+
SortedSet<String> faultyReplicaIds = scenario.getFaultyReplicaIds();
62+
List<MessageEvent> mutateableMessageEvents = messageEvents.stream().filter(msg -> faultyReplicaIds.contains(msg.getSenderId())).toList();
5763

5864
int timeoutWeight = timeoutEvents.size() * this.deliverTimeoutWeight();
5965
int deliverMessageWeight = messageEvents.size() * this.deliverMessageWeight();
6066
int deliverClientRequestWeight = clientRequestEvents.size() * this.deliverClientRequestWeight();
6167
int dropMessageWeight = (messageEvents.size() * this.dropMessageWeight(scenario));
62-
int mutateMessageWeight = (messageEvents.size() * this.mutateMessageWeight(scenario));
68+
int mutateMessageWeight = (mutateableMessageEvents.size() * this.mutateMessageWeight(scenario));
6369
int dieRoll = random.nextInt(timeoutWeight + deliverMessageWeight
6470
+ deliverClientRequestWeight + dropMessageWeight + mutateMessageWeight);
6571

6672
// check if we should trigger a timeout
6773
dieRoll -= timeoutWeight;
6874
if (dieRoll < 0) {
69-
Event timeout = timeoutEvents.get(random.nextInt(timeoutEvents.size()));
75+
Event timeout = getRandomElement(timeoutEvents);
7076
scenario.getTransport().deliverEvent(timeout.getEventId());
7177
EventDecision decision = new EventDecision(EventDecision.DecisionType.DELIVERED, timeout.getEventId());
7278
return Optional.of(decision);
@@ -84,7 +90,7 @@ public synchronized Optional<EventDecision> scheduleNext(Scenario scenario) thro
8490
// check if we should target delivering a request from a client to a replica
8591
dieRoll -= deliverClientRequestWeight;
8692
if (dieRoll < 0) {
87-
Event request = clientRequestEvents.get(random.nextInt(clientRequestEvents.size()));
93+
Event request = getRandomElement(clientRequestEvents);
8894
scenario.getTransport().deliverEvent(request.getEventId());
8995
EventDecision decision = new EventDecision(EventDecision.DecisionType.DELIVERED, request.getEventId());
9096
return Optional.of(decision);
@@ -93,7 +99,7 @@ public synchronized Optional<EventDecision> scheduleNext(Scenario scenario) thro
9399
// check if we should drop a message sent between nodes
94100
dieRoll -= dropMessageWeight;
95101
if (dieRoll < 0) {
96-
Event message = messageEvents.get(random.nextInt(messageEvents.size()));
102+
Event message = getRandomElement(messageEvents);
97103
scenario.getTransport().dropEvent(message.getEventId());
98104
EventDecision decision = new EventDecision(EventDecision.DecisionType.DROPPED, message.getEventId());
99105
return Optional.of(decision);
@@ -102,7 +108,7 @@ public synchronized Optional<EventDecision> scheduleNext(Scenario scenario) thro
102108
// check if we should mutate-and-deliver a message sent between nodes
103109
dieRoll -= mutateMessageWeight;
104110
if (dieRoll < 0) {
105-
Event message = messageEvents.get(random.nextInt(messageEvents.size()));
111+
Event message = getRandomElement(mutateableMessageEvents);
106112
List<MessageMutationFault> mutators = this.getMessageMutatorService().getMutatorsForEvent(message);
107113

108114
if (mutators.isEmpty()) {
@@ -112,7 +118,7 @@ public synchronized Optional<EventDecision> scheduleNext(Scenario scenario) thro
112118
}
113119
scenario.getTransport().applyMutation(
114120
message.getEventId(),
115-
mutators.get(random.nextInt(mutators.size())));
121+
getRandomElement(mutators));
116122
scenario.getTransport().deliverEvent(message.getEventId());
117123

118124
EventDecision decision = new EventDecision(EventDecision.DecisionType.MUTATED_AND_DELIVERED, message.getEventId());

simulator/src/main/java/byzzbench/simulator/transport/Router.java

+23-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import lombok.Getter;
44

5-
import java.util.SortedMap;
6-
import java.util.TreeMap;
5+
import java.util.*;
76
import java.util.concurrent.atomic.AtomicInteger;
87

98
/**
@@ -30,6 +29,7 @@ public class Router {
3029

3130
/**
3231
* Isolates a node from the rest of the network.
32+
*
3333
* @param nodeId The ID of the node to isolate.
3434
*/
3535
public void isolateNode(String nodeId) {
@@ -38,6 +38,7 @@ public void isolateNode(String nodeId) {
3838

3939
/**
4040
* Isolates a set of nodes from the rest of the network, but not from each other.
41+
*
4142
* @param nodeIds The IDs of the nodes to isolate.
4243
*/
4344
public void isolateNodes(String[] nodeIds) {
@@ -49,6 +50,7 @@ public void isolateNodes(String[] nodeIds) {
4950

5051
/**
5152
* Re-joins a node to the rest of the network.
53+
*
5254
* @param nodeId The ID of the node to re-join.
5355
*/
5456
public void healNode(String nodeId) {
@@ -65,6 +67,7 @@ public void resetPartitions() {
6567

6668
/**
6769
* Gets the partition ID of a node.
70+
*
6871
* @param nodeId The ID of the node.
6972
* @return The partition ID of the node.
7073
*/
@@ -74,6 +77,7 @@ public int getNodePartition(String nodeId) {
7477

7578
/**
7679
* Checks if two nodes are on the same partition.
80+
*
7781
* @param nodeId1 The ID of the first node.
7882
* @param nodeId2 The ID of the second node.
7983
* @return True if the nodes are on the same partition, false otherwise.
@@ -84,6 +88,7 @@ public boolean haveConnectivity(String nodeId1, String nodeId2) {
8488

8589
/**
8690
* Checks if there are any active partitions.
91+
*
8792
* @return True if there are active partitions, false otherwise.
8893
*/
8994
public boolean hasActivePartitions() {
@@ -92,9 +97,25 @@ public boolean hasActivePartitions() {
9297

9398
/**
9499
* Gets the next unused partition ID.
100+
*
95101
* @return The next unused partition ID.
96102
*/
97103
private int getUnusedPartitionId() {
98104
return partitionSequenceNumber.getAndIncrement();
99105
}
106+
107+
/**
108+
* Gets the reverse mapping of partition IDs to nodes.
109+
*
110+
* @return An array of arrays of node IDs that are in the same partition.
111+
*/
112+
public List<List<String>> getReversePartitionsMapping() {
113+
Map<Integer, List<String>> reversePartitions = new HashMap<>();
114+
for (Map.Entry<String, Integer> entry : partitions.entrySet()) {
115+
reversePartitions.computeIfAbsent(entry.getValue(), k -> new ArrayList<>())
116+
.add(entry.getKey());
117+
}
118+
119+
return new ArrayList<>(reversePartitions.values());
120+
}
100121
}

simulator/src/main/java/byzzbench/simulator/transport/Transport.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,12 @@ private synchronized void appendEvent(Event event) {
267267
// add the event to the map
268268
this.events.put(event.getEventId(), event);
269269

270+
// notify observers
271+
this.observers.forEach(o -> o.onEventAdded(event));
272+
270273
// apply automatic faults
271274
this.automaticFaults.values()
272275
.forEach(f -> f.testAndAccept(new FaultContext(this.scenario, event)));
273-
274-
// notify observers
275-
this.observers.forEach(o -> o.onEventAdded(event));
276276
}
277277

278278
/**

webui/app/page.tsx

+30-23
Original file line numberDiff line numberDiff line change
@@ -99,29 +99,36 @@ export default function Home() {
9999
</Stack>
100100

101101
<AppShell.Aside p="md" maw={400}>
102-
<Stack gap="xs">
103-
<Title order={5}>Schedule</Title>
104-
<ScrollArea mah={500} type="auto">
105-
{schedule?.data && (
106-
<ScheduleDetails
107-
hideTitle
108-
hideMaterializeButton
109-
hideDownloadButton
110-
hideDetailsButton
111-
hideScenario
112-
hideSaveButton
113-
title="Current Schedule"
114-
schedule={schedule.data}
115-
/>
116-
)}
117-
</ScrollArea>
118-
<Title order={5}>Trigger Faulty Behaviors</Title>
119-
<ScenarioEnabledFaultsList />
120-
<Title order={5}>Scheduled Faults</Title>
121-
<ScenarioScheduledFaultsList />
122-
<Title order={5}>Discarded Events</Title>
123-
<DroppedMessagesList />
124-
</Stack>
102+
<ScrollArea type="never" mah="100vh">
103+
<Stack gap="xs">
104+
<Title order={5}>Schedule</Title>
105+
<ScrollArea mah={500} type="always" style={{ overflowY: "auto" }}>
106+
<div style={{ maxHeight: "500px", overflowY: "auto" }}>
107+
{schedule?.data && (
108+
<ScheduleDetails
109+
hideTitle
110+
hideMaterializeButton
111+
hideDownloadButton
112+
hideDetailsButton
113+
hideScenario
114+
hideSaveButton
115+
title="Current Schedule"
116+
schedule={schedule.data}
117+
/>
118+
)}
119+
</div>
120+
</ScrollArea>
121+
<Title order={5}>Trigger Faulty Behaviors</Title>
122+
<ScenarioEnabledFaultsList />
123+
<Title order={5}>ScheduledFaults</Title>
124+
<ScenarioScheduledFaultsList />
125+
<Title order={5}>Discarded Events</Title>
126+
127+
<ScrollArea mah={500} type="always" style={{ overflowY: "auto" }}>
128+
<DroppedMessagesList />
129+
</ScrollArea>
130+
</Stack>
131+
</ScrollArea>
125132
</AppShell.Aside>
126133
</Container>
127134
);

webui/components/Events/DroppedMessagesList.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@ import { useGetDroppedMessages } from "@/lib/byzzbench-client";
55

66
export const DroppedMessagesList = () => {
77
const { data: droppedMessages } = useGetDroppedMessages();
8-
return <MessagesList messageIds={droppedMessages?.data ?? []} />;
8+
9+
const reverseOrder = (droppedMessages?.data ?? []).sort((a, b) => b - a);
10+
11+
return <MessagesList messageIds={reverseOrder} />;
912
};

0 commit comments

Comments
 (0)