Skip to content

Commit 374dfa1

Browse files
committed
Put real block state proofs behind feature flag
Signed-off-by: Matt Hess <[email protected]>
1 parent 2b03740 commit 374dfa1

File tree

6 files changed

+145
-76
lines changed

6 files changed

+145
-76
lines changed

hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImpl.java

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.hedera.hapi.block.stream.BlockItem;
3030
import com.hedera.hapi.block.stream.BlockProof;
3131
import com.hedera.hapi.block.stream.ChainOfTrustProof;
32+
import com.hedera.hapi.block.stream.MerklePath;
3233
import com.hedera.hapi.block.stream.MerkleSiblingHash;
3334
import com.hedera.hapi.block.stream.StateProof;
3435
import com.hedera.hapi.block.stream.SubMerkleTree;
@@ -784,17 +785,31 @@ private synchronized void finishProofWithSignature(
784785
} else {
785786
// This is a pending block whose block number precedes the signed block number, so we construct an
786787
// indirect state proof
787-
final var stateProof = BlockStateProofGenerator.generateStateProof(
788-
currentPendingBlock,
789-
blockNumber,
790-
blockSignature,
791-
signedBlock.blockTimestamp(),
792-
// Pass the remaining pending blocks, but don't remove them from the queue
793-
pendingBlocks.stream());
794-
proof = currentPendingBlock.proofBuilder().blockStateProof(stateProof);
795-
796-
if (log.isDebugEnabled()) {
797-
logStateProof(blockSignature, currentPendingBlock, blockNumber, stateProof);
788+
789+
if (configProvider
790+
.getConfiguration()
791+
.getConfigData(BlockStreamConfig.class)
792+
.enableStateProofs()) {
793+
final var stateProof = BlockStateProofGenerator.generateStateProof(
794+
currentPendingBlock,
795+
blockNumber,
796+
blockSignature,
797+
signedBlock.blockTimestamp(),
798+
// Pass the remaining pending blocks, but don't remove them from the queue
799+
pendingBlocks.stream());
800+
proof = currentPendingBlock.proofBuilder().blockStateProof(stateProof);
801+
802+
if (log.isDebugEnabled()) {
803+
logStateProof(blockSignature, currentPendingBlock, blockNumber, stateProof);
804+
}
805+
} else {
806+
// (FUTURE) Once state proofs are enabled, this placeholder proof can be removed
807+
proof = currentPendingBlock
808+
.proofBuilder()
809+
.blockStateProof(StateProof.newBuilder()
810+
.paths(MerklePath.newBuilder().build())
811+
.signedBlockProof(latestSignedBlockProof)
812+
.build());
798813
}
799814

800815
// Update the metrics

hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -687,10 +687,10 @@ void supportsMultiplePendingBlocksWithIndirectProofAsExpected() throws ParseExce
687687
assertEquals(
688688
FIRST_FAKE_SIGNATURE,
689689
aProof.blockStateProof().signedBlockProof().blockSignature());
690-
// An indirect state proof must have 3 merkle paths
691-
assertEquals(
692-
BlockStateProofGenerator.EXPECTED_MERKLE_PATH_COUNT,
693-
aProof.blockStateProof().paths().size());
690+
// (FUTURE) An indirect state proof must have 3 merkle paths. Enable when full state proofs are supported.
691+
// assertEquals(
692+
// BlockStateProofGenerator.EXPECTED_MERKLE_PATH_COUNT,
693+
// aProof.blockStateProof().paths().size());
694694
// And the proof for N+1 using a direct proof
695695
final var bProofItem = lastBItem.get();
696696
assertNotNull(bProofItem);

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/BlockStreamConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public record BlockStreamConfig(
3535
@ConfigProperty(defaultValue = "100") @Min(1) @NodeProperty int maxConsecutiveScheduleSecondsToProbe,
3636
@ConfigProperty(defaultValue = "1s") @Min(1) @NodeProperty Duration quiescedHeartbeatInterval,
3737
@ConfigProperty(defaultValue = "512") @NodeProperty int maxReadDepth,
38-
@ConfigProperty(defaultValue = "500000000") @NodeProperty int maxReadBytesSize) {
38+
@ConfigProperty(defaultValue = "500000000") @NodeProperty int maxReadBytesSize,
39+
@ConfigProperty(defaultValue = "false") @NetworkProperty boolean enableStateProofs) {
3940

4041
/**
4142
* Whether to stream to block nodes.

hedera-node/test-clients/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ tasks.register<Test>("testSubprocess") {
269269
val hintsThresholdDenominator =
270270
if (gradle.startParameter.taskNames.contains("hapiTestRestart")) "4" else "3"
271271
systemProperty("hapi.spec.hintsThresholdDenominator", hintsThresholdDenominator)
272+
systemProperty("hapi.spec.block.stateproof.verification", "false")
272273

273274
// Default quiet mode is "false" unless we are running in CI or set it explicitly to "true"
274275
systemProperty(

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/IndirectProofSequenceValidator.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ class IndirectProofSequenceValidator {
4949

5050
private final Map<Long, Bytes> expectedBlockRootHashes = new HashMap<>();
5151
private final Map<Long, Bytes> expectedPreviousBlockRootHashes = new HashMap<>();
52+
private final Map<Long, MerkleSiblingHash[]> expectedSiblingsByBlock = new HashMap<>();
5253

5354
/**
5455
* The signed block proof corresponding to the latest signed block number. This is needed to
5556
* verify the indirect proofs and must be the last proof in the sequence.
5657
*/
5758
private BlockProof signedProof;
59+
5860
/**
5961
* The consensus timestamp of the signed block corresponding to the latest signed block number.
6062
*/
@@ -93,9 +95,10 @@ boolean containsIndirectProofs() {
9395
void registerProof(
9496
final long blockNumber,
9597
final @NonNull BlockProof proof,
96-
final @NonNull Bytes blockRootHash,
98+
final @NonNull Bytes expectedBlockHash,
9799
final @NonNull Bytes previousBlockHash,
98-
final @NonNull Timestamp blockTimestamp) {
100+
final @NonNull Timestamp blockTimestamp,
101+
final @NonNull MerkleSiblingHash[] expectedSiblings) {
99102
if (endOfSequenceReached) {
100103
throw new IllegalStateException(
101104
"Cannot track indirect proof for block #%s: end of sequence previously reached"
@@ -135,10 +138,7 @@ void registerProof(
135138
// Block's Merkle Path 2: block contents path
136139
// Technically we could roll the timestamp into the sibling hashes here, but for clarity we keep them
137140
// separate
138-
final var siblingHashes = proof.blockStateProof().paths().get(BLOCK_CONTENTS_PATH_INDEX).siblings().stream()
139-
.map(s -> new MerkleSiblingHash(s.isLeft(), s.hash()))
140-
.toList();
141-
final var mp2 = new PartialMerklePath(null, previousBlockHash, siblingHashes);
141+
final var mp2 = new PartialMerklePath(null, previousBlockHash, Arrays.asList(expectedSiblings));
142142

143143
// Block's Merkle Path 3: parent (i.e. combined hash of left child and right child)
144144
// This is the combined result and should have no data
@@ -150,8 +150,9 @@ void registerProof(
150150
actualIndirectProofs.put(blockNumber, proof.blockStateProof());
151151
}
152152

153-
expectedBlockRootHashes.put(blockNumber, blockRootHash);
153+
expectedBlockRootHashes.put(blockNumber, expectedBlockHash);
154154
expectedPreviousBlockRootHashes.put(blockNumber, previousBlockHash);
155+
expectedSiblingsByBlock.put(blockNumber, expectedSiblings);
155156

156157
log.info("Registered proof for block {}", blockNumber);
157158
}
@@ -313,8 +314,8 @@ private Map<Long, MerklePath[]> constructExpectedMerklePaths() {
313314
// Merkle Path 2: enumerate all sibling hashes for remaining UNSIGNED blocks
314315
MerklePath.Builder earliestBlockMp2 = MerklePath.newBuilder();
315316

316-
// Create a set of siblings for all unsigned blocks remaining, plus another set for the signed block (excluding
317-
// its timestamp)
317+
// Create a set of siblings for all _unsigned_ blocks remaining, plus another set for the signed block
318+
// (excluding its timestamp)
318319
final var numBlocksRemaining = signedBlockNum - firstUnsignedBlockNum;
319320
final var totalExpectedSiblings = expectedSiblingsFrom(numBlocksRemaining);
320321
final SiblingNode[] allSiblingHashes = new SiblingNode[totalExpectedSiblings];
@@ -361,11 +362,12 @@ private Map<Long, MerklePath[]> constructExpectedMerklePaths() {
361362
final var currUnsignedBlockSiblings = new SiblingNode[currUnsignedBlockSiblingsSize];
362363
final var currUnsignedBlockStartingIndex =
363364
(int) ((currUnsignedBlock - firstUnsignedBlockNum) * UNSIGNED_BLOCK_SIBLING_COUNT);
365+
currentBlockNum = 0;
364366
for (int i = currUnsignedBlockStartingIndex; i < allSiblingHashes.length; i++) {
365367
// Copy the subset of sibling hashes from the full set of hashes (note: COPIES the current hash)
366-
currUnsignedBlockSiblings[i] = allSiblingHashes[currUnsignedBlockStartingIndex + i]
367-
.copyBuilder()
368-
.build();
368+
currUnsignedBlockSiblings[(int) currentBlockNum] =
369+
allSiblingHashes[i].copyBuilder().build();
370+
currentBlockNum++;
369371
}
370372

371373
newMp2.siblings(currUnsignedBlockSiblings).nextPathIndex(FINAL_MERKLE_PATH_INDEX);
@@ -415,8 +417,14 @@ private void verifyHashSequence() {
415417
// Verify the sequence of hashes by recomputing each block hash from the previous block
416418
// hash and the indirect proof
417419
final var finalExpectedHash = expectedBlockRootHashes.get(signedBlockNum);
420+
assertTrue(
421+
finalExpectedHash.length() > 0,
422+
"Final expected block hash is empty for signed block " + signedBlockNum);
418423
for (long blockNum = firstUnsignedBlockNum; blockNum < signedBlockNum; blockNum++) {
419424
final var expectedPreviousBlockHash = expectedPreviousBlockRootHashes.get(blockNum);
425+
assertTrue(
426+
expectedPreviousBlockHash.length() > 0,
427+
"Expected previous block hash is empty for block " + blockNum);
420428

421429
final var actualBlockStateProof = actualIndirectProofs.get(blockNum);
422430
assertNotNull(actualBlockStateProof, "Missing indirect state proof for block " + blockNum);
@@ -489,7 +497,7 @@ private static int expectedSiblingsFrom(final long numUnsignedBlocksRemaining) {
489497
// For the signed block we _do_ include its siblings in the state proof's block contents path, but we _don't_
490498
// include the timestamp as a sibling hash. The verification algorithm expects the signed block's timestamp to
491499
// be provided separately.
492-
return unsignedSiblingCount + +SIGNED_BLOCK_SIBLING_COUNT;
500+
return unsignedSiblingCount + SIGNED_BLOCK_SIBLING_COUNT;
493501
}
494502

495503
// A quick note: any field in this record can be null depending on which of the three types of merkle path it

0 commit comments

Comments
 (0)