Skip to content

Commit dd85b79

Browse files
authored
Merge pull request #128 from joaomlneto/hBFT
hbft to main
2 parents 2443b60 + 99cce7e commit dd85b79

File tree

65 files changed

+8043
-261
lines changed

Some content is hidden

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

65 files changed

+8043
-261
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"java.configuration.updateBuildConfiguration": "interactive"
3+
}

schedules/hbft known violation.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"events":[{"type":"ClientRequest","eventId":2,"createdAt":1736960554.6270425,"status":"DELIVERED","recipientId":"B","senderId":"C0","timestamp":1736960554627,"payload":{"timestamp":1736960554627,"operation":"C0/1","signed":false,"signedBy":null,"type":"DefaultClientRequest"},"deliveredAt":null},{"type":"ClientRequest","eventId":9,"createdAt":1736960554.6280425,"status":"DELIVERED","recipientId":"B","senderId":"C1","timestamp":1736960554628,"payload":{"timestamp":1736960554628,"operation":"C1/1","signed":false,"signedBy":null,"type":"DefaultClientRequest"},"deliveredAt":null},{"type":"Message","eventId":16,"createdAt":1736960556.0801437,"status":"DELIVERED","recipientId":"A","senderId":"B","timestamp":0,"payload":{"viewNumber":1,"sequenceNumber":1,"digest":"CtMEMkjjICiJ0zfwSspofgpOE5M=","request":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"},"signed":true,"signedBy":"B","type":"PREPARE"},"deliveredAt":null},{"type":"Message","eventId":18,"createdAt":1736960556.0801437,"status":"DELIVERED","recipientId":"D","senderId":"B","timestamp":0,"payload":{"viewNumber":1,"sequenceNumber":1,"digest":"CtMEMkjjICiJ0zfwSspofgpOE5M=","request":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"},"signed":true,"signedBy":"B","type":"PREPARE"},"deliveredAt":null},{"type":"Message","eventId":21,"createdAt":1736960556.0811455,"status":"DELIVERED","recipientId":"D","senderId":"B","timestamp":0,"payload":{"viewNumber":1,"sequenceNumber":1,"digest":"CtMEMkjjICiJ0zfwSspofgpOE5M=","request":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"},"replicaId":"B","speculativeHistory":{"history":{},"empty":true,"greatestSeqNumber":0,"requests":{}},"signed":true,"signedBy":"B","type":"COMMIT"},"deliveredAt":null},{"type":"Message","eventId":31,"createdAt":1736960562.4083266,"status":"DELIVERED","recipientId":"D","senderId":"A","timestamp":0,"payload":{"viewNumber":1,"sequenceNumber":1,"digest":"CtMEMkjjICiJ0zfwSspofgpOE5M=","request":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"},"replicaId":"A","speculativeHistory":{"history":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}},"empty":false,"greatestSeqNumber":1,"requests":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}}},"signed":true,"signedBy":"A","type":"COMMIT"},"deliveredAt":null},{"type":"ClientRequest","eventId":13,"createdAt":1736960554.6280425,"status":"DELIVERED","recipientId":"D","senderId":"C1","timestamp":1736960554628,"payload":{"timestamp":1736960554628,"operation":"C1/1","signed":false,"signedBy":null,"type":"DefaultClientRequest"},"deliveredAt":null},{"type":"Timeout","eventId":42,"createdAt":1736960574.9117787,"status":"DELIVERED","description":"REQUEST1736960554628","nodeId":"D","timeout":10,"expiresAt":10.001,"task":{},"recipientId":"D","deliveredAt":null},{"type":"Timeout","eventId":22,"createdAt":1736960556.9483867,"status":"DELIVERED","description":"REQUEST1736960554628","nodeId":"B","timeout":10,"expiresAt":10.002,"task":{},"recipientId":"B","deliveredAt":null},{"type":"MutateMessage","eventId":52,"senderId":"B","recipientId":"C","payload":{"eventId":24,"mutatorId":"hbft-prepare-sec-dec"},"createdAt":1736960584.0889707,"status":"DELIVERED","deliveredAt":null},{"type":"Message","eventId":24,"createdAt":1736960556.9483867,"status":"DELIVERED","recipientId":"C","senderId":"B","timestamp":0,"payload":{"viewNumber":1,"sequenceNumber":1,"digest":"vK5bH85oOq0vaso+TSfV+PLPXFE=","request":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"},"signed":true,"signedBy":"B","type":"PREPARE"},"deliveredAt":null},{"type":"Message","eventId":47,"createdAt":1736960576.5642834,"status":"DELIVERED","recipientId":"C","senderId":"D","timestamp":0,"payload":{"newViewNumber":2,"speculativeHistoryP":null,"speculativeHistoryQ":null,"requestsR":{"1":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"}},"replicaId":"D","signed":true,"signedBy":"D","type":"VIEW-CHANGE"},"deliveredAt":null},{"type":"MutateMessage","eventId":56,"senderId":"B","recipientId":"C","payload":{"eventId":50,"mutatorId":"hbft-view-change-remove-first-request"},"createdAt":1736960613.561314,"status":"DELIVERED","deliveredAt":null},{"type":"MutateMessage","eventId":57,"senderId":"B","recipientId":"C","payload":{"eventId":50,"mutatorId":"hbft-view-change-decrement-last-request-seqNum"},"createdAt":1736960617.884014,"status":"DELIVERED","deliveredAt":null},{"type":"Message","eventId":50,"createdAt":1736960579.971162,"status":"DELIVERED","recipientId":"C","senderId":"B","timestamp":0,"payload":{"newViewNumber":2,"speculativeHistoryP":null,"speculativeHistoryQ":null,"requestsR":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}},"replicaId":"B","signed":true,"signedBy":"B","type":"VIEW-CHANGE"},"deliveredAt":null},{"type":"Message","eventId":62,"createdAt":1736960619.5324345,"status":"DELIVERED","recipientId":"A","senderId":"C","timestamp":0,"payload":{"newViewNumber":2,"viewChangeProofs":[{"newViewNumber":2,"speculativeHistoryP":null,"speculativeHistoryQ":null,"requestsR":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}},"replicaId":"B","signed":true,"signedBy":"B","type":"VIEW-CHANGE"},{"newViewNumber":2,"speculativeHistoryP":null,"speculativeHistoryQ":null,"requestsR":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}},"replicaId":"C","signed":true,"signedBy":"C","type":"VIEW-CHANGE"},{"newViewNumber":2,"speculativeHistoryP":null,"speculativeHistoryQ":null,"requestsR":{"1":{"operation":"C0/1","timestamp":1736960554627,"clientId":"C0","signed":false,"signedBy":null,"type":"REQUEST"}},"replicaId":"D","signed":true,"signedBy":"D","type":"VIEW-CHANGE"}],"checkpoint":{"sequenceNumber":0,"history":null},"speculativeHistory":{"history":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}},"empty":false,"greatestSeqNumber":1,"requests":{"1":{"operation":"C1/1","timestamp":1736960554628,"clientId":"C1","signed":false,"signedBy":null,"type":"REQUEST"}}},"signed":true,"signedBy":"C","type":"NEW-VIEW"},"deliveredAt":null}],"brokenInvariants":[],"scenarioId":"hbft","finalized":false}

