2
2
package org .hiero .block .simulator .generator ;
3
3
4
4
import static java .lang .System .Logger .Level .DEBUG ;
5
+ import static java .lang .System .Logger .Level .ERROR ;
5
6
import static java .lang .System .Logger .Level .INFO ;
7
+ import static java .lang .System .Logger .Level .WARNING ;
8
+ import static java .util .Objects .requireNonNull ;
6
9
7
10
import com .hedera .hapi .block .stream .BlockProof ;
8
11
import com .hedera .hapi .block .stream .protoc .Block ;
12
15
import java .io .IOException ;
13
16
import java .lang .System .Logger ;
14
17
import java .util .ArrayList ;
18
+ import java .util .Collections ;
19
+ import java .util .HashMap ;
20
+ import java .util .Iterator ;
21
+ import java .util .LinkedHashSet ;
15
22
import java .util .List ;
23
+ import java .util .Map ;
16
24
import java .util .Random ;
25
+ import java .util .Set ;
26
+ import java .util .regex .Matcher ;
27
+ import java .util .regex .Pattern ;
17
28
import org .hiero .block .common .hasher .Hashes ;
18
29
import org .hiero .block .common .hasher .HashingUtilities ;
19
30
import org .hiero .block .common .hasher .NaiveStreamingTreeHasher ;
20
31
import org .hiero .block .common .hasher .StreamingTreeHasher ;
21
32
import org .hiero .block .internal .BlockItemUnparsed ;
22
33
import org .hiero .block .simulator .config .data .BlockGeneratorConfig ;
34
+ import org .hiero .block .simulator .config .data .UnorderedStreamConfig ;
23
35
import org .hiero .block .simulator .config .types .GenerationMode ;
24
36
import org .hiero .block .simulator .exception .BlockSimulatorParsingException ;
25
37
import org .hiero .block .simulator .generator .itemhandler .BlockHeaderHandler ;
@@ -56,17 +68,22 @@ public class CraftBlockStreamManager implements BlockStreamManager {
56
68
private StreamingTreeHasher inputTreeHasher ;
57
69
private StreamingTreeHasher outputTreeHasher ;
58
70
71
+ // Unordered streaming
72
+ private final boolean unorderedStreamingEnabled ;
73
+ private Iterator <Block > unorderedStreamIterator ;
74
+
59
75
/**
60
76
* Constructs a new CraftBlockStreamManager with the specified configuration.
61
77
*
62
78
* @param blockGeneratorConfig Configuration parameters for block generation
63
- * including event and transaction counts
79
+ * including event and transaction counts
64
80
* @param simulatorStartupData simulator startup data used for initialization
65
81
* @throws NullPointerException if blockGeneratorConfig is null
66
82
*/
67
83
public CraftBlockStreamManager (
68
84
@ NonNull final BlockGeneratorConfig blockGeneratorConfig ,
69
- @ NonNull final SimulatorStartupData simulatorStartupData ) {
85
+ @ NonNull final SimulatorStartupData simulatorStartupData ,
86
+ @ NonNull final UnorderedStreamConfig unorderedStreamConfig ) {
70
87
this .generationMode = blockGeneratorConfig .generationMode ();
71
88
this .minEventsPerBlock = blockGeneratorConfig .minEventsPerBlock ();
72
89
this .maxEventsPerBlock = blockGeneratorConfig .maxEventsPerBlock ();
@@ -81,6 +98,39 @@ public CraftBlockStreamManager(
81
98
this .currentBlockNumber = simulatorStartupData .getLatestAckBlockNumber () + 1L ;
82
99
this .previousBlockHash = simulatorStartupData .getLatestAckBlockHash ();
83
100
LOGGER .log (INFO , "Block Stream Simulator will use Craft mode for block management" );
101
+
102
+ // Unordered streaming
103
+ unorderedStreamingEnabled = unorderedStreamConfig .enabled ();
104
+ if (unorderedStreamingEnabled ) {
105
+ initUnorderedStreaming (simulatorStartupData , unorderedStreamConfig );
106
+ }
107
+ }
108
+
109
+ private void initUnorderedStreaming (
110
+ SimulatorStartupData simulatorStartupData , UnorderedStreamConfig unorderedStreamConfig ) {
111
+
112
+ if (simulatorStartupData .isEnabled ()) {
113
+ throw new IllegalStateException ("Unordered streaming does not support start-up data enabled" );
114
+ }
115
+ this .currentBlockNumber = 0 ;
116
+ final List <Block > blockStreamList ;
117
+ final int scrambleLevel = unorderedStreamConfig .sequenceScrambleLevel ();
118
+ if (scrambleLevel == 0 ) {
119
+ // use a predefined order of streaming from the properties file
120
+ final LinkedHashSet <Long > fixedStreamingSequence =
121
+ parseRangeSet (unorderedStreamConfig .fixedStreamingSequence ());
122
+ blockStreamList = getBlockStreamList (fixedStreamingSequence );
123
+ } else {
124
+ // put the predefined available blocks in a list and scramble by the provided coefficient
125
+ final Set <Long > availableBlocks = parseRangeSet (unorderedStreamConfig .availableBlocks ());
126
+ blockStreamList = getBlockStreamList (availableBlocks );
127
+ scrambleBlocks (blockStreamList , scrambleLevel );
128
+ }
129
+ if (blockStreamList .isEmpty ()) {
130
+ throw new IllegalStateException ("No blocks are available for streaming with the current configuration" );
131
+ }
132
+ unorderedStreamIterator = blockStreamList .iterator ();
133
+ LOGGER .log (INFO , "Unordered streaming is enabled" );
84
134
}
85
135
86
136
/**
@@ -109,11 +159,22 @@ public BlockItem getNextBlockItem() {
109
159
* Each block includes a header, events with their transactions and results, and a proof.
110
160
*
111
161
* @return A newly generated Block
112
- * @throws IOException if there is an error processing block items
162
+ * @throws IOException if there is an error processing block items
113
163
* @throws BlockSimulatorParsingException if there is an error parsing block components
114
164
*/
115
165
@ Override
116
166
public Block getNextBlock () throws IOException , BlockSimulatorParsingException {
167
+ if (unorderedStreamingEnabled ) {
168
+ if (unorderedStreamIterator .hasNext ()) {
169
+ return unorderedStreamIterator .next ();
170
+ }
171
+ return null ;
172
+ } else {
173
+ return createNextBlock ();
174
+ }
175
+ }
176
+
177
+ private Block createNextBlock () throws BlockSimulatorParsingException {
117
178
LOGGER .log (DEBUG , "Started creation of block number %s." .formatted (currentBlockNumber ));
118
179
// todo(683) Refactor common hasher to accept protoc types, in order to avoid the additional overhead of keeping
119
180
// and unparsing.
@@ -188,4 +249,104 @@ private void resetState() {
188
249
currentBlockNumber ++;
189
250
previousBlockHash = currentBlockHash ;
190
251
}
252
+
253
+ private List <Block > getBlockStreamList (Set <Long > blockSequence ) {
254
+ requireNonNull (blockSequence );
255
+ final Map <Long , Block > craftedBlocksMap = new HashMap <>();
256
+ final List <Block > blockStreamList = new ArrayList <>();
257
+ if (!blockSequence .isEmpty ()) {
258
+ try {
259
+ for (int i = 0 ; i <= Collections .max (blockSequence ); i ++) {
260
+ final Block block = createNextBlock ();
261
+ final long blockNbr = block .getItems (block .getItemsCount () - 1 )
262
+ .getBlockProof ()
263
+ .getBlock ();
264
+ craftedBlocksMap .put (blockNbr , block );
265
+ }
266
+ } catch (Exception e ) {
267
+ LOGGER .log (ERROR , e .getMessage (), e );
268
+ throw new RuntimeException (e );
269
+ }
270
+ for (Long blockNbr : blockSequence ) {
271
+ if (craftedBlocksMap .containsKey (blockNbr )) {
272
+ blockStreamList .add (craftedBlocksMap .get (blockNbr ));
273
+ }
274
+ }
275
+ }
276
+ return blockStreamList ;
277
+ }
278
+
279
+ private void scrambleBlocks (List <Block > list , int coefficient ) {
280
+ requireNonNull (list );
281
+ final int size = list .size ();
282
+ if (size > 1 ) {
283
+ final List <Block > originalList = new ArrayList <>(list );
284
+ final Random random = new Random ();
285
+ final int maxAttempts = 10 ;
286
+ int attempt = 0 ;
287
+
288
+ // The number of swaps is proportional to the coefficient
289
+ final int swapCount = (int ) ((coefficient / 10.0 ) * (size * 2 ));
290
+
291
+ while (list .equals (originalList ) && attempt ++ < maxAttempts ) {
292
+ for (int i = 0 ; i < swapCount ; i ++) {
293
+ final int index1 = random .nextInt (size - 1 );
294
+ final int index2 ;
295
+ if (coefficient <= 5 ) {
296
+ // When coefficient is low, swap with a neighbor
297
+ index2 = index1 + 1 ;
298
+ } else {
299
+ // Higher coefficient allows more distant swaps
300
+ index2 = random .nextInt (size );
301
+ }
302
+ Collections .swap (list , index1 , index2 );
303
+ }
304
+ }
305
+
306
+ // this condition is not supposed to be met when valid configurations are provided
307
+ // maxAttempts variable serves for endless loop prevention in case of inaccurate config inputs
308
+ if (list .equals (originalList )) {
309
+ LOGGER .log (WARNING , "Scramble unsuccessful after " + maxAttempts + " attempts" );
310
+ }
311
+ }
312
+ }
313
+
314
+ private LinkedHashSet <Long > parseRangeSet (String input ) {
315
+ requireNonNull (input );
316
+ final LinkedHashSet <Long > result = new LinkedHashSet <>();
317
+ if (!input .isBlank ()) {
318
+ try {
319
+ final Matcher matcher =
320
+ Pattern .compile ("\\ [(\\ d+)-(\\ d+)\\ ]|(\\ d+)" ).matcher (input );
321
+ while (matcher .find ()) {
322
+ if (matcher .group (1 ) != null && matcher .group (2 ) != null ) {
323
+ final long start = Long .parseLong (matcher .group (1 ));
324
+ final long end = Long .parseLong (matcher .group (2 ));
325
+ if (start <= 0 || end <= 0 ) {
326
+ throw new IllegalArgumentException (input
327
+ + " does not match the expected format. Range values must be positive and non-zero: ["
328
+ + start + "-" + end + "]" );
329
+ }
330
+ if (start >= end ) {
331
+ throw new IllegalArgumentException (
332
+ input + " does not match the expected format. Invalid range: start >= end in ["
333
+ + start + "-" + end + "]" );
334
+ }
335
+ for (long i = start ; i <= end ; i ++) {
336
+ result .add (i );
337
+ }
338
+ } else if (matcher .group (3 ) != null ) {
339
+ final long value = Long .parseLong (matcher .group (3 ));
340
+ if (value <= 0 ) {
341
+ throw new IllegalArgumentException ("Values must be positive and non-zero: " + value );
342
+ }
343
+ result .add (value );
344
+ }
345
+ }
346
+ } catch (Exception e ) {
347
+ throw new IllegalArgumentException ("Exception in parsing input: " + input , e );
348
+ }
349
+ }
350
+ return result ;
351
+ }
191
352
}
0 commit comments