22package org .hiero .consensus .event .creator .impl .tipset ;
33
44import static com .swirlds .logging .legacy .LogMarker .EXCEPTION ;
5- import static org .hiero .consensus .event .creator .impl .tipset .TipsetAdvancementWeight .ZERO_ADVANCEMENT_WEIGHT ;
65
76import com .hedera .hapi .node .state .roster .Roster ;
87import com .swirlds .base .time .Time ;
8+ import com .swirlds .base .utility .Pair ;
99import com .swirlds .common .utility .throttle .RateLimitedLogger ;
1010import com .swirlds .config .api .Configuration ;
1111import com .swirlds .logging .legacy .LogMarker ;
1616import java .time .Duration ;
1717import java .time .Instant ;
1818import java .util .ArrayList ;
19+ import java .util .Arrays ;
1920import java .util .Collection ;
2021import java .util .Collections ;
22+ import java .util .Comparator ;
2123import java .util .List ;
2224import java .util .Objects ;
2325import java .util .function .Function ;
@@ -53,6 +55,7 @@ public class TipsetEventCreator implements EventCreator {
5355 private final TipsetWeightCalculator tipsetWeightCalculator ;
5456 private final ChildlessEventTracker childlessOtherEventTracker ;
5557 private final EventTransactionSupplier transactionSupplier ;
58+ private final int maxOtherParents ;
5659 private EventWindow eventWindow ;
5760 /** The wall-clock time when the event window was last updated */
5861 private Instant lastReceivedEventWindow ;
@@ -147,6 +150,7 @@ public TipsetEventCreator(
147150 eventWindow = EventWindow .getGenesisEventWindow ();
148151 lastReceivedEventWindow = time .now ();
149152 eventHasher = new PbjStreamHasher ();
153+ maxOtherParents = eventCreationConfig .maxOtherParents ();
150154 }
151155
152156 /**
@@ -229,12 +233,13 @@ public PlatformEvent maybeCreateEvent() {
229233 }
230234
231235 /**
232- * For simplicity, we will always create an event based only on single self parent; this is a special rare case
233- * and it will unblock the network
236+ * For simplicity, we will always create an event based only on single self parent; this is a special rare case and
237+ * it will unblock the network
238+ *
234239 * @return new event based only on self parent
235240 */
236241 private UnsignedEvent createQuiescenceBreakEvent () {
237- return buildAndProcessEvent (null );
242+ return buildAndProcessEvent ();
238243 }
239244
240245 @ Nullable
@@ -243,56 +248,52 @@ private UnsignedEvent maybeCreateUnsignedEvent() {
243248 // Special case: network of size 1.
244249 // We can always create a new event, no need to run the tipset algorithm.
245250 // In a network of size one, we create events without other parents.
246- return buildAndProcessEvent (null );
251+ return buildAndProcessEvent ();
247252 }
248253
249- final long selfishness = tipsetWeightCalculator .getMaxSelfishnessScore ();
250- tipsetMetrics .getSelfishnessMetric ().update (selfishness );
251-
252- // Never bother with anti-selfishness techniques if we have a selfishness score of 1.
253- // We are pretty much guaranteed to be selfish to ~1/3 of other nodes by a score of 1.
254- final double beNiceChance = (selfishness - 1 ) / antiSelfishnessFactor ;
255-
256- if (beNiceChance > 0 && random .nextDouble () < beNiceChance ) {
257- return createEventToReduceSelfishness ();
258- } else {
259- return createEventByOptimizingAdvancementWeight ();
260- }
254+ return createEventCombinedAlgorithm ();
261255 }
262256
263257 private PlatformEvent signEvent (final UnsignedEvent event ) {
264258 return new PlatformEvent (event , signer .sign (event .getHash ().getBytes ()));
265259 }
266260
267261 /**
268- * Create an event using the other parent with the best tipset advancement weight.
262+ * Create an event using the other parent with the best tipset advancement weight, possibly mixing a single other
263+ * parent to reduce selfishness.
269264 *
270265 * @return the new event, or null if it is not legal to create a new event
271266 */
272267 @ Nullable
273- private UnsignedEvent createEventByOptimizingAdvancementWeight () {
268+ private UnsignedEvent createEventCombinedAlgorithm () {
274269 final List <PlatformEvent > possibleOtherParents =
275270 new ArrayList <>(childlessOtherEventTracker .getChildlessEvents ());
276271 Collections .shuffle (possibleOtherParents , random );
277272
278- PlatformEvent bestOtherParent = null ;
279- TipsetAdvancementWeight bestAdvancementWeight = ZERO_ADVANCEMENT_WEIGHT ;
280- for (final PlatformEvent otherParent : possibleOtherParents ) {
281- final List <EventDescriptorWrapper > parents = new ArrayList <>(2 );
282- parents .add (otherParent .getDescriptor ());
283- if (lastSelfEvent != null ) {
284- parents .add (lastSelfEvent .getDescriptor ());
285- }
273+ final List <PlatformEvent > bestParents = possibleOtherParents .stream ()
274+ .map (op -> {
275+ final List <EventDescriptorWrapper > parents = new ArrayList <>(2 );
276+ parents .add (op .getDescriptor ());
277+ if (lastSelfEvent != null ) {
278+ parents .add (lastSelfEvent .getDescriptor ());
279+ }
280+ return new Pair <>(op , tipsetWeightCalculator .getTheoreticalAdvancementWeight (parents ));
281+ })
282+ .filter (p -> p .right ().isNonZero ())
283+ .sorted (Comparator .comparing (Pair ::right ))
284+ .map (Pair ::left )
285+ .toList ();
286286
287- final TipsetAdvancementWeight advancementWeight =
288- tipsetWeightCalculator .getTheoreticalAdvancementWeight (parents );
289- if (advancementWeight .isGreaterThan (bestAdvancementWeight )) {
290- bestOtherParent = otherParent ;
291- bestAdvancementWeight = advancementWeight ;
292- }
287+ final PlatformEvent [] chosenBestParents ;
288+ if (bestParents .size () > maxOtherParents ) {
289+ chosenBestParents = bestParents
290+ .subList (bestParents .size () - maxOtherParents , bestParents .size ())
291+ .toArray (new PlatformEvent [0 ]);
292+ } else {
293+ chosenBestParents = bestParents .toArray (new PlatformEvent [0 ]);
293294 }
294295
295- if (bestOtherParent == null ) {
296+ if (chosenBestParents . length == 0 ) {
296297 // If there are no available other parents, it is only legal to create a new event if we are
297298 // creating a genesis event. In order to create a genesis event, we must have never created
298299 // an event before and the current event window must have never been advanced.
@@ -302,20 +303,66 @@ private UnsignedEvent createEventByOptimizingAdvancementWeight() {
302303 }
303304
304305 // we are creating a genesis event, so we can use a null other parent
305- return buildAndProcessEvent (null );
306+ return buildAndProcessEvent ();
307+ }
308+
309+ final long selfishness = tipsetWeightCalculator .getMaxSelfishnessScore ();
310+ tipsetMetrics .getSelfishnessMetric ().update (selfishness );
311+
312+ // Never bother with anti-selfishness techniques if we have a selfishness score of 1.
313+ // We are pretty much guaranteed to be selfish to ~1/3 of other nodes by a score of 1.
314+ final double beNiceChance = (selfishness - 1 ) / antiSelfishnessFactor ;
315+
316+ boolean replacedBestParentForSelfishness = false ;
317+
318+ if (beNiceChance > 0 && random .nextDouble () < beNiceChance ) {
319+ // replace one of the best parents with the one chosen to reduce selfishness
320+ final PlatformEvent selflessParent = selectParentToReduceSelfishness ();
321+ // if we already contain that event, everything is good
322+ if (!contains (chosenBestParents , selflessParent )) {
323+ // otherwise, replace the least important parent with one we have chosen to reduce selfishness
324+ // please note in case of single-parent events, this will replace the only parent
325+ chosenBestParents [chosenBestParents .length - 1 ] = selflessParent ;
326+ replacedBestParentForSelfishness = true ;
327+ }
306328 }
307329
308- tipsetMetrics .getTipsetParentMetric (bestOtherParent .getCreatorId ()).cycle ();
309- return buildAndProcessEvent (bestOtherParent );
330+ for (int i = 0 ; i < chosenBestParents .length ; i ++) {
331+ if (replacedBestParentForSelfishness && i == chosenBestParents .length - 1 ) {
332+ tipsetMetrics
333+ .getPityParentMetric (chosenBestParents [i ].getCreatorId ())
334+ .cycle ();
335+ } else {
336+ tipsetMetrics
337+ .getTipsetParentMetric (chosenBestParents [i ].getCreatorId ())
338+ .cycle ();
339+ }
340+ }
341+
342+ return buildAndProcessEvent (chosenBestParents );
343+ }
344+
345+ /**
346+ * Check if given element is present inside the array
347+ * @param array collection to check
348+ * @param element element to look for
349+ * @return true if element is contained in array, false otherwise
350+ */
351+ private static <T > boolean contains (@ NonNull final T [] array , @ NonNull final T element ) {
352+ for (final T entry : array ) {
353+ if (element .equals (entry )) {
354+ return true ;
355+ }
356+ }
357+ return false ;
310358 }
311359
312360 /**
313- * Create an event that reduces the selfishness score.
361+ * Select a parent event that reduces the selfishness score.
314362 *
315- * @return the new event, or null if it is not legal to create a new event
363+ * @return parent to reduce selfishness
316364 */
317- @ Nullable
318- private UnsignedEvent createEventToReduceSelfishness () {
365+ private @ NonNull PlatformEvent selectParentToReduceSelfishness () {
319366 final Collection <PlatformEvent > possibleOtherParents = childlessOtherEventTracker .getChildlessEvents ();
320367 final List <PlatformEvent > ignoredNodes = new ArrayList <>(possibleOtherParents .size ());
321368
@@ -374,8 +421,7 @@ private UnsignedEvent createEventToReduceSelfishness() {
374421 runningSum += selfishnessScores .get (i );
375422 if (choice < runningSum ) {
376423 final PlatformEvent ignoredNode = ignoredNodes .get (i );
377- tipsetMetrics .getPityParentMetric (ignoredNode .getCreatorId ()).cycle ();
378- return buildAndProcessEvent (ignoredNode );
424+ return ignoredNode ;
379425 }
380426 }
381427
@@ -384,14 +430,22 @@ private UnsignedEvent createEventToReduceSelfishness() {
384430 }
385431
386432 /**
387- * Given an other parent , build the next self event and process it.
433+ * Given the list of other parents , build the next self event and process it.
388434 *
389- * @param otherParent the other parent , or null if there is no other parent
435+ * @param otherParents the other parents , or zero length arglist if there is no other parents
390436 * @return the new event
391437 */
392- private UnsignedEvent buildAndProcessEvent (@ Nullable final PlatformEvent otherParent ) {
393- final EventDescriptorWrapper otherParentDescriptor = otherParent == null ? null : otherParent .getDescriptor ();
394- final UnsignedEvent event = assembleEventObject (otherParent );
438+ private UnsignedEvent buildAndProcessEvent (@ NonNull final PlatformEvent ... otherParents ) {
439+ final List <EventDescriptorWrapper > otherParentDescriptors ;
440+
441+ if (otherParents .length == 0 ) {
442+ otherParentDescriptors = List .of ();
443+ } else {
444+ otherParentDescriptors = Arrays .stream (otherParents )
445+ .map (PlatformEvent ::getDescriptor )
446+ .toList ();
447+ }
448+ final UnsignedEvent event = assembleEventObject (otherParents );
395449
396450 tipsetTracker .addSelfEvent (event .getDescriptor (), event .getMetadata ().getAllParents ());
397451 final TipsetAdvancementWeight advancementWeight =
@@ -400,31 +454,30 @@ private UnsignedEvent buildAndProcessEvent(@Nullable final PlatformEvent otherPa
400454 / (double ) tipsetWeightCalculator .getMaximumPossibleAdvancementWeight ();
401455 tipsetMetrics .getTipsetAdvancementMetric ().update (weightRatio );
402456
403- if (otherParent != null ) {
404- childlessOtherEventTracker .registerSelfEventParents (List .of (otherParentDescriptor ));
405- }
457+ childlessOtherEventTracker .registerSelfEventParents (otherParentDescriptors );
406458
407459 return event ;
408460 }
409461
410462 /**
411463 * Given the parents, assemble the event object.
412464 *
413- * @param otherParent the other parent
465+ * @param otherParents list of the other parents
414466 * @return the event
415467 */
416468 @ NonNull
417- private UnsignedEvent assembleEventObject (@ Nullable final PlatformEvent otherParent ) {
469+ private UnsignedEvent assembleEventObject (final PlatformEvent ... otherParents ) {
418470 final List <TimestampedTransaction > transactions = transactionSupplier .getTransactionsForEvent ();
419- final List <EventDescriptorWrapper > allParents = Stream .of (lastSelfEvent , otherParent )
471+ final List <PlatformEvent > allParents = Stream .concat ( Stream . of (lastSelfEvent ), Stream . of ( otherParents ) )
420472 .filter (Objects ::nonNull )
421- .map (PlatformEvent ::getDescriptor )
422473 .toList ();
474+ final List <EventDescriptorWrapper > allParentDescriptors =
475+ allParents .stream ().map (PlatformEvent ::getDescriptor ).toList ();
423476 final UnsignedEvent event = new UnsignedEvent (
424477 selfId ,
425- allParents ,
478+ allParentDescriptors ,
426479 eventWindow .newEventBirthRound (),
427- calculateNewEventCreationTime (lastSelfEvent , otherParent , transactions ),
480+ calculateNewEventCreationTime (lastSelfEvent , allParents , transactions ),
428481 transactions .stream ().map (TimestampedTransaction ::transaction ).toList (),
429482 random .nextLong (0 , roster .rosterEntries ().size () + 1 ));
430483 eventHasher .hashUnsignedEvent (event );
@@ -482,19 +535,17 @@ public String toString() {
482535 * parent to child.
483536 *
484537 * @param selfParent the self parent
485- * @param otherParent the other parent
538+ * @param allParents list of all the parents, including self parent in the first position
486539 * @param transactions the transactions to be included in the new event
487540 * @return the creation time for the new event
488541 */
489542 @ NonNull
490543 private Instant calculateNewEventCreationTime (
491544 @ Nullable final PlatformEvent selfParent ,
492- @ Nullable final PlatformEvent otherParent ,
545+ @ NonNull final List < PlatformEvent > allParents ,
493546 @ NonNull final List <TimestampedTransaction > transactions ) {
494547 final Instant maxReceivedTime = Stream .of (
495- Stream .of (selfParent , otherParent )
496- .filter (Objects ::nonNull )
497- .map (PlatformEvent ::getTimeReceived ),
548+ allParents .stream ().map (PlatformEvent ::getTimeReceived ),
498549 transactions .stream ().map (TimestampedTransaction ::receivedTime ),
499550 Stream .of (lastReceivedEventWindow ))
500551 // flatten the stream of streams
0 commit comments