Skip to content

Commit 536b453

Browse files
feat: add stake weight histogram to validation stats (#2508)
Signed-off-by: Rocky Thind <harpender.t@swirldslabs.com> Signed-off-by: Rocky Singh <rockysingh@users.noreply.github.com> Co-authored-by: Joseph S. <121976561+jsync-swirlds@users.noreply.github.com>
1 parent 2a3cc2a commit 536b453

12 files changed

Lines changed: 1622 additions & 555 deletions

File tree

tools-and-tests/tools/src/main/java/org/hiero/block/tools/blocks/ToWrappedBlocksCommand.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import org.hiero.block.tools.blocks.model.PreVerifiedBlock;
5151
import org.hiero.block.tools.blocks.model.hashing.BlockStreamBlockHashRegistry;
5252
import org.hiero.block.tools.blocks.model.hashing.StreamingHasher;
53+
import org.hiero.block.tools.blocks.validation.SignatureBlockStats;
54+
import org.hiero.block.tools.blocks.validation.SignatureStatsCollector;
5355
import org.hiero.block.tools.config.NetworkConfig;
5456
import org.hiero.block.tools.days.model.AddressBookRegistry;
5557
import org.hiero.block.tools.days.model.NodeStakeRegistry;
@@ -296,6 +298,10 @@ public Integer call() throws Exception {
296298
final AmendmentProvider amendmentProvider =
297299
createAmendmentProvider(NetworkConfig.current().networkName());
298300

301+
// Create signature stats collector for CSV output
302+
final SignatureStatsCollector statsCollector =
303+
new SignatureStatsCollector(outputBlocksDir.resolve("signature_statistics_wrap.csv"));
304+
299305
// ---- Pipeline executor services ----
300306
final int resolvedPrefetch = prefetchSize < 1 ? parseThreads : prefetchSize;
301307
final ExecutorService parseAndVerifyPool = Executors.newFixedThreadPool(parseThreads);
@@ -650,6 +656,26 @@ public Integer call() throws Exception {
650656
System.err.printf("Warning: node stake auto-update failed at block %d: %s%n", blockNum, e);
651657
}
652658

659+
// Collect signature stats from pre-verified data
660+
{
661+
final var blockTime = effectiveBlock.recordBlock().blockTime();
662+
final List<Long> verifiedNodeIds = effectiveBlock.verifiedSignatures().stream()
663+
.map(RecordFileSignature::nodeId)
664+
.toList();
665+
final List<Long> addressBookNodeIds = effectiveBlock.addressBook().nodeAddress().stream()
666+
.map(na -> na.nodeId())
667+
.toList();
668+
final Map<Long, Long> stakeMap = nodeStakeRegistry.getStakeMapForBlock(blockTime);
669+
final SignatureBlockStats blockStats = SignatureBlockStats.fromPreVerifiedData(
670+
blockNum,
671+
blockTime,
672+
verifiedNodeIds,
673+
effectiveBlock.verifiedSignatures().size(),
674+
addressBookNodeIds,
675+
stakeMap);
676+
statsCollector.accept(blockStats);
677+
}
678+
653679
// Monthly checkpoint: save state once per calendar month of blockchain data.
654680
// Worst-case on corruption: re-process at most ~1 month of blocks.
655681
// Check BEFORE updating chain state so the saved state is consistent with
@@ -834,6 +860,11 @@ public Integer call() throws Exception {
834860
addressBookRegistry.saveAddressBookRegistryToJsonFile(addressBookFile);
835861
nodeStakeRegistry.saveToJsonFile(nodeStakeFile);
836862

863+
// Finalize and close signature stats collector
864+
statsCollector.finalizeDayStats();
865+
statsCollector.printFinalSummary();
866+
statsCollector.close();
867+
837868
// If we stopped due to a parse failure, throw after saving state
838869
if (parseFailureMessage != null) {
839870
throw new RuntimeException(parseFailureMessage);

tools-and-tests/tools/src/main/java/org/hiero/block/tools/blocks/ValidateBlocksCommand.java

Lines changed: 213 additions & 189 deletions
Large diffs are not rendered by default.

tools-and-tests/tools/src/main/java/org/hiero/block/tools/blocks/validation/AddressBookUpdateValidation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class AddressBookUpdateValidation implements BlockValidation {
3333

3434
private static final String SAVE_FILE_NAME = "addressBookHistory.json";
3535
private static final int MAX_DEPTH = 512;
36-
private static final int MAX_RECORD_FILE_SIZE = 64 * 1024 * 1024;
36+
private static final int MAX_RECORD_FILE_SIZE = 128 * 1024 * 1024;
3737

3838
private final AddressBookRegistry addressBookRegistry;
3939

tools-and-tests/tools/src/main/java/org/hiero/block/tools/blocks/validation/NodeStakeUpdateValidation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public final class NodeStakeUpdateValidation implements BlockValidation {
3232

3333
private static final String SAVE_FILE_NAME = "nodeStakeHistory.json";
3434
private static final int MAX_DEPTH = 512;
35-
private static final int MAX_RECORD_FILE_SIZE = 64 * 1024 * 1024;
35+
private static final int MAX_RECORD_FILE_SIZE = 128 * 1024 * 1024;
3636

3737
private final NodeStakeRegistry nodeStakeRegistry;
3838
private boolean firstStakeUpdateSeen = false;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package org.hiero.block.tools.blocks.validation;
3+
4+
import java.time.Instant;
5+
import java.util.HashSet;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Set;
10+
11+
/**
12+
* Per-block signature validation statistics, providing richer data than the
13+
* simple pass/fail result of {@link SignatureValidation#validate}.
14+
*
15+
* <p>Collected by {@link SignatureValidation} when detailed stats are enabled,
16+
* and delivered to {@link SignatureStatsCollector} for per-day aggregation and CSV output.
17+
*
18+
* @param blockNumber the block number
19+
* @param blockTime the block's consensus timestamp
20+
* @param stakeWeighted true if stake-weighted consensus was used
21+
* @param totalNodes total nodes in the address book
22+
* @param totalStake total stake across all nodes (equal to totalNodes in equal-weight mode)
23+
* @param threshold minimum stake required for consensus
24+
* @param validatedNodes set of node IDs that produced valid signatures
25+
* @param validatedStake accumulated stake from validated nodes
26+
* @param totalSignatures total signature entries in the block proof
27+
* @param nodeStakes stake weights per node (nodeId to stake); empty in equal-weight mode
28+
* @param perNodeResults per-node verification outcome (keyed by nodeId)
29+
*/
30+
public record SignatureBlockStats(
31+
long blockNumber,
32+
Instant blockTime,
33+
boolean stakeWeighted,
34+
int totalNodes,
35+
long totalStake,
36+
long threshold,
37+
Set<Long> validatedNodes,
38+
long validatedStake,
39+
int totalSignatures,
40+
Map<Long, Long> nodeStakes,
41+
Map<Long, NodeResult> perNodeResults) {
42+
43+
/** Result of signature verification for a single node. */
44+
public enum NodeResult {
45+
/** Signature was verified successfully. */
46+
VERIFIED,
47+
/** Signature verification failed (bad signature). */
48+
FAILED,
49+
/** Error during verification (e.g. unknown node, key lookup failure). */
50+
ERROR,
51+
/** Node is in the address book but no signature was present. */
52+
NOT_PRESENT
53+
}
54+
55+
/**
56+
* Builds a {@link SignatureBlockStats} from pre-verified signature data. This is used by
57+
* commands that verify signatures outside of {@link SignatureValidation} (e.g. the wrap
58+
* command's Stage 1 pre-verification).
59+
*
60+
* @param blockNumber the block number
61+
* @param blockTime the block's consensus timestamp
62+
* @param verifiedNodeIds node IDs that produced valid signatures
63+
* @param totalSignatures total signature entries
64+
* @param addressBookNodeIds all node IDs from the address book
65+
* @param stakeMap node stake weights (nodeId to stake), or null for equal-weight
66+
* @return the constructed stats record
67+
*/
68+
public static SignatureBlockStats fromPreVerifiedData(
69+
long blockNumber,
70+
Instant blockTime,
71+
List<Long> verifiedNodeIds,
72+
int totalSignatures,
73+
List<Long> addressBookNodeIds,
74+
Map<Long, Long> stakeMap) {
75+
76+
int totalNodes = addressBookNodeIds.size();
77+
long rawTotalStake = stakeMap != null
78+
? stakeMap.values().stream().mapToLong(Long::longValue).sum()
79+
: 0;
80+
boolean stakeWeighted = stakeMap != null && rawTotalStake > 0;
81+
long totalStake = stakeWeighted ? rawTotalStake : totalNodes;
82+
long threshold = stakeWeighted ? (totalStake / 3) + ((totalStake % 3 == 0) ? 0 : 1) : (totalNodes / 3) + 1;
83+
84+
Set<Long> uniqueValidated = new HashSet<>();
85+
long validatedStake = 0;
86+
Map<Long, NodeResult> perNodeResults = new LinkedHashMap<>();
87+
for (long nodeId : verifiedNodeIds) {
88+
if (uniqueValidated.add(nodeId)) {
89+
long weight = stakeWeighted ? stakeMap.getOrDefault(nodeId, 0L) : 1;
90+
validatedStake += weight;
91+
}
92+
perNodeResults.put(nodeId, NodeResult.VERIFIED);
93+
}
94+
for (long nodeId : addressBookNodeIds) {
95+
perNodeResults.putIfAbsent(nodeId, NodeResult.NOT_PRESENT);
96+
}
97+
98+
return new SignatureBlockStats(
99+
blockNumber,
100+
blockTime,
101+
stakeWeighted,
102+
totalNodes,
103+
totalStake,
104+
threshold,
105+
Set.copyOf(uniqueValidated),
106+
validatedStake,
107+
totalSignatures,
108+
stakeMap != null ? Map.copyOf(stakeMap) : Map.of(),
109+
Map.copyOf(perNodeResults));
110+
}
111+
}

0 commit comments

Comments
 (0)