From 4fb038d8cf6d3517e74fcb6264de38e02b9b71a9 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Thu, 19 Dec 2024 12:26:16 +0100 Subject: [PATCH] optimize indexed attestation validation --- ...IndexedAttestationValidationBenchmark.java | 86 +++++++++++++++++++ .../logic/common/util/AttestationUtil.java | 33 ++++--- 2 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 eth-benchmark-tests/src/jmh/java/tech/pegasys/teku/benchmarks/IndexedAttestationValidationBenchmark.java diff --git a/eth-benchmark-tests/src/jmh/java/tech/pegasys/teku/benchmarks/IndexedAttestationValidationBenchmark.java b/eth-benchmark-tests/src/jmh/java/tech/pegasys/teku/benchmarks/IndexedAttestationValidationBenchmark.java new file mode 100644 index 00000000000..a222ba86261 --- /dev/null +++ b/eth-benchmark-tests/src/jmh/java/tech/pegasys/teku/benchmarks/IndexedAttestationValidationBenchmark.java @@ -0,0 +1,86 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * 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.benchmarks; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import tech.pegasys.teku.benchmarks.util.CustomRunner; +import tech.pegasys.teku.bls.BLSSignatureVerifier; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; +import tech.pegasys.teku.spec.datastructures.state.Fork; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.util.AsyncBLSSignatureVerifier; + +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 2, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class IndexedAttestationValidationBenchmark { + Spec spec; + BeaconState beaconState; + IndexedAttestation indexedAttestation; + Fork fork; + AsyncBLSSignatureVerifier asyncBLSSignatureVerifier; + + @Setup(Level.Trial) + public void init() throws Exception { + spec = TestSpecFactory.createMainnetDeneb(); + + final byte[] stateBytes = Files.readAllBytes(Path.of("/Users/tbenr/state.ssz")); + final byte[] indexedAttestationBytes = + Files.readAllBytes(Path.of("/Users/tbenr/attestation.ssz")); + + beaconState = spec.deserializeBeaconState(Bytes.of(stateBytes)); + indexedAttestation = + spec.atSlot(beaconState.getSlot()) + .getSchemaDefinitions() + .getIndexedAttestationSchema() + .sszDeserialize(Bytes.of(indexedAttestationBytes)); + + fork = spec.getForkSchedule().getFork(spec.computeEpochAtSlot(beaconState.getSlot())); + asyncBLSSignatureVerifier = AsyncBLSSignatureVerifier.wrap(BLSSignatureVerifier.NO_OP); + } + + @Benchmark + public void validateIndexedAttestation(Blackhole bh) { + bh.consume( + spec.atSlot(beaconState.getSlot()) + .getAttestationUtil() + .isValidIndexedAttestationAsync( + fork, beaconState, indexedAttestation, asyncBLSSignatureVerifier) + .join()); + } + + public static void main(String[] args) throws Exception { + IndexedAttestationValidationBenchmark benchmark = new IndexedAttestationValidationBenchmark(); + benchmark.init(); + new CustomRunner(2, 10000).withBench(benchmark::validateIndexedAttestation).run(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java index ecd8906aa7b..647406494c4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java @@ -16,9 +16,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture; -import com.google.common.collect.Comparators; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.Comparator; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; @@ -236,20 +235,28 @@ public SafeFuture isValidIndexedAttestationAsync( final AsyncBLSSignatureVerifier signatureVerifier) { final SszUInt64List indices = indexedAttestation.getAttestingIndices(); - if (indices.isEmpty() - || !Comparators.isInStrictOrder(indices.asListUnboxed(), Comparator.naturalOrder())) { + if (indices.isEmpty()) { return completedFuture( - AttestationProcessingResult.invalid("Attesting indices are not sorted")); + AttestationProcessingResult.invalid("Attesting indices must not be empty")); } - final List pubkeys = - indices - .streamUnboxed() - .flatMap(i -> beaconStateAccessors.getValidatorPubKey(state, i).stream()) - .toList(); - if (pubkeys.size() < indices.size()) { - return completedFuture( - AttestationProcessingResult.invalid("Attesting indices include non-existent validator")); + UInt64 lastIndex = null; + final List pubkeys = new ArrayList<>(indices.size()); + + for (final UInt64 index : indices.asListUnboxed()) { + if (lastIndex != null && index.isLessThanOrEqualTo(lastIndex)) { + return completedFuture( + AttestationProcessingResult.invalid("Attesting indices are not sorted")); + } + lastIndex = index; + final Optional validatorPubKey = + beaconStateAccessors.getValidatorPubKey(state, index); + if (validatorPubKey.isEmpty()) { + return completedFuture( + AttestationProcessingResult.invalid( + "Attesting indices include non-existent validator")); + } + pubkeys.add(validatorPubKey.get()); } return validateAttestationDataSignature(