diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index bf706c17838..cead36ba8a4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -343,18 +343,28 @@ public boolean isExecutionEnabled(final BeaconState genericState, final BeaconBl return false; } - /** - * Performs data availability by validating the blobs against the kzg commitments from the block. - * It will also check the slot and blockRoot consistency. - */ - public boolean isDataAvailable( - final UInt64 slot, - final Bytes32 beaconBlockRoot, - final List kzgCommitments, - final List blobSidecars) { + public boolean isDataAvailable(final List blobSidecars, final BeaconBlock block) { return false; } + public boolean verifyBlobKzgProofBatch(final List blobSidecars) { + return false; + } + + public void validateBlobSidecarsBatchAgainstBlock( + final List blobSidecars, + final BeaconBlock block, + final List kzgCommitmentsFromBlock) { + throw new UnsupportedOperationException("Blob Sidecars before Deneb"); + } + + public void verifyBlobSidecarCompleteness( + final List verifiedBlobSidecars, + final List kzgCommitmentsFromBlock) + throws IllegalArgumentException { + throw new UnsupportedOperationException("No KZGCommitments before Deneb"); + } + public VersionedHash kzgCommitmentToVersionedHash(final KZGCommitment kzgCommitment) { throw new UnsupportedOperationException("No KZGCommitments before Deneb"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java index 716cd6cb872..5a486963ef9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java @@ -16,10 +16,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.teku.spec.config.SpecConfigDeneb.VERSIONED_HASH_VERSION_KZG; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.crypto.Hash; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -31,8 +32,10 @@ import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.logic.versions.capella.helpers.MiscHelpersCapella; import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; @@ -58,33 +61,142 @@ private static KZG initKZG(final SpecConfigDeneb config) { } /** - * is_data_available + * + *

In Deneb we don't need to retrieve data, everything is already available via blob sidecars. + */ + @Override + public boolean isDataAvailable(final List blobSidecars, final BeaconBlock block) { + + final List kzgCommitmentsFromBlock = + BeaconBlockBodyDeneb.required(block.getBody()).getBlobKzgCommitments().stream() + .map(SszKZGCommitment::getKZGCommitment) + .toList(); + + validateBlobSidecarsBatchAgainstBlock(blobSidecars, block, kzgCommitmentsFromBlock); + + if (!verifyBlobKzgProofBatch(blobSidecars)) { + return false; + } + + verifyBlobSidecarCompleteness(blobSidecars, kzgCommitmentsFromBlock); + + return true; + } + + /** + * Performs a verifyBlobKzgProofBatch on the given blob sidecars + * + * @param blobSidecars blob sidecars to verify, can be a partial set + * @return true if all blob sidecars are valid */ @Override - public boolean isDataAvailable( - final UInt64 slot, - final Bytes32 beaconBlockRoot, - final List kzgCommitments, - final List blobSidecars) { + public boolean verifyBlobKzgProofBatch(final List blobSidecars) { + final List blobs = new ArrayList<>(); + final List kzgProofs = new ArrayList<>(); + final List kzgCommitments = new ArrayList<>(); + blobSidecars.forEach( blobSidecar -> { + blobs.add(blobSidecar.getBlob().getBytes()); + kzgProofs.add(blobSidecar.getKZGProof()); + kzgCommitments.add(blobSidecar.getKZGCommitment()); + }); + + return kzg.verifyBlobKzgProofBatch(blobs, kzgCommitments, kzgProofs); + } + + /** + * Validates blob sidecars against block by matching all fields they have in common + * + * @param blobSidecars blob sidecars to validate + * @param block block to validate blob sidecar against + * @param kzgCommitmentsFromBlock kzg commitments from block. They could be extracted from block + * but since we already have them we can avoid extracting them again. + */ + @Override + public void validateBlobSidecarsBatchAgainstBlock( + final List blobSidecars, + final BeaconBlock block, + final List kzgCommitmentsFromBlock) { + + final String slotAndBlockRoot = block.getSlotAndBlockRoot().toLogString(); + + blobSidecars.forEach( + blobSidecar -> { + final UInt64 blobIndex = blobSidecar.getIndex(); + checkArgument( - slot.equals(blobSidecar.getSlot()), - "Blob sidecar slot %s does not match block slot %s", - blobSidecar.getSlot(), - slot); + blobSidecar.getBlockRoot().equals(block.getRoot()), + "Block and blob sidecar slot mismatch for %s, blob index %s", + slotAndBlockRoot, + blobIndex); + checkArgument( - beaconBlockRoot.equals(blobSidecar.getBlockRoot()), - "Blob sidecar block root %s does not match block root %s", - blobSidecar.getBlockRoot(), - beaconBlockRoot); + blobSidecar.getSlot().equals(block.getSlot()), + "Block and blob sidecar slot mismatch for %s, blob index %s", + slotAndBlockRoot, + blobIndex); + + checkArgument( + blobSidecar.getProposerIndex().equals(block.getProposerIndex()), + "Block and blob sidecar proposer index mismatch for %s, blob index %s", + slotAndBlockRoot, + blobIndex); + checkArgument( + blobSidecar.getBlockParentRoot().equals(block.getParentRoot()), + "Block and blob sidecar parent block mismatch for %s, blob index %s", + slotAndBlockRoot, + blobIndex); + + final KZGCommitment kzgCommitmentFromBlock; + + try { + kzgCommitmentFromBlock = kzgCommitmentsFromBlock.get(blobIndex.intValue()); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException( + String.format( + "Blob sidecar index out of bound with respect to block %s, blob index %s", + slotAndBlockRoot, blobIndex)); + } + + checkArgument( + blobSidecar.getKZGCommitment().equals(kzgCommitmentFromBlock), + "Block and blob sidecar kzg commitments mismatch for %s, blob index %s", + slotAndBlockRoot, + blobIndex); }); - final List blobs = - blobSidecars.stream().map(BlobSidecar::getBlob).map(Blob::getBytes).toList(); - final List proofs = blobSidecars.stream().map(BlobSidecar::getKZGProof).toList(); + } - return kzg.verifyBlobKzgProofBatch(blobs, kzgCommitments, proofs); + /** + * Verifies that blob sidecars are complete and with expected indexes + * + * @param completeVerifiedBlobSidecars blob sidecars to verify, It is assumed that it is an + * ordered list based on BlobSidecar index + * @param kzgCommitmentsFromBlock kzg commitments from block. + */ + @Override + public void verifyBlobSidecarCompleteness( + final List completeVerifiedBlobSidecars, + final List kzgCommitmentsFromBlock) + throws IllegalArgumentException { + checkArgument( + completeVerifiedBlobSidecars.size() == kzgCommitmentsFromBlock.size(), + "Blob sidecars are not complete"); + + IntStream.range(0, completeVerifiedBlobSidecars.size()) + .forEach( + index -> { + final BlobSidecar blobSidecar = completeVerifiedBlobSidecars.get(index); + final UInt64 blobIndex = blobSidecar.getIndex(); + + checkArgument( + blobIndex.longValue() == index, + "Blob sidecar index mismatch, expected %s, got %s", + index, + blobIndex); + }); } /** diff --git a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebPropertyTest.java b/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebPropertyTest.java index d68a8b1203d..1eb71b7c104 100644 --- a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebPropertyTest.java +++ b/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebPropertyTest.java @@ -20,16 +20,14 @@ import net.jqwik.api.ForAll; import net.jqwik.api.From; import net.jqwik.api.Property; -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZGCommitment; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.propertytest.suppliers.SpecSupplier; import tech.pegasys.teku.spec.propertytest.suppliers.blobs.versions.deneb.BlobSidecarSupplier; -import tech.pegasys.teku.spec.propertytest.suppliers.type.Bytes32Supplier; +import tech.pegasys.teku.spec.propertytest.suppliers.blocks.versions.deneb.BeaconBlockSupplier; import tech.pegasys.teku.spec.propertytest.suppliers.type.KZGCommitmentSupplier; -import tech.pegasys.teku.spec.propertytest.suppliers.type.UInt64Supplier; public class MiscHelpersDenebPropertyTest { private final SpecConfigDeneb specConfig = @@ -40,22 +38,40 @@ public class MiscHelpersDenebPropertyTest { .orElseThrow(); private final MiscHelpersDeneb miscHelpers = new MiscHelpersDeneb(specConfig); + @Property + void fuzzKzgCommitmentToVersionedHash( + @ForAll(supplier = KZGCommitmentSupplier.class) final KZGCommitment commitment) { + miscHelpers.kzgCommitmentToVersionedHash(commitment); + } + @Property(tries = 100) - void fuzzIsDataAvailable( - @ForAll(supplier = UInt64Supplier.class) final UInt64 slot, - @ForAll(supplier = Bytes32Supplier.class) final Bytes32 beaconBlockRoot, - @ForAll final List<@From(supplier = KZGCommitmentSupplier.class) KZGCommitment> commitments, + void fuzzVerifyBlobKzgProofBatch( @ForAll final List<@From(supplier = BlobSidecarSupplier.class) BlobSidecar> blobSidecars) { + miscHelpers.verifyBlobKzgProofBatch(blobSidecars); + } + + @Property(tries = 100) + void fuzzValidateBlobSidecarsBatchAgainstBlock( + @ForAll final List<@From(supplier = BlobSidecarSupplier.class) BlobSidecar> blobSidecars, + @ForAll(supplier = BeaconBlockSupplier.class) final BeaconBlock block, + @ForAll + final List<@From(supplier = KZGCommitmentSupplier.class) KZGCommitment> kzgCommitments) { try { - miscHelpers.isDataAvailable(slot, beaconBlockRoot, commitments, blobSidecars); + miscHelpers.validateBlobSidecarsBatchAgainstBlock(blobSidecars, block, kzgCommitments); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); } } - @Property - void fuzzKzgCommitmentToVersionedHash( - @ForAll(supplier = KZGCommitmentSupplier.class) final KZGCommitment commitment) { - miscHelpers.kzgCommitmentToVersionedHash(commitment); + @Property(tries = 100) + void fuzzVerifyBlobSidecarCompleteness( + @ForAll final List<@From(supplier = BlobSidecarSupplier.class) BlobSidecar> blobSidecars, + @ForAll + final List<@From(supplier = KZGCommitmentSupplier.class) KZGCommitment> kzgCommitments) { + try { + miscHelpers.verifyBlobSidecarCompleteness(blobSidecars, kzgCommitments); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class); + } } } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java index c00c2d8c44f..a580bfc1452 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java @@ -14,14 +14,24 @@ package tech.pegasys.teku.spec.logic.versions.deneb.helpers; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static tech.pegasys.teku.spec.config.SpecConfigDeneb.VERSIONED_HASH_VERSION_KZG; +import java.util.List; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZGCommitment; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; +import tech.pegasys.teku.spec.util.DataStructureUtil; class MiscHelpersDenebTest { @@ -34,6 +44,7 @@ class MiscHelpersDenebTest { private final Spec spec = TestSpecFactory.createMinimalDeneb(); private final MiscHelpersDeneb miscHelpersDeneb = new MiscHelpersDeneb(spec.getGenesisSpecConfig().toVersionDeneb().orElseThrow()); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); @Test public void versionedHash() { @@ -43,4 +54,120 @@ public void versionedHash() { "0x85d1edf1ee88f68260e750abb2c766398ad1125d4e94e1de04034075ccbd2bb79c5689b952ef15374fd03ca2b2475371")); assertThat(actual).isEqualTo(VERSIONED_HASH); } + + @Test + void validateBlobSidecarsAgainstBlock_shouldNotThrowOnValidBlobSidecar() { + final SignedBeaconBlock block = dataStructureUtil.randomSignedBeaconBlock(); + final List blobSidecars = dataStructureUtil.randomBlobSidecarsForBlock(block); + + // make sure we are testing something + assertThat(blobSidecars).isNotEmpty(); + + miscHelpersDeneb.validateBlobSidecarsBatchAgainstBlock( + blobSidecars, + block.getMessage(), + BeaconBlockBodyDeneb.required(block.getMessage().getBody()).getBlobKzgCommitments().stream() + .map(SszKZGCommitment::getKZGCommitment) + .toList()); + } + + @ParameterizedTest + @EnumSource(value = BlobSidecarsAlteration.class) + void validateBlobSidecarsAgainstBlock_shouldThrowOnBlobSidecarNotMatching( + final BlobSidecarsAlteration blobSidecarsAlteration) { + + final SignedBeaconBlock block = dataStructureUtil.randomSignedBeaconBlock(); + + final List kzgCommitments = + BeaconBlockBodyDeneb.required(block.getMessage().getBody()).getBlobKzgCommitments().stream() + .map(SszKZGCommitment::getKZGCommitment) + .toList(); + + // make sure we are testing something + assertThat(kzgCommitments).isNotEmpty(); + + final int indexToBeAltered = + Math.toIntExact(dataStructureUtil.randomPositiveLong(kzgCommitments.size())); + + // let's create blobs with only one altered with the given alteration + final List blobSidecars = + dataStructureUtil.randomBlobSidecarsForBlock( + block, + (index, randomBlobSidecarBuilder) -> { + if (!index.equals(indexToBeAltered)) { + return randomBlobSidecarBuilder; + } + + return switch (blobSidecarsAlteration) { + case BLOB_SIDECAR_PROPOSER_INDEX -> randomBlobSidecarBuilder.proposerIndex( + block.getProposerIndex().plus(1)); + case BLOB_SIDECAR_INDEX -> randomBlobSidecarBuilder.index( + UInt64.valueOf(kzgCommitments.size())); // out of bounds + case BLOB_SIDECAR_PARENT_ROOT -> randomBlobSidecarBuilder.blockParentRoot( + block.getParentRoot().not()); + case BLOB_SIDECAR_KZG_COMMITMENT -> randomBlobSidecarBuilder.kzgCommitment( + BeaconBlockBodyDeneb.required(block.getMessage().getBody()) + .getBlobKzgCommitments() + .get(index) + .getBytes() + .not()); + }; + }); + + assertThatThrownBy( + () -> + miscHelpersDeneb.validateBlobSidecarsBatchAgainstBlock( + blobSidecars, + block.getMessage(), + BeaconBlockBodyDeneb.required(block.getMessage().getBody()) + .getBlobKzgCommitments() + .stream() + .map(SszKZGCommitment::getKZGCommitment) + .toList())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith( + switch (blobSidecarsAlteration) { + case BLOB_SIDECAR_PROPOSER_INDEX -> "Block and blob sidecar proposer index mismatch"; + case BLOB_SIDECAR_INDEX -> "Blob sidecar index out of bound with respect to block"; + case BLOB_SIDECAR_PARENT_ROOT -> "Block and blob sidecar parent block mismatch"; + case BLOB_SIDECAR_KZG_COMMITMENT -> "Block and blob sidecar kzg commitments mismatch"; + }) + .hasMessageEndingWith( + "blob index %s", + blobSidecarsAlteration == BlobSidecarsAlteration.BLOB_SIDECAR_INDEX + ? kzgCommitments.size() // out of bounds altered index + : indexToBeAltered); + } + + @Test + void verifyBlobSidecarCompleteness_shouldThrowWhenSizesDoNotMatch() { + assertThatThrownBy( + () -> + miscHelpersDeneb.verifyBlobSidecarCompleteness( + dataStructureUtil.randomBlobSidecars(1), + List.of( + dataStructureUtil.randomKZGCommitment(), + dataStructureUtil.randomKZGCommitment()))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Blob sidecars are not complete"); + } + + @Test + void verifyBlobSidecarCompleteness_shouldThrowWhenBlobSidecarIndexIsWrong() { + final List blobSidecars = dataStructureUtil.randomBlobSidecars(1); + assertThatThrownBy( + () -> + miscHelpersDeneb.verifyBlobSidecarCompleteness( + blobSidecars, List.of(dataStructureUtil.randomKZGCommitment()))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Blob sidecar index mismatch, expected 0, got %s", blobSidecars.get(0).getIndex()); + } + + private enum BlobSidecarsAlteration { + BLOB_SIDECAR_PROPOSER_INDEX, + BLOB_SIDECAR_INDEX, + BLOB_SIDECAR_PARENT_ROOT, + BLOB_SIDECAR_KZG_COMMITMENT, + } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/blocks/versions/deneb/BeaconBlockSupplier.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/blocks/versions/deneb/BeaconBlockSupplier.java new file mode 100644 index 00000000000..cc2a448a38f --- /dev/null +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/blocks/versions/deneb/BeaconBlockSupplier.java @@ -0,0 +1,25 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.propertytest.suppliers.blocks.versions.deneb; + +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.propertytest.suppliers.DataStructureUtilSupplier; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class BeaconBlockSupplier extends DataStructureUtilSupplier { + public BeaconBlockSupplier() { + super(DataStructureUtil::randomBeaconBlock, SpecMilestone.DENEB); + } +} diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 4d9ceadf540..c2bb0672e03 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Optional; import java.util.Random; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -2123,24 +2124,38 @@ public Bytes randomBlobBytes() { } public List randomBlobSidecarsForBlock(final SignedBeaconBlock block) { - return randomSignedBlobSidecarsForBlock(block).stream() + return randomBlobSidecarsForBlock(block, (__, builder) -> builder); + } + + public List randomBlobSidecarsForBlock( + final SignedBeaconBlock block, + final BiFunction modifier) { + return randomSignedBlobSidecarsForBlock(block, modifier).stream() .map(SignedBlobSidecar::getBlobSidecar) .collect(toList()); } - public List randomSignedBlobSidecarsForBlock(final SignedBeaconBlock block) { - final int numberOfKzgCommitments = + public List randomSignedBlobSidecarsForBlock( + final SignedBeaconBlock block, + final BiFunction modifier) { + final SszList blobKzgCommitments = BeaconBlockBodyDeneb.required(block.getBeaconBlock().orElseThrow().getBody()) - .getBlobKzgCommitments() - .size(); - return IntStream.range(0, numberOfKzgCommitments) + .getBlobKzgCommitments(); + + return IntStream.range(0, blobKzgCommitments.size()) .mapToObj( - index -> - createRandomBlobSidecarBuilder() - .slot(block.getSlot()) - .blockRoot(block.getRoot()) - .index(UInt64.valueOf(index)) - .buildSigned()) + index -> { + final RandomBlobSidecarBuilder builder = + createRandomBlobSidecarBuilder() + .slot(block.getSlot()) + .blockRoot(block.getRoot()) + .blockParentRoot(block.getParentRoot()) + .proposerIndex(block.getMessage().getProposerIndex()) + .kzgCommitment(blobKzgCommitments.get(index).getBytes()) + .index(UInt64.valueOf(index)); + + return modifier.apply(index, builder).buildSigned(); + }) .collect(toList()); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTracker.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTracker.java index fddf76accec..ebec35ad023 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTracker.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTracker.java @@ -31,6 +31,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; @@ -45,7 +46,7 @@ public class BlockBlobSidecarsTracker { private final SlotAndBlockRoot slotAndBlockRoot; private final UInt64 maxBlobsPerBlock; - private final AtomicReference> blockBody = + private final AtomicReference> block = new AtomicReference<>(Optional.empty()); private final NavigableMap blobSidecars = new ConcurrentSkipListMap<>(); @@ -80,8 +81,8 @@ public SafeFuture getCompletionFuture() { return newCompletionFuture; } - public Optional getBlockBody() { - return blockBody.get(); + public Optional getBlock() { + return block.get(); } public boolean containsBlobSidecar(final BlobIdentifier blobIdentifier) { @@ -91,9 +92,9 @@ public boolean containsBlobSidecar(final BlobIdentifier blobIdentifier) { } public Stream getMissingBlobSidecars() { - final Optional body = blockBody.get(); - if (body.isPresent()) { - return UInt64.range(UInt64.ZERO, UInt64.valueOf(body.get().getBlobKzgCommitments().size())) + final Optional blockCommitmentsCount = getBlockKzgCommitmentsCount(); + if (blockCommitmentsCount.isPresent()) { + return UInt64.range(UInt64.ZERO, UInt64.valueOf(blockCommitmentsCount.get())) .filter(blobIndex -> !blobSidecars.containsKey(blobIndex)) .map(blobIndex -> new BlobIdentifier(slotAndBlockRoot.getBlockRoot(), blobIndex)); } @@ -109,10 +110,10 @@ public Stream getMissingBlobSidecars() { } public Stream getUnusedBlobSidecarsForBlock() { - final Optional body = blockBody.get(); - checkState(body.isPresent(), "Block must me known to call this method"); + final Optional blockCommitmentsCount = getBlockKzgCommitmentsCount(); + checkState(blockCommitmentsCount.isPresent(), "Block must me known to call this method"); - final UInt64 firstUnusedIndex = UInt64.valueOf(body.get().getBlobKzgCommitments().size()); + final UInt64 firstUnusedIndex = UInt64.valueOf(blockCommitmentsCount.get()); return UInt64.range(firstUnusedIndex, maxBlobsPerBlock) .map(blobIndex -> new BlobIdentifier(slotAndBlockRoot.getBlockRoot(), blobIndex)); } @@ -150,9 +151,7 @@ public int blobSidecarsCount() { public boolean setBlock(final SignedBeaconBlock block) { checkArgument(block.getSlotAndBlockRoot().equals(slotAndBlockRoot), "Wrong block"); - final Optional oldBlock = - blockBody.getAndSet( - Optional.of(BeaconBlockBodyDeneb.required(block.getMessage().getBody()))); + final Optional oldBlock = this.block.getAndSet(block.getBeaconBlock()); if (oldBlock.isPresent()) { return false; } @@ -196,10 +195,13 @@ public void setFetchTriggered() { } private boolean areBlobsComplete() { - return blockBody + return getBlockKzgCommitmentsCount().map(count -> blobSidecars.size() >= count).orElse(false); + } + + private Optional getBlockKzgCommitmentsCount() { + return block .get() - .map(b -> blobSidecars.size() >= b.getBlobKzgCommitments().size()) - .orElse(false); + .map(b -> BeaconBlockBodyDeneb.required(b.getBody()).getBlobKzgCommitments().size()); } /** @@ -252,7 +254,7 @@ private void printDebugTimings(final Map debugTimings) { public String toString() { return MoreObjects.toStringHelper(this) .add("slotAndBlockRoot", slotAndBlockRoot) - .add("isBlockBodyPresent", blockBody.get().isPresent()) + .add("isBlockPresent", block.get().isPresent()) .add("isCompleted", isCompleted()) .add("fetchTriggered", fetchTriggered) .add( diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java index 9b04a84c729..76e47e3f322 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityChecker.java @@ -36,8 +36,11 @@ import tech.pegasys.teku.kzg.KZGCommitment; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.versions.deneb.blobs.BlobSidecarsAndValidationResult; import tech.pegasys.teku.spec.logic.versions.deneb.blobs.BlobSidecarsAvailabilityChecker; import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTracker; @@ -56,7 +59,7 @@ public class ForkChoiceBlobSidecarsAvailabilityChecker implements BlobSidecarsAv private final SafeFuture validationResult = new SafeFuture<>(); - private final Supplier> kzgCommitmentsSupplier = + private final Supplier> kzgCommitmentsFromBlockSupplier = createLazyKzgCommitmentsSupplier(this); private final Duration waitForTrackerCompletionTimeout; @@ -93,6 +96,7 @@ public boolean initiateDataAvailabilityCheck() { asyncRunner .runAsync(this::validateImmediatelyAvailable) .thenCompose(this::completeValidation) + .thenApply(this::performCompletenessValidation) .propagateTo(validationResult); return true; @@ -106,34 +110,39 @@ public SafeFuture getAvailabilityCheckResult() @Override public BlobSidecarsAndValidationResult validateImmediately(final List blobSidecars) { - final List kzgCommitments = kzgCommitmentsSupplier.get(); + final List kzgCommitmentsFromBlock = kzgCommitmentsFromBlockSupplier.get(); if (!blobSidecars.isEmpty()) { - return validateBatch(blobSidecars, kzgCommitments); + final BlobSidecarsAndValidationResult blobSidecarsAndValidationResult = + validateBatch(blobSidecars); + + return performCompletenessValidation(blobSidecarsAndValidationResult); } + // no blob sidecars + if (isBlockOutsideDataAvailabilityWindow()) { return BlobSidecarsAndValidationResult.NOT_REQUIRED; } - if (kzgCommitments.isEmpty()) { + if (kzgCommitmentsFromBlock.isEmpty()) { return BlobSidecarsAndValidationResult.validResult(List.of()); } return BlobSidecarsAndValidationResult.NOT_AVAILABLE; } - private BlobSidecarsAndValidationResult validateBatch( - final List blobSidecars, final List kzgCommitments) { + private BlobSidecarsAndValidationResult validateBatch(final List blobSidecars) { + final BeaconBlock block = blockBlobSidecarsTracker.getBlock().orElseThrow(); final SlotAndBlockRoot slotAndBlockRoot = blockBlobSidecarsTracker.getSlotAndBlockRoot(); + + final MiscHelpers miscHelpers = spec.atSlot(slotAndBlockRoot.getSlot()).miscHelpers(); + try { - if (!spec.atSlot(slotAndBlockRoot.getSlot()) - .miscHelpers() - .isDataAvailable( - slotAndBlockRoot.getSlot(), - slotAndBlockRoot.getBlockRoot(), - kzgCommitments, - blobSidecars)) { + miscHelpers.validateBlobSidecarsBatchAgainstBlock( + blobSidecars, block, kzgCommitmentsFromBlockSupplier.get()); + + if (!miscHelpers.verifyBlobKzgProofBatch(blobSidecars)) { return BlobSidecarsAndValidationResult.invalidResult(blobSidecars); } } catch (final Exception ex) { @@ -153,16 +162,14 @@ private BlobSidecarsAndValidationResult validateBatch( * be completed it returns empty */ private Optional validateImmediatelyAvailable() { - final List kzgCommitmentsInBlock = kzgCommitmentsSupplier.get(); + final List kzgCommitmentsInBlock = kzgCommitmentsFromBlockSupplier.get(); - final List kzgCommitmentsToValidate; final List blobSidecarsToValidate; final boolean performCompleteValidation = blockBlobSidecarsTracker.isCompleted(); if (performCompleteValidation) { // the tracker contains all required blobs for our block: we can perform a complete validation - kzgCommitmentsToValidate = kzgCommitmentsInBlock; blobSidecarsToValidate = List.copyOf(blockBlobSidecarsTracker.getBlobSidecars().values()); LOG.debug( "The expected {} BlobSidecars have already been received. Performing full validation.", @@ -170,14 +177,8 @@ private Optional validateImmediatelyAvailable() } else { // prepare partial validation by matching the currently available blobs with the corresponding // commitments from the block - kzgCommitmentsToValidate = new ArrayList<>(); blobSidecarsToValidate = - blockBlobSidecarsTracker.getBlobSidecars().values().stream() - .peek( - blobSidecar -> - kzgCommitmentsToValidate.add( - kzgCommitmentsInBlock.get(blobSidecar.getIndex().intValue()))) - .toList(); + blockBlobSidecarsTracker.getBlobSidecars().values().stream().toList(); LOG.debug( "{} out of {} BlobSidecars have been received so far. Performing partial validation.", @@ -185,7 +186,7 @@ private Optional validateImmediatelyAvailable() kzgCommitmentsInBlock.size()); if (blobSidecarsToValidate.isEmpty() - && kzgCommitmentsInBlock.size() > 0 + && !kzgCommitmentsInBlock.isEmpty() && isBlockOutsideDataAvailabilityWindow()) { // there are no available blobs so far, but we are outside the availability window. We can // skip additional checks @@ -194,8 +195,7 @@ && isBlockOutsideDataAvailabilityWindow()) { } // perform the actual validation - final BlobSidecarsAndValidationResult result = - validateBatch(blobSidecarsToValidate, kzgCommitmentsToValidate); + final BlobSidecarsAndValidationResult result = validateBatch(blobSidecarsToValidate); if (result.isFailure()) { return Optional.of(result); @@ -263,10 +263,9 @@ private BlobSidecarsAndValidationResult computeAndValidateRemaining() { blockBlobSidecarsTracker.isCompleted(), "BlobSidecar tracker assumed to be completed but it is not."); - final List additionalKzgCommitmentsToBeValidated = new ArrayList<>(); final List additionalBlobSidecarsToBeValidated = new ArrayList<>(); - final List kzgCommitmentsInBlock = kzgCommitmentsSupplier.get(); + final List kzgCommitmentsInBlock = kzgCommitmentsFromBlockSupplier.get(); final SortedMap completeBlobSidecars = blockBlobSidecarsTracker.getBlobSidecars(); @@ -274,8 +273,6 @@ private BlobSidecarsAndValidationResult computeAndValidateRemaining() { .forEachOrdered( index -> { if (!validatedBlobSidecars.containsKey(index)) { - additionalKzgCommitmentsToBeValidated.add( - kzgCommitmentsInBlock.get(index.intValue())); Optional.ofNullable(completeBlobSidecars.get(index)) .ifPresentOrElse( @@ -293,15 +290,12 @@ private BlobSidecarsAndValidationResult computeAndValidateRemaining() { additionalBlobSidecarsToBeValidated.size(), kzgCommitmentsInBlock.size()); - return validateBatch( - additionalBlobSidecarsToBeValidated, additionalKzgCommitmentsToBeValidated); + return validateBatch(additionalBlobSidecarsToBeValidated); } /** * Computes the final validation result combining the already validated blobs from - * `validatedBlobSidecars` and the additional validation result containing (if validated) the - * required additional blobs to reconstruct the full list that will have to match the kzg - * commitments from the block + * `validatedBlobSidecars` * * @param additionalBlobSidecarsAndValidationResult * @return @@ -318,32 +312,47 @@ private BlobSidecarsAndValidationResult computeFinalValidationResult( .getBlobSidecars() .forEach(blobSidecar -> validatedBlobSidecars.put(blobSidecar.getIndex(), blobSidecar)); - // let's create the final list of validated BlobSidecars making sure that indices and - // final order are consistent - final List completeValidatedBlobSidecars = new ArrayList<>(); - validatedBlobSidecars.forEach( - (index, blobSidecar) -> { - checkState( - index.equals(blobSidecar.getIndex()) - && index.equals(UInt64.valueOf(completeValidatedBlobSidecars.size())), - "Inconsistency detected during blob sidecar validation"); - completeValidatedBlobSidecars.add(blobSidecar); - }); - - if (completeValidatedBlobSidecars.size() < kzgCommitmentsSupplier.get().size()) { - // we haven't verified enough blobs to match the commitments present in the block - // this should never happen in practice. If it does, is likely a bug and should be fixed. - checkState( - isBlockOutsideDataAvailabilityWindow(), - "Validated blobs are less than commitments present in block."); + final List completeValidatedBlobSidecars = + new ArrayList<>(validatedBlobSidecars.values()); + + return BlobSidecarsAndValidationResult.validResult( + Collections.unmodifiableList(completeValidatedBlobSidecars)); + } + + /** + * make sure that final blob sidecar list is complete + * + * @param blobSidecarsAndValidationResult + * @return + */ + private BlobSidecarsAndValidationResult performCompletenessValidation( + final BlobSidecarsAndValidationResult blobSidecarsAndValidationResult) { + + if (blobSidecarsAndValidationResult.isFailure() + || blobSidecarsAndValidationResult.isNotRequired()) { + return blobSidecarsAndValidationResult; + } + + try { + spec.atSlot(blockBlobSidecarsTracker.getSlotAndBlockRoot().getSlot()) + .miscHelpers() + .verifyBlobSidecarCompleteness( + blobSidecarsAndValidationResult.getBlobSidecars(), + kzgCommitmentsFromBlockSupplier.get()); + } catch (final IllegalArgumentException ex) { + if (!isBlockOutsideDataAvailabilityWindow()) { + return BlobSidecarsAndValidationResult.invalidResult( + blobSidecarsAndValidationResult.getBlobSidecars(), + new IllegalArgumentException( + "Validated blobs are less than commitments present in block.", ex)); + } LOG.error( "Inconsistent state detected: validated blobs is less then commitments in block. Since slot is outside availability the window we can consider blobs as NOT_REQUIRED."); return BlobSidecarsAndValidationResult.NOT_REQUIRED; } - return BlobSidecarsAndValidationResult.validResult( - Collections.unmodifiableList(completeValidatedBlobSidecars)); + return blobSidecarsAndValidationResult; } private boolean isBlockOutsideDataAvailabilityWindow() { @@ -355,12 +364,8 @@ static Supplier> createLazyKzgCommitmentsSupplier( final ForkChoiceBlobSidecarsAvailabilityChecker availabilityChecker) { return Suppliers.memoize( () -> - availabilityChecker - .blockBlobSidecarsTracker - .getBlockBody() - .orElseThrow() - .toVersionDeneb() - .orElseThrow() + BeaconBlockBodyDeneb.required( + availabilityChecker.blockBlobSidecarsTracker.getBlock().orElseThrow().getBody()) .getBlobKzgCommitments() .stream() .map(SszKZGCommitment::getKZGCommitment) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImpl.java index f6f68a09de9..1bf98fb4163 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImpl.java @@ -267,7 +267,7 @@ public synchronized boolean containsBlobSidecar(final BlobIdentifier blobIdentif @Override public synchronized boolean containsBlock(final Bytes32 blockRoot) { return Optional.ofNullable(blockBlobSidecarsTrackers.get(blockRoot)) - .map(tracker -> tracker.getBlockBody().isPresent()) + .map(tracker -> tracker.getBlock().isPresent()) .orElse(false); } @@ -432,7 +432,7 @@ private synchronized void fetchMissingContent(final SlotAndBlockRoot slotAndBloc blockBlobSidecarsTracker.setFetchTriggered(); - if (blockBlobSidecarsTracker.getBlockBody().isEmpty()) { + if (blockBlobSidecarsTracker.getBlock().isEmpty()) { requiredBlockRootSubscribers.deliver( RequiredBlockRootSubscriber::onRequiredBlockRoot, blockBlobSidecarsTracker.getSlotAndBlockRoot().getBlockRoot()); @@ -452,7 +452,7 @@ private void dropMissingContent(final BlockBlobSidecarsTracker blockBlobSidecars return; } - if (blockBlobSidecarsTracker.getBlockBody().isEmpty()) { + if (blockBlobSidecarsTracker.getBlock().isEmpty()) { requiredBlockRootDroppedSubscribers.deliver( RequiredBlockRootDroppedSubscriber::onRequiredBlockRootDropped, blockBlobSidecarsTracker.getSlotAndBlockRoot().getBlockRoot()); diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTrackerTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTrackerTest.java index f032455d2e7..c098e53b41a 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTrackerTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/blobs/BlockBlobSidecarsTrackerTest.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; @@ -61,7 +60,7 @@ void isNotCompletedJustAfterCreation() { SafeFutureAssert.assertThatSafeFuture(blockBlobSidecarsTracker.getCompletionFuture()) .isNotCompleted(); assertThat(blockBlobSidecarsTracker.getMissingBlobSidecars()).isEmpty(); - assertThat(blockBlobSidecarsTracker.getBlockBody()).isEmpty(); + assertThat(blockBlobSidecarsTracker.getBlock()).isEmpty(); assertThat(blockBlobSidecarsTracker.getBlobSidecars()).isEmpty(); assertThat(blockBlobSidecarsTracker.getSlotAndBlockRoot()).isEqualTo(slotAndBlockRoot); assertThat( @@ -79,8 +78,7 @@ void setBlock_shouldAcceptCorrectBlock() { .isNotCompleted(); assertThat(blockBlobSidecarsTracker.getMissingBlobSidecars()) .containsExactlyInAnyOrderElementsOf(blobIdentifiersForBlock); - assertThat(blockBlobSidecarsTracker.getBlockBody()) - .isEqualTo(Optional.of(block.getMessage().getBody())); + assertThat(blockBlobSidecarsTracker.getBlock()).isEqualTo(block.getBeaconBlock()); assertThat(blockBlobSidecarsTracker.getBlobSidecars()).isEmpty(); } @@ -98,8 +96,7 @@ void setBlock_shouldAcceptBlockTwice() { new BlockBlobSidecarsTracker(slotAndBlockRoot, maxBlobsPerBlock); blockBlobSidecarsTracker.setBlock(block); blockBlobSidecarsTracker.setBlock(block); - assertThat(blockBlobSidecarsTracker.getBlockBody()) - .isEqualTo(Optional.of(block.getMessage().getBody())); + assertThat(blockBlobSidecarsTracker.getBlock()).isEqualTo(block.getBeaconBlock()); } @Test @@ -182,8 +179,7 @@ void add_shouldWorkTillCompletionWhenAddingBlobsBeforeBlockIsSet() { .containsExactlyInAnyOrderEntriesOf(added); blockBlobSidecarsTracker.setBlock(block); - assertThat(blockBlobSidecarsTracker.getBlockBody()) - .isEqualTo(Optional.of(block.getMessage().getBody())); + assertThat(blockBlobSidecarsTracker.getBlock()).isEqualTo(block.getBeaconBlock()); // now we know the block and we know about missing blobs final List stillMissing = @@ -237,8 +233,7 @@ void add_shouldWorkWhenBlockIsSetFirst() { SafeFutureAssert.assertThatSafeFuture(completionFuture).isNotCompleted(); assertThat(blockBlobSidecarsTracker.getBlobSidecars()) .containsExactlyInAnyOrderEntriesOf(added); - assertThat(blockBlobSidecarsTracker.getBlockBody()) - .isEqualTo(Optional.of(block.getMessage().getBody())); + assertThat(blockBlobSidecarsTracker.getBlock()).isEqualTo(block.getBeaconBlock()); } @Test @@ -255,8 +250,7 @@ void add_shouldAcceptAcceptSameBlobSidecarTwice() { new BlockBlobSidecarsTracker(slotAndBlockRoot, maxBlobsPerBlock); blockBlobSidecarsTracker.setBlock(block); blockBlobSidecarsTracker.setBlock(block); - assertThat(blockBlobSidecarsTracker.getBlockBody()) - .isEqualTo(Optional.of(block.getMessage().getBody())); + assertThat(blockBlobSidecarsTracker.getBlock()).isEqualTo(block.getBeaconBlock()); } @Test diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityCheckerTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityCheckerTest.java index e9fc445a660..c3d647bbfc4 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityCheckerTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceBlobSidecarsAvailabilityCheckerTest.java @@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -29,17 +30,16 @@ import com.google.common.collect.ImmutableSortedMap; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.ArgumentMatcher; import org.mockito.stubbing.OngoingStubbing; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.async.SafeFutureAssert; @@ -80,9 +80,7 @@ public class ForkChoiceBlobSidecarsAvailabilityCheckerTest { private List kzgCommitmentsComplete; private List blobSidecarsInitial; - private List kzgCommitmentsInitial; private List blobSidecarsAdditional; - private List kzgCommitmentsAdditional; private final SafeFuture trackerCompletionFuture = new SafeFuture<>(); @@ -115,16 +113,16 @@ void shouldVerifyAvailableBlobsInTwoBatches(final Availability availability) thr verify(blockBlobSidecarsTracker, never()).getBlobSidecars(); // mock kzg availability check to be OK for the initial set - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenReturn(true); + whenDataAvailability(blobSidecarsInitial).thenReturn(true); // let availability check to be performed. asyncRunner.executeDueActions(); // verify that kzg validation has been performed for the initial batch - verifyDataAvailabilityCall(blobSidecarsInitial, kzgCommitmentsInitial); + verifyValidationAndDataAvailabilityCall(blobSidecarsInitial, false); // mock the additional check to be OK. - whenDataAvailability(blobSidecarsAdditional, kzgCommitmentsAdditional).thenReturn(true); + whenDataAvailability(blobSidecarsAdditional).thenReturn(true); // let the tracker complete with all blobSidecars completeTrackerWith(blobSidecarsComplete); @@ -132,7 +130,7 @@ void shouldVerifyAvailableBlobsInTwoBatches(final Availability availability) thr Waiter.waitFor(availabilityCheckResult); // verify that kzg validation has been performed for the additional batch - verifyDataAvailabilityCall(blobSidecarsAdditional, kzgCommitmentsAdditional); + verifyValidationAndDataAvailabilityCall(blobSidecarsAdditional, true); assertAvailable(availabilityCheckResult); @@ -153,7 +151,7 @@ void shouldVerifyAvailableBlobsInOneBatch() throws Exception { assertThat(blobSidecarsAvailabilityChecker.initiateDataAvailabilityCheck()).isTrue(); // mock kzg availability check to be OK for the initial set - whenDataAvailability(blobSidecarsComplete, kzgCommitmentsComplete).thenReturn(true); + whenDataAvailability(blobSidecarsComplete).thenReturn(true); // tracker is completed in advance completeTrackerWith(blobSidecarsComplete); @@ -162,7 +160,7 @@ void shouldVerifyAvailableBlobsInOneBatch() throws Exception { asyncRunner.executeDueActions(); // verify that kzg validation has been performed for the initial batch - verifyDataAvailabilityCall(blobSidecarsComplete, kzgCommitmentsComplete); + verifyValidationAndDataAvailabilityCall(blobSidecarsComplete, true); Waiter.waitFor(availabilityCheckResult); @@ -185,7 +183,7 @@ void shouldReturnNotAvailableOnTimeout() throws Exception { verifyDataAvailabilityNeverCalled(); verify(blockBlobSidecarsTracker, never()).getBlobSidecars(); - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenReturn(true); + whenDataAvailability(blobSidecarsInitial).thenReturn(true); asyncRunner.executeDueActions(); @@ -210,12 +208,16 @@ void shouldReturnNotRequiredWhenBlockIsOutsideAvailabilityWindow() { } @ParameterizedTest - @ValueSource(booleans = {true, false}) - void shouldReturnNotAvailableIfFirstBatchFails(final boolean failByException) { + @EnumSource(value = BatchFailure.class) + void shouldReturnNotAvailableIfFirstBatchFails(final BatchFailure batchFailure) { prepareInitialAvailability(Availability.PARTIAL); final Optional cause = - failByException ? Optional.of(new RuntimeException("oops")) : Optional.empty(); + switch (batchFailure) { + case BLOB_SIDECAR_VALIDATION_EXCEPTION, IS_DATA_AVAILABLE_EXCEPTION -> Optional.of( + new RuntimeException("oops")); + default -> Optional.empty(); + }; final SafeFuture availabilityCheckResult = blobSidecarsAvailabilityChecker.getAvailabilityCheckResult(); @@ -225,11 +227,20 @@ void shouldReturnNotAvailableIfFirstBatchFails(final boolean failByException) { // initiate availability check assertThat(blobSidecarsAvailabilityChecker.initiateDataAvailabilityCheck()).isTrue(); - // mock kzg availability check failure for the initial set - if (failByException) { - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenThrow(cause.get()); - } else { - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenReturn(false); + switch (batchFailure) { + // blobsidecar validation check failure for the initial set + case BLOB_SIDECAR_VALIDATION_EXCEPTION: + throwWhenValidatingBlobSidecarsBatchAgainstBlock(blobSidecarsInitial, cause.get()); + break; + + // mock kzg availability check failure for the initial set + case IS_DATA_AVAILABLE_EXCEPTION: + whenDataAvailability(blobSidecarsInitial).thenThrow(cause.get()); + break; + + case IS_DATA_AVAILABLE_RETURN_FALSE: + whenDataAvailability(blobSidecarsInitial).thenReturn(false); + break; } asyncRunner.executeDueActions(); @@ -238,12 +249,16 @@ void shouldReturnNotAvailableIfFirstBatchFails(final boolean failByException) { } @ParameterizedTest - @ValueSource(booleans = {true, false}) - void shouldReturnNotAvailableIfSecondBatchFails(final boolean failByException) { + @EnumSource(value = BatchFailure.class) + void shouldReturnNotAvailableIfSecondBatchFails(final BatchFailure batchFailure) { prepareInitialAvailability(Availability.PARTIAL); final Optional cause = - failByException ? Optional.of(new RuntimeException("oops")) : Optional.empty(); + switch (batchFailure) { + case BLOB_SIDECAR_VALIDATION_EXCEPTION, IS_DATA_AVAILABLE_EXCEPTION -> Optional.of( + new RuntimeException("oops")); + default -> Optional.empty(); + }; final SafeFuture availabilityCheckResult = blobSidecarsAvailabilityChecker.getAvailabilityCheckResult(); @@ -254,16 +269,25 @@ void shouldReturnNotAvailableIfSecondBatchFails(final boolean failByException) { assertThat(blobSidecarsAvailabilityChecker.initiateDataAvailabilityCheck()).isTrue(); // mock kzg availability check to be OK for the initial set - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenReturn(true); + whenDataAvailability(blobSidecarsInitial).thenReturn(true); // let availability check to be performed. asyncRunner.executeDueActions(); - // mock kzg availability check failure for the initial set - if (failByException) { - whenDataAvailability(blobSidecarsAdditional, kzgCommitmentsAdditional).thenThrow(cause.get()); - } else { - whenDataAvailability(blobSidecarsAdditional, kzgCommitmentsAdditional).thenReturn(false); + switch (batchFailure) { + // blobsidecar validation check failure for the additional set + case BLOB_SIDECAR_VALIDATION_EXCEPTION: + throwWhenValidatingBlobSidecarsBatchAgainstBlock(blobSidecarsAdditional, cause.get()); + break; + + // mock kzg availability check failure for the additional set + case IS_DATA_AVAILABLE_EXCEPTION: + whenDataAvailability(blobSidecarsAdditional).thenThrow(cause.get()); + break; + + case IS_DATA_AVAILABLE_RETURN_FALSE: + whenDataAvailability(blobSidecarsAdditional).thenReturn(false); + break; } // let the tracker complete with all blobSidecars @@ -273,7 +297,7 @@ void shouldReturnNotAvailableIfSecondBatchFails(final boolean failByException) { } @Test - void shouldThrowIfTrackerLiesWithCompletionButItIsNot() { + void shouldReturnInvalidIfTrackerLiesWithCompletionButItIsNot() { prepareInitialAvailability(Availability.PARTIAL); final SafeFuture availabilityCheckResult = @@ -290,31 +314,44 @@ void shouldThrowIfTrackerLiesWithCompletionButItIsNot() { verify(blockBlobSidecarsTracker, never()).getBlobSidecars(); // mock kzg availability check to be OK for the initial set - whenDataAvailability(blobSidecarsInitial, kzgCommitmentsInitial).thenReturn(true); + whenDataAvailability(blobSidecarsInitial).thenReturn(true); // let availability check to be performed. asyncRunner.executeDueActions(); // verify that kzg validation has been performed for the initial batch - verifyDataAvailabilityCall(blobSidecarsInitial, kzgCommitmentsInitial); + verifyValidationAndDataAvailabilityCall(blobSidecarsInitial, false); // we complete the blobs without index 3 final List partialBlobs = blobSidecarsComplete.subList(1, 2); // we lie on availability check too (not actually possible) - whenDataAvailability(partialBlobs, kzgCommitmentsAdditional).thenReturn(true); + whenDataAvailability(partialBlobs).thenReturn(true); + + final List expectedIncompleteBlobSidecar = new ArrayList<>(); + expectedIncompleteBlobSidecar.add(blobSidecarsComplete.get(0)); // blob 0 + expectedIncompleteBlobSidecar.add(blobSidecarsComplete.get(1)); // blob 1 + expectedIncompleteBlobSidecar.add(blobSidecarsComplete.get(2)); // blob 2 + + final Throwable cause = new IllegalArgumentException("oops"); + + throwWhenVerifyingBlobSidecarCompleteness(expectedIncompleteBlobSidecar, cause); // let the tracker complete with all blobSidecars completeTrackerWith(partialBlobs); - SafeFutureAssert.assertThatSafeFuture(availabilityCheckResult) - .isCompletedExceptionallyWith(IllegalStateException.class); + assertInvalid( + availabilityCheckResult, + expectedIncompleteBlobSidecar, + Optional.of( + new IllegalArgumentException( + "Validated blobs are less than commitments present in block.", cause))); } @Test - void validate_shouldReturnAvailable() { + void validateImmediately_shouldReturnAvailable() { prepareInitialAvailability(Availability.FULL); - whenDataAvailability(blobSidecarsComplete, kzgCommitmentsComplete).thenReturn(true); + whenDataAvailability(blobSidecarsComplete).thenReturn(true); assertAvailable( SafeFuture.completedFuture( @@ -322,7 +359,26 @@ void validate_shouldReturnAvailable() { } @Test - void validate_shouldNotAvailable() { + void validateImmediately_shouldReturnInvalidIfCompletenessCheckFails() { + prepareInitialAvailability(Availability.FULL); + + whenDataAvailability(blobSidecarsComplete).thenReturn(true); + final Throwable cause = new IllegalArgumentException("oops"); + doThrow(cause).when(miscHelpers).verifyBlobSidecarCompleteness(any(), any()); + + final BlobSidecarsAndValidationResult availabilityCheckResult = + blobSidecarsAvailabilityChecker.validateImmediately(blobSidecarsComplete); + + assertInvalid( + SafeFuture.completedFuture(availabilityCheckResult), + blobSidecarsComplete, + Optional.of( + new IllegalArgumentException( + "Validated blobs are less than commitments present in block.", cause))); + } + + @Test + void validateImmediately_shouldReturnNotAvailableWithEmptyBlobsButRequired() { prepareInitialAvailability(Availability.FULL); assertNotAvailable( @@ -331,7 +387,7 @@ void validate_shouldNotAvailable() { } @Test - void validate_shouldReturnAvailableOnEmptyBlobs() { + void validateImmediately_shouldReturnAvailableOnEmptyBlobs() { prepareInitialAvailabilityWithEmptyCommitmentsBlock(); assertAvailable( @@ -340,7 +396,7 @@ void validate_shouldReturnAvailableOnEmptyBlobs() { } @Test - void validate_shouldReturnNotRequiredWhenBlockIsOutsideAvailabilityWindow() { + void validateImmediately_shouldReturnNotRequiredWhenBlockIsOutsideAvailabilityWindow() { prepareBlockAndBlobSidecarsOutsideAvailabilityWindow(); assertNotRequired( @@ -371,7 +427,20 @@ private void assertInvalid( .isCompletedWithValueMatching( result -> result.getBlobSidecars().equals(invalidBlobs), "doesn't have blob sidecars") .isCompletedWithValueMatching( - result -> result.getCause().equals(cause), "matches the cause"); + result -> { + if (cause.isEmpty() != result.getCause().isEmpty()) { + return false; + } + return result + .getCause() + .map( + resultCause -> + resultCause.getClass().equals(cause.get().getClass()) + && Objects.equals(resultCause.getMessage(), cause.get().getMessage()) + && Objects.equals(resultCause.getCause(), cause.get().getCause())) + .orElse(true); + }, + "matches the cause"); } private void assertNotAvailableDueToTimeout( @@ -441,31 +510,22 @@ private void prepareInitialAvailability( .getBlobKzgCommitments() .stream() .map(SszKZGCommitment::getKZGCommitment) - .collect(Collectors.toUnmodifiableList()); + .toList(); when(spec.isAvailabilityOfBlobSidecarsRequiredAtSlot(store, block.getSlot())).thenReturn(true); switch (blobsAvailability) { case FULL: blobSidecarsInitial = blobSidecarsComplete; - kzgCommitmentsInitial = kzgCommitmentsComplete; blobSidecarsAdditional = List.of(); - kzgCommitmentsAdditional = List.of(); break; case EMPTY: blobSidecarsInitial = List.of(); - kzgCommitmentsInitial = List.of(); blobSidecarsAdditional = blobSidecarsComplete; - kzgCommitmentsAdditional = kzgCommitmentsComplete; break; case PARTIAL: blobSidecarsInitial = List.of(blobSidecarsComplete.get(0), blobSidecarsComplete.get(2)); - kzgCommitmentsInitial = - List.of(kzgCommitmentsComplete.get(0), kzgCommitmentsComplete.get(2)); - blobSidecarsAdditional = List.of(blobSidecarsComplete.get(1), blobSidecarsComplete.get(3)); - kzgCommitmentsAdditional = - List.of(kzgCommitmentsComplete.get(1), kzgCommitmentsComplete.get(3)); break; } @@ -473,8 +533,7 @@ private void prepareInitialAvailability( ImmutableSortedMap.naturalOrder(); blobSidecarsInitial.forEach(blobSidecar -> mapBuilder.put(blobSidecar.getIndex(), blobSidecar)); - when(blockBlobSidecarsTracker.getBlockBody()) - .thenReturn(block.getMessage().getBody().toVersionDeneb()); + when(blockBlobSidecarsTracker.getBlock()).thenReturn(block.getBeaconBlock()); when(blockBlobSidecarsTracker.getBlobSidecars()).thenReturn(mapBuilder.build()); when(blockBlobSidecarsTracker.getSlotAndBlockRoot()).thenReturn(block.getSlotAndBlockRoot()); @@ -492,25 +551,53 @@ private void completeTrackerWith(final List blobSidecars) { trackerCompletionFuture.complete(null); } - private OngoingStubbing whenDataAvailability( - final List blobSidecars, final List kzgCommitments) { - return when( - miscHelpers.isDataAvailable( - eq(block.getSlot()), - eq(block.getRoot()), - argThat(new KzgCommitmentsArgumentMatcher(kzgCommitments)), - eq(blobSidecars))); + private OngoingStubbing whenDataAvailability(final List blobSidecars) { + return when(miscHelpers.verifyBlobKzgProofBatch(eq(blobSidecars))); } - private void verifyDataAvailabilityCall( - final List blobSidecars, final List kzgCommitments) { + private void throwWhenValidatingBlobSidecarsBatchAgainstBlock( + final List blobSidecars, final Throwable cause) { + doThrow(cause) + .when(miscHelpers) + .validateBlobSidecarsBatchAgainstBlock( + eq(blobSidecars), + argThat(block -> block.equals(this.block.getBeaconBlock().orElseThrow())), + assertArg( + kzgCommitmentsArg -> + assertThat(kzgCommitmentsArg).isEqualTo(kzgCommitmentsComplete))); + } + private void throwWhenVerifyingBlobSidecarCompleteness( + final List blobSidecars, final Throwable cause) { + doThrow(cause) + .when(miscHelpers) + .verifyBlobSidecarCompleteness( + eq(blobSidecars), + assertArg( + kzgCommitmentsArg -> + assertThat(kzgCommitmentsArg).isEqualTo(kzgCommitmentsComplete))); + } + + private void verifyValidationAndDataAvailabilityCall( + final List blobSidecars, final boolean isFinalValidation) { verify(miscHelpers, times(1)) - .isDataAvailable( - eq(block.getSlot()), - eq(block.getRoot()), - assertArg(kzgCommitmentsArg -> assertThat(kzgCommitmentsArg).isEqualTo(kzgCommitments)), - eq(blobSidecars)); + .validateBlobSidecarsBatchAgainstBlock( + eq(blobSidecars), + argThat(block -> block.equals(this.block.getBeaconBlock().orElseThrow())), + assertArg( + kzgCommitmentsArg -> + assertThat(kzgCommitmentsArg).isEqualTo(kzgCommitmentsComplete))); + + verify(miscHelpers, times(1)).verifyBlobKzgProofBatch(eq(blobSidecars)); + + if (isFinalValidation) { + verify(miscHelpers, times(1)) + .verifyBlobSidecarCompleteness( + eq(blobSidecarsComplete), + assertArg( + kzgCommitmentsArg -> + assertThat(kzgCommitmentsArg).isEqualTo(kzgCommitmentsComplete))); + } // assume we verified all interaction before resetting verifyNoMoreInteractions(miscHelpers); @@ -518,7 +605,7 @@ private void verifyDataAvailabilityCall( } private void verifyDataAvailabilityNeverCalled() { - verify(miscHelpers, never()).isDataAvailable(any(), any(), any(), any()); + verify(miscHelpers, never()).verifyBlobKzgProofBatch(any()); } private void prepareBlockAndBlobSidecarsOutsideAvailabilityWindow() { @@ -531,8 +618,7 @@ private void prepareBlockAndBlobSidecarsOutsideAvailabilityWindow() { blobSidecar -> mapBuilder.put(blobSidecar.getIndex(), blobSidecar)); when(spec.isAvailabilityOfBlobSidecarsRequiredAtSlot(store, block.getSlot())).thenReturn(false); - when(blockBlobSidecarsTracker.getBlockBody()) - .thenReturn(block.getMessage().getBody().toVersionDeneb()); + when(blockBlobSidecarsTracker.getBlock()).thenReturn(block.getBeaconBlock()); when(blockBlobSidecarsTracker.getCompletionFuture()).thenReturn(SafeFuture.COMPLETE); when(blockBlobSidecarsTracker.getBlobSidecars()).thenReturn(ImmutableSortedMap.of()); when(blockBlobSidecarsTracker.getSlotAndBlockRoot()).thenReturn(block.getSlotAndBlockRoot()); @@ -548,18 +634,9 @@ private enum Availability { FULL } - private static class KzgCommitmentsArgumentMatcher - implements ArgumentMatcher> { - - private final List kzgCommitments; - - private KzgCommitmentsArgumentMatcher(final List kzgCommitments) { - this.kzgCommitments = kzgCommitments; - } - - @Override - public boolean matches(final List argument) { - return kzgCommitments.equals(argument); - } + private enum BatchFailure { + BLOB_SIDECAR_VALIDATION_EXCEPTION, + IS_DATA_AVAILABLE_EXCEPTION, + IS_DATA_AVAILABLE_RETURN_FALSE, } } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImplTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImplTest.java index a4b1407fae1..73056c1a0b8 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImplTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/util/BlobSidecarPoolImplTest.java @@ -47,7 +47,6 @@ import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.util.DataStructureUtil; @@ -322,7 +321,7 @@ public void onCompletedBlockAndBlobSidecars_shouldCreateTrackerIgnoringHistorica assertThat(blockBlobSidecarsTracker.getBlobSidecars().values()) .containsExactlyInAnyOrderElementsOf(blobSidecars); - assertThat(blockBlobSidecarsTracker.getBlockBody()).isPresent(); + assertThat(blockBlobSidecarsTracker.getBlock()).isPresent(); assertThat(blockBlobSidecarsTracker.isFetchTriggered()).isFalse(); assertThatSafeFuture(blockBlobSidecarsTracker.getCompletionFuture()).isCompleted(); @@ -352,7 +351,7 @@ public void onCompletedBlockAndBlobSidecars_shouldNotTriggerFetch() { assertThat(blockBlobSidecarsTracker.getBlobSidecars().values()) .containsExactlyInAnyOrderElementsOf(blobSidecars); - assertThat(blockBlobSidecarsTracker.getBlockBody()).isPresent(); + assertThat(blockBlobSidecarsTracker.getBlock()).isPresent(); assertThat(blockBlobSidecarsTracker.isFetchTriggered()).isFalse(); assertThatSafeFuture(blockBlobSidecarsTracker.getCompletionFuture()).isNotCompleted(); @@ -370,7 +369,7 @@ public void onCompletedBlockAndBlobSidecars_shouldNotTriggerFetch() { final BlockBlobSidecarsTracker blockBlobSidecarsTracker = blobSidecarPool.getOrCreateBlockBlobSidecarsTracker(block); - assertThat(blockBlobSidecarsTracker.getBlockBody()).isPresent(); + assertThat(blockBlobSidecarsTracker.getBlock()).isPresent(); assertThat(blockBlobSidecarsTracker.isFetchTriggered()).isFalse(); assertThatSafeFuture(blockBlobSidecarsTracker.getCompletionFuture()).isNotCompleted(); @@ -485,8 +484,7 @@ void shouldFetchMissingBlobSidecars() { (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); when(tracker.getMissingBlobSidecars()).thenReturn(missingBlobs.stream()); - when(tracker.getBlockBody()) - .thenReturn(Optional.of((BeaconBlockBodyDeneb) block.getMessage().getBody())); + when(tracker.getBlock()).thenReturn(block.getBeaconBlock()); return tracker; }); @@ -520,7 +518,7 @@ void shouldFetchMissingBlockAndBlobSidecars() { new BlobIdentifier(slotAndBlockRoot.getBlockRoot(), UInt64.ZERO)); final BlockBlobSidecarsTracker mockedTracker = mock(BlockBlobSidecarsTracker.class); - when(mockedTracker.getBlockBody()).thenReturn(Optional.empty()); + when(mockedTracker.getBlock()).thenReturn(Optional.empty()); when(mockedTracker.getMissingBlobSidecars()).thenReturn(missingBlobs.stream()); when(mockedTracker.getSlotAndBlockRoot()).thenReturn(slotAndBlockRoot); @@ -563,7 +561,7 @@ void shouldDropBlobSidecarsThatHasBeenFetchedButNotPresentInBlock() { Optional.of( (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); - when(tracker.getBlockBody()).thenReturn(Optional.empty()); + when(tracker.getBlock()).thenReturn(Optional.empty()); when(tracker.getSlotAndBlockRoot()).thenReturn(slotAndBlockRoot); when(tracker.setBlock(block)).thenReturn(true); when(tracker.isFetchTriggered()).thenReturn(true); @@ -601,7 +599,7 @@ void shouldNotDropUnusedBlobSidecarsIfFetchingHasNotOccurred() { Optional.of( (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); - when(tracker.getBlockBody()).thenReturn(Optional.empty()); + when(tracker.getBlock()).thenReturn(Optional.empty()); when(tracker.getSlotAndBlockRoot()).thenReturn(slotAndBlockRoot); when(tracker.setBlock(block)).thenReturn(true); when(tracker.isFetchTriggered()).thenReturn(false); @@ -652,8 +650,7 @@ void shouldDropPossiblyFetchedBlobSidecars() { (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); when(tracker.getMissingBlobSidecars()).thenReturn(missingBlobs.stream()); - when(tracker.getBlockBody()) - .thenReturn(Optional.of((BeaconBlockBodyDeneb) block.getMessage().getBody())); + when(tracker.getBlock()).thenReturn(block.getBeaconBlock()); when(tracker.getSlotAndBlockRoot()).thenReturn(block.getSlotAndBlockRoot()); when(tracker.isFetchTriggered()).thenReturn(true); return tracker; @@ -688,7 +685,7 @@ void shouldDropPossiblyFetchedBlock() { Optional.of( (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); - when(tracker.getBlockBody()).thenReturn(Optional.empty()); + when(tracker.getBlock()).thenReturn(Optional.empty()); when(tracker.getSlotAndBlockRoot()).thenReturn(slotAndBlockRoot); when(tracker.isFetchTriggered()).thenReturn(true); return tracker; @@ -723,7 +720,7 @@ void shouldNotDropPossiblyFetchedBlockIfFetchHasNotOccurred() { Optional.of( (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); - when(tracker.getBlockBody()).thenReturn(Optional.empty()); + when(tracker.getBlock()).thenReturn(Optional.empty()); when(tracker.getSlotAndBlockRoot()).thenReturn(slotAndBlockRoot); when(tracker.isFetchTriggered()).thenReturn(false); return tracker; @@ -847,8 +844,7 @@ void getAllRequiredBlobSidecars_shouldReturnAllRequiredBlobSidecars() { (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); when(tracker.getMissingBlobSidecars()).thenReturn(missingBlobs1.stream()); - when(tracker.getBlockBody()) - .thenReturn(Optional.of((BeaconBlockBodyDeneb) block1.getMessage().getBody())); + when(tracker.getBlock()).thenReturn(block1.getBeaconBlock()); return tracker; }); @@ -866,8 +862,7 @@ void getAllRequiredBlobSidecars_shouldReturnAllRequiredBlobSidecars() { (slotAndRoot) -> { BlockBlobSidecarsTracker tracker = mock(BlockBlobSidecarsTracker.class); when(tracker.getMissingBlobSidecars()).thenReturn(missingBlobs2.stream()); - when(tracker.getBlockBody()) - .thenReturn(Optional.of((BeaconBlockBodyDeneb) block2.getMessage().getBody())); + when(tracker.getBlock()).thenReturn(block2.getBeaconBlock()); return tracker; });