schedules/hbft-bug-or-potential-violation.json

+1
Large diffs are not rendered by default.

schedules/hbft-random-0-0-violation.json

+1
Large diffs are not rendered by default.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public final void setupScenario() {
175175
this.markReplicaFaulty(replicaIds.get(i));
176176
}
177177

178-
this.getClients().values().forEach(Client::initialize);
178+
//this.getClients().values().forEach(Client::initialize);
179179
this.getNodes().values().forEach(Node::initialize);
180180
this.scheduler.initializeScenario(this);
181181
}

simulator/src/main/java/byzzbench/simulator/Client.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package byzzbench.simulator;
22

3+
import byzzbench.simulator.protocols.hbft.message.ClientRequestMessage;
34
import byzzbench.simulator.transport.MessagePayload;
45
import byzzbench.simulator.utils.NonNull;
56
import com.fasterxml.jackson.annotation.JsonIgnore;
6-
import lombok.Builder;
77
import lombok.Getter;
88
import lombok.RequiredArgsConstructor;
9+
import lombok.experimental.SuperBuilder;
910

1011
import java.io.Serializable;
1112
import java.time.Duration;
@@ -19,31 +20,31 @@
1920
* The client is responsible for sending requests to the replicas in the system.
2021
*/
2122
@Getter
22-
@Builder
23+
@SuperBuilder
2324
@RequiredArgsConstructor
2425
public class Client implements Serializable, Node {
2526
/**
2627
* The scenario object that this client belongs to.
2728
*/
2829
@JsonIgnore
2930
@NonNull
30-
private final transient Scenario scenario;
31+
protected final transient Scenario scenario;
3132

3233
/**
3334
* The unique ID of the client.
3435
*/
3536
@NonNull
36-
private final String id;
37+
protected final String id;
3738

3839
/**
3940
* The sequence number of the next request to be sent by the client.
4041
*/
41-
private final AtomicLong requestSequenceNumber = new AtomicLong(0);
42+
protected final AtomicLong requestSequenceNumber = new AtomicLong(0);
4243

4344
/**
4445
* The maximum number of requests that can be sent by the client.
4546
*/
46-
private final long maxRequests = 3;
47+
protected final long maxRequests = 100;
4748

4849
/**
4950
* The replies received by the client.
@@ -63,8 +64,8 @@ public void initialize() {
6364
*/
6465
public void sendRequest() {
6566
String recipientId = this.getScenario().getReplicas().keySet().iterator().next();
66-
String requestId = String.format("%s/%d", this.id, this.requestSequenceNumber.getAndIncrement());
67-
this.getScenario().getTransport().sendClientRequest(this.id, requestId, recipientId);
67+
MessagePayload payload = new ClientRequestMessage(this.getCurrentTime().toEpochMilli(), recipientId);
68+
this.getScenario().getTransport().sendMessage(this, payload, recipientId);
6869
}
6970

7071
/**
@@ -94,7 +95,7 @@ public Instant getCurrentTime() {
9495
* @return the timer object
9596
*/
9697
public long setTimeout(String name, Runnable r, Duration timeout) {
97-
return this.scenario.getTransport().setTimeout(this, r, timeout);
98+
return this.scenario.getTransport().setTimeout(this, r, timeout, "REQUEST");
9899
}
99100

100101
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package byzzbench.simulator;
2+
3+
import byzzbench.simulator.protocols.hbft.message.*;
4+
import byzzbench.simulator.protocols.hbft.pojo.ClientReplyKey;
5+
import byzzbench.simulator.transport.MessagePayload;
6+
import com.fasterxml.jackson.annotation.JsonIgnore;
7+
import lombok.Getter;
8+
import lombok.experimental.SuperBuilder;
9+
10+
import java.io.Serializable;
11+
import java.security.MessageDigest;
12+
import java.security.NoSuchAlgorithmException;
13+
import java.time.Duration;
14+
import java.util.*;
15+
16+
17+
/**
18+
* Represents a client in the system. Each client has a unique identifier.
19+
* The client is responsible for sending requests to the replicas in the system.
20+
*/
21+
@Getter
22+
@SuperBuilder
23+
public class HbftClient extends Client {
24+
/**
25+
* The message digest algorithm to use for hashing messages.
26+
*/
27+
@JsonIgnore
28+
static MessageDigest md;
29+
30+
static {
31+
try {
32+
md = MessageDigest.getInstance("SHA-1");
33+
} catch (NoSuchAlgorithmException e) {
34+
throw new RuntimeException(e);
35+
}
36+
}
37+
38+
/**
39+
* The replies received by the client.
40+
*/
41+
private final SortedMap<String, SortedMap<ClientReplyKey, Collection<MessagePayload>>> hbftreplies = new TreeMap<>();
42+
43+
/**
44+
* The request sequence number already completed.
45+
*/
46+
private final Set<ClientReplyKey> completedRequests = new HashSet<>();
47+
48+
/**
49+
* The sent requests.
50+
*/
51+
private final SortedMap<Long, RequestMessage> sentRequests = new TreeMap<>();
52+
53+
/**
54+
* The sent requests by timestamp.
55+
*/
56+
private final SortedMap<Long, String> sentRequestsByTimestamp = new TreeMap<>();
57+
58+
/**
59+
* timeouts
60+
*/
61+
private final SortedMap<Long, Long> timeouts = new TreeMap<>();
62+
63+
/**
64+
* Timeout for client in seconds
65+
*/
66+
private final long timeout = 8;
67+
68+
69+
/**
70+
* As of hBFT 4.1, sends a request to all replica in the system.
71+
*/
72+
@Override
73+
public void sendRequest() {
74+
String requestId = String.format("%s/%d", super.id, super.requestSequenceNumber.incrementAndGet());
75+
long timestamp = this.getCurrentTime().toEpochMilli();
76+
RequestMessage request = new RequestMessage(requestId, timestamp, super.id);
77+
this.sentRequests.put(super.requestSequenceNumber.get(), request);
78+
this.sentRequestsByTimestamp.put(timestamp, requestId);
79+
this.broadcastRequest(timestamp, requestId);
80+
81+
// Set timeout
82+
Long timeoutId = this.setTimeout("REQUEST", this::retransmitOrPanic, this.timeout);
83+
timeouts.put(super.requestSequenceNumber.get(), timeoutId);
84+
}
85+
86+
public void retransmitOrPanic() {
87+
long tolerance = (long) Math.floor((super.scenario.getTransport().getNodeIds().size() - 1) / 3);
88+
if (this.shouldRetransmit(tolerance)) {
89+
String requestId = String.format("%s/%d", super.id, super.requestSequenceNumber.get());
90+
// Based on hBFT 4.1 it uses the identical request
91+
// TODO: It probably should not be the same timestamp
92+
long timestamp = this.sentRequests.get(super.requestSequenceNumber.get()).getTimestamp();
93+
this.broadcastRequest(timestamp, requestId);
94+
} else if (this.shouldPanic(tolerance)) {
95+
RequestMessage message = this.sentRequests.get(super.requestSequenceNumber.get());
96+
PanicMessage panic = new PanicMessage(this.digest(message), this.getCurrentTime().toEpochMilli(), super.id);
97+
super.scenario.getTransport().multicast(this, super.scenario.getTransport().getNodeIds(), panic);
98+
}
99+
this.clearTimeout(timeouts.get(super.requestSequenceNumber.get()));
100+
Long timeoutId = this.setTimeout("REQUEST", this::retransmitOrPanic, this.timeout);
101+
timeouts.put(super.requestSequenceNumber.get(), timeoutId);
102+
}
103+
104+
private void broadcastRequest(long timestamp, String requestId) {
105+
MessagePayload payload = new ClientRequestMessage(timestamp, requestId);
106+
SortedSet<String> replicaIds = super.scenario.getTransport().getNodeIds();
107+
getScenario().getTransport().multicast(this, replicaIds, payload);
108+
}
109+
110+
/**
111+
* Handles a reply received by the client.
112+
*
113+
* @param senderId The ID of the sender of the reply.
114+
* @param payload The payload received by the client.
115+
*/
116+
public void handleMessage(String senderId, MessagePayload payload) {
117+
if (!(payload instanceof ClientReplyMessage clientReplyMessage)) {
118+
return;
119+
}
120+
ReplyMessage reply = clientReplyMessage.getReply();
121+
ClientReplyKey key = new ClientReplyKey(reply.getResult().toString(), reply.getSequenceNumber());
122+
// Default is for testing only
123+
String currRequest = this.sentRequestsByTimestamp.getOrDefault(reply.getTimestamp(), "C/0");
124+
this.hbftreplies.putIfAbsent(currRequest, new TreeMap<>());
125+
this.hbftreplies.get(currRequest).putIfAbsent(key, new ArrayList<>());
126+
this.hbftreplies.get(currRequest).get(key).add(reply);
127+
128+
/**
129+
* If the client received 2f + 1 correct replies,
130+
* and the request has not been completed yet.
131+
*/
132+
if (this.completedReplies(clientReplyMessage.getTolerance())
133+
&& !this.completedRequests.contains(key)
134+
&& super.requestSequenceNumber.get() <= this.maxRequests) {
135+
this.completedRequests.add(key);
136+
this.clearTimeout(this.timeouts.get(super.requestSequenceNumber.get()));
137+
this.sendRequest();
138+
}
139+
}
140+
141+
/**
142+
* Set a timeout for this replica.
143+
*
144+
* @param name a name for the timeout
145+
* @param r the runnable to execute when the timeout occurs
146+
* @param timeout the timeout duration
147+
* @return the timer object
148+
*/
149+
public long setTimeout(String name, Runnable r, long timeout) {
150+
Duration duration = Duration.ofSeconds(timeout);
151+
return super.scenario.getTransport().setTimeout(this, r, duration, name);
152+
}
153+
154+
/**
155+
* Checks whether client should retransmit the request
156+
* if #replies < f + 1
157+
*/
158+
public boolean shouldRetransmit(long tolerance) {
159+
String currRequest = String.format("%s/%d", super.id, super.requestSequenceNumber.get());
160+
if (!hbftreplies.containsKey(currRequest)) {
161+
return true;
162+
}
163+
for (ClientReplyKey key : hbftreplies.get(currRequest).keySet()) {
164+
return !(this.hbftreplies.get(currRequest).get(key).size() >= tolerance + 1);
165+
}
166+
return true;
167+
}
168+
169+
/**
170+
* Checks whether client should send PANIC
171+
* if f + 1 <= #replies < 2f + 1
172+
*/
173+
public boolean shouldPanic(long tolerance) {
174+
String currRequest = String.format("%s/%d", super.id, super.requestSequenceNumber.get());
175+
for (ClientReplyKey key : hbftreplies.get(currRequest).keySet()) {
176+
return this.hbftreplies.get(currRequest).get(key).size() >= tolerance + 1
177+
&& this.hbftreplies.get(currRequest).get(key).size() < tolerance * 2 + 1;
178+
}
179+
return false;
180+
}
181+
182+
/**
183+
* Checks whether it has received 2f + 1 replies
184+
*/
185+
public boolean completedReplies(long tolerance) {
186+
String currRequest = String.format("%s/%d", super.id, super.requestSequenceNumber.get());
187+
if (!hbftreplies.containsKey(currRequest)) {
188+
return false;
189+
}
190+
for (ClientReplyKey key : hbftreplies.get(currRequest).keySet()) {
191+
if (this.hbftreplies.get(currRequest).get(key).size() >= 2 * tolerance + 1) {
192+
return true;
193+
}
194+
}
195+
return false;
196+
}
197+
198+
/**
199+
* Clear all timeouts for this client.
200+
*/
201+
// public void clearAllTimeouts() {
202+
// super.scenario.getTransport().clearClientTimeouts(super.id);
203+
// }
204+
205+
/**
206+
* Create a digest of a message.
207+
*
208+
* @param message the message to digest
209+
* @return the digest of the message
210+
*/
211+
public byte[] digest(Serializable message) {
212+
return md.digest(message.toString().getBytes());
213+
}
214+
215+
}

simulator/src/main/java/byzzbench/simulator/Replica.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,12 @@ public void initialize() {
164164
/**
165165
* Handle a request received from a client.
166166
*
167-
* @param clientId the ID of the client
168-
* @param request the request payload
167+
* @param clientId the ID of the client
168+
* @param request the request payload
169+
* @param timestamp the time the request was created/sent
169170
* @throws Exception if an error occurs while handling the request
170171
*/
171-
public abstract void handleClientRequest(String clientId, Serializable request) throws Exception;
172+
public abstract void handleClientRequest(String clientId, long timestamp, Serializable request) throws Exception;
172173

173174
/**
174175
* Send a reply to a client.
@@ -186,8 +187,10 @@ public void sendReplyToClient(String clientId, Serializable reply) {
186187
* @param operation the operation to commit
187188
*/
188189
public void commitOperation(long sequenceNumber, LogEntry operation) {
189-
this.commitLog.add(sequenceNumber, operation);
190-
this.notifyObserversLocalCommit(operation);
190+
if (this.commitLog.get(sequenceNumber) == null) {
191+
this.commitLog.add(sequenceNumber, operation);
192+
this.notifyObserversLocalCommit(operation);
193+
}
191194
}
192195

193196
/**
@@ -214,7 +217,7 @@ public long setTimeout(String name, Runnable r, Duration timeout) {
214217
this.notifyObserversTimeout();
215218
r.run();
216219
};
217-
return this.transport.setTimeout(this, wrapper, timeout);
220+
return this.transport.setTimeout(this, wrapper, timeout, name);
218221
}
219222

220223
/**
@@ -226,6 +229,13 @@ public void clearTimeout(long eventId) {
226229
this.transport.clearTimeout(this, eventId);
227230
}
228231

232+
/**
233+
* Clear timeout based on description.
234+
*/
235+
public void clearTimeout(String description) {
236+
this.scenario.getTransport().clearTimeout(this, description);
237+
}
238+
229239
/**
230240
* Clear all timeouts for this replica.
231241
*/

simulator/src/main/java/byzzbench/simulator/config/ByzzBenchConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public final class BehaviorConfig {
171171
@Data
172172
public final class ScenarioConfig {
173173
private TerminationConfig termination = new TerminationConfig();
174-
private String id = "pbft-java";
174+
private String id = "hbft";
175175
private Map<String, String> params = new HashMap<>();
176176
}
177177
}

0 commit comments

Comments
 (0)