diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index e8171babdcd..35a699a83f2 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; @@ -34,7 +35,9 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.BeaconChainMethods; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarByRootValidator; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeListenerValidatingProxy; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlocksByRangeListenerWrapper; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.StatusMessageFactory; @@ -246,7 +249,8 @@ public SafeFuture requestBlobSidecarsByRoot( method, new BlobSidecarsByRootRequestMessage( blobSidecarsByRootRequestMessageSchema.get(), blobIdentifiers), - listener)) + new BlobSidecarsByRootListenerValidatingProxy( + this, spec, listener, kzg, blobIdentifiers))) .orElse(failWithUnsupportedMethodException("BlobSidecarsByRoot")); } @@ -278,10 +282,19 @@ public SafeFuture> requestBlobSidecarByRoot( .map( method -> requestOptionalItem( - method, - new BlobSidecarsByRootRequestMessage( - blobSidecarsByRootRequestMessageSchema.get(), - Collections.singletonList(blobIdentifier)))) + method, + new BlobSidecarsByRootRequestMessage( + blobSidecarsByRootRequestMessageSchema.get(), + Collections.singletonList(blobIdentifier))) + .thenPeek( + maybeBlobSidecar -> + maybeBlobSidecar.ifPresent( + blobSidecar -> { + final BlobSidecarByRootValidator blobSidecarByRootValidator = + new BlobSidecarByRootValidator( + this, spec, kzg, Set.of(blobIdentifier)); + blobSidecarByRootValidator.validateBlobSidecar(blobSidecar); + }))) .orElse(failWithUnsupportedMethodException("BlobSidecarsByRoot")); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidator.java similarity index 87% rename from networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidatingProxy.java rename to networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidator.java index cd952dbbf27..a950331b594 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidatingProxy.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/AbstractBlobSidecarsValidator.java @@ -20,19 +20,19 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -public class AbstractBlobSidecarsValidatingProxy { +public class AbstractBlobSidecarsValidator { private static final Logger LOG = LogManager.getLogger(); protected final Spec spec; protected final KZG kzg; - public AbstractBlobSidecarsValidatingProxy(final Spec spec, final KZG kzg) { + public AbstractBlobSidecarsValidator(final Spec spec, final KZG kzg) { this.spec = spec; this.kzg = kzg; } - protected boolean verifyBlobSidecar(final BlobSidecar blobSidecar) { + boolean verifyBlobSidecarKzg(final BlobSidecar blobSidecar) { try { return spec.atSlot(blobSidecar.getSlot()).miscHelpers().verifyBlobKzgProof(kzg, blobSidecar); } catch (final KZGException ex) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidator.java new file mode 100644 index 00000000000..cba6d975d73 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidator.java @@ -0,0 +1,54 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_KZG_VERIFICATION_FAILED; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNEXPECTED_IDENTIFIER; + +import java.util.Set; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.p2p.peer.Peer; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; + +public class BlobSidecarByRootValidator extends AbstractBlobSidecarsValidator { + + private final Peer peer; + private final Set expectedBlobIdentifiers; + + public BlobSidecarByRootValidator( + final Peer peer, + final Spec spec, + final KZG kzg, + final Set expectedBlobIdentifiers) { + super(spec, kzg); + this.peer = peer; + this.expectedBlobIdentifiers = expectedBlobIdentifiers; + } + + public void validateBlobSidecar(final BlobSidecar blobSidecar) { + final BlobIdentifier blobIdentifier = + new BlobIdentifier(blobSidecar.getBlockRoot(), blobSidecar.getIndex()); + if (!expectedBlobIdentifiers.contains(blobIdentifier)) { + throw new BlobSidecarsResponseInvalidResponseException( + peer, BLOB_SIDECAR_UNEXPECTED_IDENTIFIER); + } + + if (!verifyBlobSidecarKzg(blobSidecar)) { + throw new BlobSidecarsResponseInvalidResponseException( + peer, BLOB_SIDECAR_KZG_VERIFICATION_FAILED); + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxy.java index 60bbb3a315f..5c6f39633d1 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxy.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxy.java @@ -13,11 +13,11 @@ package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; -import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_KZG_VERIFICATION_FAILED; -import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_SLOT_NOT_IN_RANGE; -import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNEXPECTED_INDEX; -import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNEXPECTED_SLOT; -import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNKNOWN_PARENT; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_KZG_VERIFICATION_FAILED; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_SLOT_NOT_IN_RANGE; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNEXPECTED_INDEX; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNEXPECTED_SLOT; +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsResponseInvalidResponseException.InvalidResponseType.BLOB_SIDECAR_UNKNOWN_PARENT; import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; @@ -29,7 +29,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -public class BlobSidecarsByRangeListenerValidatingProxy extends AbstractBlobSidecarsValidatingProxy +public class BlobSidecarsByRangeListenerValidatingProxy extends AbstractBlobSidecarsValidator implements RpcResponseListener { private final Peer peer; @@ -62,20 +62,20 @@ public SafeFuture onResponse(final BlobSidecar blobSidecar) { () -> { final UInt64 blobSidecarSlot = blobSidecar.getSlot(); if (!blobSidecarSlotIsInRange(blobSidecarSlot)) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( + throw new BlobSidecarsResponseInvalidResponseException( peer, BLOB_SIDECAR_SLOT_NOT_IN_RANGE); } if (blobSidecar.getIndex().isGreaterThanOrEqualTo(maxBlobsPerBlock)) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( + throw new BlobSidecarsResponseInvalidResponseException( peer, BLOB_SIDECAR_UNEXPECTED_INDEX); } final BlobSidecarSummary blobSidecarSummary = BlobSidecarSummary.create(blobSidecar); verifyBlobSidecarIsAfterLast(blobSidecarSummary); - if (!verifyBlobSidecar(blobSidecar)) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( + if (!verifyBlobSidecarKzg(blobSidecar)) { + throw new BlobSidecarsResponseInvalidResponseException( peer, BLOB_SIDECAR_KZG_VERIFICATION_FAILED); } @@ -92,34 +92,29 @@ private boolean blobSidecarSlotIsInRange(final UInt64 blobSidecarSlot) { private void verifyBlobSidecarIsAfterLast(final BlobSidecarSummary blobSidecarSummary) { if (maybeLastBlobSidecarSummary.isEmpty()) { if (!blobSidecarSummary.index().equals(UInt64.ZERO)) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( - peer, BLOB_SIDECAR_UNEXPECTED_INDEX); + throw new BlobSidecarsResponseInvalidResponseException(peer, BLOB_SIDECAR_UNEXPECTED_INDEX); } return; } if (blobSidecarSummary.inTheSameBlock(maybeLastBlobSidecarSummary.get())) { if (!blobSidecarSummary.index().equals(maybeLastBlobSidecarSummary.get().index().plus(1))) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( - peer, BLOB_SIDECAR_UNEXPECTED_INDEX); + throw new BlobSidecarsResponseInvalidResponseException(peer, BLOB_SIDECAR_UNEXPECTED_INDEX); } } else { if (!blobSidecarSummary.index().equals(UInt64.ZERO)) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( - peer, BLOB_SIDECAR_UNEXPECTED_INDEX); + throw new BlobSidecarsResponseInvalidResponseException(peer, BLOB_SIDECAR_UNEXPECTED_INDEX); } if (!blobSidecarSummary .blockParentRoot() .equals(maybeLastBlobSidecarSummary.get().blockRoot())) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( - peer, BLOB_SIDECAR_UNKNOWN_PARENT); + throw new BlobSidecarsResponseInvalidResponseException(peer, BLOB_SIDECAR_UNKNOWN_PARENT); } if (!blobSidecarSummary.slot().isGreaterThan(maybeLastBlobSidecarSummary.get().slot())) { - throw new BlobSidecarsByRangeResponseInvalidResponseException( - peer, BLOB_SIDECAR_UNEXPECTED_SLOT); + throw new BlobSidecarsResponseInvalidResponseException(peer, BLOB_SIDECAR_UNEXPECTED_SLOT); } } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxy.java new file mode 100644 index 00000000000..c83bc9fe2e6 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxy.java @@ -0,0 +1,58 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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.networking.eth2.rpc.beaconchain.methods; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.p2p.peer.Peer; +import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; + +public class BlobSidecarsByRootListenerValidatingProxy implements RpcResponseListener { + + private final Peer peer; + private final Spec spec; + private final RpcResponseListener blobSidecarResponseListener; + private final Set expectedBlobIdentifiers; + private final KZG kzg; + + public BlobSidecarsByRootListenerValidatingProxy( + final Peer peer, + final Spec spec, + final RpcResponseListener blobSidecarResponseListener, + final KZG kzg, + final List expectedBlobIdentifiers) { + this.peer = peer; + this.spec = spec; + this.blobSidecarResponseListener = blobSidecarResponseListener; + this.kzg = kzg; + this.expectedBlobIdentifiers = new HashSet<>(expectedBlobIdentifiers); + } + + @Override + public SafeFuture onResponse(final BlobSidecar blobSidecar) { + return SafeFuture.of( + () -> { + final BlobSidecarByRootValidator blobSidecarByRootValidator = + new BlobSidecarByRootValidator(peer, spec, kzg, expectedBlobIdentifiers); + blobSidecarByRootValidator.validateBlobSidecar(blobSidecar); + return blobSidecarResponseListener.onResponse(blobSidecar); + }); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeResponseInvalidResponseException.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsResponseInvalidResponseException.java similarity index 82% rename from networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeResponseInvalidResponseException.java rename to networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsResponseInvalidResponseException.java index f633686eba9..3da9585e333 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeResponseInvalidResponseException.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsResponseInvalidResponseException.java @@ -16,17 +16,16 @@ import tech.pegasys.teku.networking.eth2.rpc.core.InvalidResponseException; import tech.pegasys.teku.networking.p2p.peer.Peer; -public class BlobSidecarsByRangeResponseInvalidResponseException extends InvalidResponseException { +public class BlobSidecarsResponseInvalidResponseException extends InvalidResponseException { - public BlobSidecarsByRangeResponseInvalidResponseException( + public BlobSidecarsResponseInvalidResponseException( Peer peer, InvalidResponseType invalidResponseType) { super( String.format( "Received invalid response from peer %s: %s", peer, invalidResponseType.describe())); } - public BlobSidecarsByRangeResponseInvalidResponseException( - InvalidResponseType invalidResponseType) { + public BlobSidecarsResponseInvalidResponseException(InvalidResponseType invalidResponseType) { super("Received invalid response: " + invalidResponseType.describe()); } @@ -36,7 +35,8 @@ public enum InvalidResponseType { BLOB_SIDECAR_UNEXPECTED_INDEX("BlobSidecar with unexpected index"), BLOB_SIDECAR_UNKNOWN_PARENT( "BlobSidecar parent blockRoot doesn't match previous blobSidecar blockRoot"), - BLOB_SIDECAR_UNEXPECTED_SLOT("BlobSidecar slot is from block with incorrect slot"); + BLOB_SIDECAR_UNEXPECTED_SLOT("BlobSidecar slot is from block with incorrect slot"), + BLOB_SIDECAR_UNEXPECTED_IDENTIFIER("BlobSidecar is not within requested identifiers"); private final String description; diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidatorTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidatorTest.java new file mode 100644 index 00000000000..e01faad9959 --- /dev/null +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarByRootValidatorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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.networking.eth2.rpc.beaconchain.methods; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Set; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.eth2.peers.Eth2Peer; +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.networking.libp2p.rpc.BlobIdentifier; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class BlobSidecarByRootValidatorTest { + private final Spec spec = TestSpecFactory.createMainnetDeneb(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private BlobSidecarByRootValidator validator; + private final Eth2Peer peer = mock(Eth2Peer.class); + private final KZG kzg = mock(KZG.class); + + @BeforeEach + void setUp() { + when(kzg.verifyBlobKzgProof(any(), any(), any())).thenReturn(true); + } + + @Test + void blobSidecarIsCorrect() { + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final BlobIdentifier blobIdentifier1 = new BlobIdentifier(blockRoot1, UInt64.ZERO); + final BlobSidecar blobSidecar1 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + + validator = new BlobSidecarByRootValidator(peer, spec, kzg, Set.of(blobIdentifier1)); + assertDoesNotThrow(() -> validator.validateBlobSidecar(blobSidecar1)); + } + + @Test + void blobSidecarIdentifierNotRequested() { + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final BlobIdentifier blobIdentifier2 = + new BlobIdentifier(dataStructureUtil.randomBytes32(), UInt64.ZERO); + final BlobSidecar blobSidecar1 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + + validator = new BlobSidecarByRootValidator(peer, spec, kzg, Set.of(blobIdentifier2)); + assertThatThrownBy(() -> validator.validateBlobSidecar(blobSidecar1)) + .isExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class) + .hasMessageContaining( + BlobSidecarsResponseInvalidResponseException.InvalidResponseType + .BLOB_SIDECAR_UNEXPECTED_IDENTIFIER + .describe()); + } + + @Test + void blobSidecarFailsKzgVerification() { + when(kzg.verifyBlobKzgProof(any(), any(), any())).thenReturn(false); + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final BlobIdentifier blobIdentifier1 = new BlobIdentifier(blockRoot1, UInt64.ZERO); + final BlobSidecar blobSidecar1 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + + validator = new BlobSidecarByRootValidator(peer, spec, kzg, Set.of(blobIdentifier1)); + assertThatThrownBy(() -> validator.validateBlobSidecar(blobSidecar1)) + .isExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class) + .hasMessageContaining( + BlobSidecarsResponseInvalidResponseException.InvalidResponseType + .BLOB_SIDECAR_KZG_VERIFICATION_FAILED + .describe()); + } +} diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxyTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxyTest.java index af7afcbc57f..961f0c062a9 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxyTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeListenerValidatingProxyTest.java @@ -66,10 +66,10 @@ void blobSidecarFailsKzgVerification() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar1); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_KZG_VERIFICATION_FAILED .describe()); } @@ -89,10 +89,10 @@ void blobSidecarSlotSmallerThanFromSlot() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar0); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_SLOT_NOT_IN_RANGE .describe()); } @@ -161,10 +161,10 @@ void blobSidecarSlotGreaterThanToSlot() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar5); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_SLOT_NOT_IN_RANGE .describe()); } @@ -190,10 +190,10 @@ void blobSidecarParentRootDoesNotMatch() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar2); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNKNOWN_PARENT .describe()); } @@ -238,10 +238,10 @@ void blobSidecarIndexIsGreaterOrEqualThanMaxBlobs() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar7); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_INDEX .describe()); } @@ -270,10 +270,10 @@ void blobSidecarIndexIsInTheSameBlockButNotNext() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar3); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_INDEX .describe()); } @@ -293,10 +293,10 @@ void firstBlobSidecarIndexIsINotZero() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar1); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_INDEX .describe()); } @@ -321,10 +321,10 @@ void firstBlobSidecarIndexInNextBlockIsNotZero() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar2); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_INDEX .describe()); } @@ -353,10 +353,10 @@ void missedBlobSidecarIndex() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar4); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_INDEX .describe()); } @@ -381,10 +381,10 @@ void blobSidecarUnexpectedSlot() { final SafeFuture result = listenerWrapper.onResponse(blobSidecar2); assertThat(result).isCompletedExceptionally(); assertThatThrownBy(result::get) - .hasCauseExactlyInstanceOf(BlobSidecarsByRangeResponseInvalidResponseException.class); + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); assertThatThrownBy(result::get) .hasMessageContaining( - BlobSidecarsByRangeResponseInvalidResponseException.InvalidResponseType + BlobSidecarsResponseInvalidResponseException.InvalidResponseType .BLOB_SIDECAR_UNEXPECTED_SLOT .describe()); } diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxyTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxyTest.java new file mode 100644 index 00000000000..b7dbf209845 --- /dev/null +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRootListenerValidatingProxyTest.java @@ -0,0 +1,142 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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.networking.eth2.rpc.beaconchain.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.eth2.peers.Eth2Peer; +import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; +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.networking.libp2p.rpc.BlobIdentifier; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class BlobSidecarsByRootListenerValidatingProxyTest { + private final Spec spec = TestSpecFactory.createMainnetDeneb(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private BlobSidecarsByRootListenerValidatingProxy listenerWrapper; + private final Eth2Peer peer = mock(Eth2Peer.class); + private final KZG kzg = mock(KZG.class); + + @SuppressWarnings("unchecked") + private final RpcResponseListener listener = mock(RpcResponseListener.class); + + @BeforeEach + void setUp() { + when(listener.onResponse(any())).thenReturn(SafeFuture.completedFuture(null)); + when(kzg.verifyBlobKzgProof(any(), any(), any())).thenReturn(true); + } + + @Test + void blobSidecarsAreCorrect() { + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final Bytes32 blockRoot2 = dataStructureUtil.randomBytes32(); + final Bytes32 blockRoot3 = dataStructureUtil.randomBytes32(); + final Bytes32 blockRoot4 = dataStructureUtil.randomBytes32(); + final List blobIdentifiers = + List.of( + new BlobIdentifier(blockRoot1, UInt64.ZERO), + new BlobIdentifier(blockRoot1, UInt64.ONE), + new BlobIdentifier(blockRoot2, UInt64.ZERO), + new BlobIdentifier(blockRoot2, UInt64.ONE), // will be missed, shouldn't be fatal + new BlobIdentifier(blockRoot3, UInt64.ZERO), + new BlobIdentifier(blockRoot4, UInt64.ZERO)); + listenerWrapper = + new BlobSidecarsByRootListenerValidatingProxy(peer, spec, listener, kzg, blobIdentifiers); + + final BlobSidecar blobSidecar10 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + final BlobSidecar blobSidecar11 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ONE); + final BlobSidecar blobSidecar2 = + dataStructureUtil.randomBlobSidecar(UInt64.valueOf(2), blockRoot2, blockRoot1, UInt64.ZERO); + final BlobSidecar blobSidecar3 = + dataStructureUtil.randomBlobSidecar(UInt64.valueOf(3), blockRoot3, blockRoot2, UInt64.ZERO); + final BlobSidecar blobSidecar4 = + dataStructureUtil.randomBlobSidecar(UInt64.valueOf(4), blockRoot4, blockRoot3, UInt64.ZERO); + + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar10).join()); + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar11).join()); + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar2).join()); + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar3).join()); + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar4).join()); + } + + @Test + void blobSidecarIdentifierNotRequested() { + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final Bytes32 blockRoot2 = dataStructureUtil.randomBytes32(); + final List blobIdentifiers = + List.of( + new BlobIdentifier(blockRoot1, UInt64.ZERO), + new BlobIdentifier(blockRoot1, UInt64.ONE)); + listenerWrapper = + new BlobSidecarsByRootListenerValidatingProxy(peer, spec, listener, kzg, blobIdentifiers); + + final BlobSidecar blobSidecar10 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + final BlobSidecar blobSidecar11 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ONE); + final BlobSidecar blobSidecar2 = + dataStructureUtil.randomBlobSidecar(UInt64.valueOf(2), blockRoot2, blockRoot1, UInt64.ZERO); + + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar10).join()); + assertDoesNotThrow(() -> listenerWrapper.onResponse(blobSidecar11).join()); + final SafeFuture result = listenerWrapper.onResponse(blobSidecar2); + assertThat(result).isCompletedExceptionally(); + assertThatThrownBy(result::get) + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); + assertThatThrownBy(result::get) + .hasMessageContaining( + BlobSidecarsResponseInvalidResponseException.InvalidResponseType + .BLOB_SIDECAR_UNEXPECTED_IDENTIFIER + .describe()); + } + + @Test + void blobSidecarFailsKzgVerification() { + when(kzg.verifyBlobKzgProof(any(), any(), any())).thenReturn(false); + final Bytes32 blockRoot1 = dataStructureUtil.randomBytes32(); + final BlobIdentifier blobIdentifier = new BlobIdentifier(blockRoot1, UInt64.ZERO); + listenerWrapper = + new BlobSidecarsByRootListenerValidatingProxy( + peer, spec, listener, kzg, List.of(blobIdentifier)); + + final BlobSidecar blobSidecar1 = + dataStructureUtil.randomBlobSidecar(UInt64.ONE, blockRoot1, Bytes32.ZERO, UInt64.ZERO); + + final SafeFuture result = listenerWrapper.onResponse(blobSidecar1); + assertThat(result).isCompletedExceptionally(); + assertThatThrownBy(result::get) + .hasCauseExactlyInstanceOf(BlobSidecarsResponseInvalidResponseException.class); + assertThatThrownBy(result::get) + .hasMessageContaining( + BlobSidecarsResponseInvalidResponseException.InvalidResponseType + .BLOB_SIDECAR_KZG_VERIFICATION_FAILED + .describe()); + } +}