From 4292c45629188031f617dc099bf1dfaf6c2fc973 Mon Sep 17 00:00:00 2001 From: Justin Traglia <95511699+jtraglia@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:26:32 -0600 Subject: [PATCH] Use 16-bit random value in validator filter (#8916) --------- Signed-off-by: Paul Harris Co-authored-by: Paul Harris Co-authored-by: Mehdi AOUADI Co-authored-by: Enrico Del Fante --- .../helpers/BeaconStateAccessorsElectra.java | 48 ++++++++++++++++ .../electra/helpers/MiscHelpersElectra.java | 52 +++++++++++++++--- .../helpers/MiscHelpersElectraTest.java | 2 +- .../tech/pegasys/teku/fuzz/FuzzUtilTest.java | 35 ++++++------ .../attestation.ssz_snappy | Bin 150 -> 150 bytes .../one_basic_attestation/post.ssz_snappy | Bin 6813 -> 6497 bytes .../one_basic_attestation/pre.ssz_snappy | Bin 6789 -> 6473 bytes 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java index 10685789ef5..f1b29f6eb22 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java @@ -14,13 +14,24 @@ package tech.pegasys.teku.spec.logic.versions.electra.helpers; import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.infrastructure.crypto.Hash.getSha256Instance; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.bytesToUInt64; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.uint64ToBytes; +import static tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra.MAX_RANDOM_VALUE; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.crypto.Sha256; +import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.constants.Domain; +import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; @@ -105,4 +116,41 @@ public static BeaconStateAccessorsElectra required( public IntList getNextSyncCommitteeIndices(final BeaconState state) { return getNextSyncCommitteeIndices(state, configElectra.getMaxEffectiveBalanceElectra()); } + + @Override + protected IntList getNextSyncCommitteeIndices( + final BeaconState state, final UInt64 maxEffectiveBalance) { + final UInt64 epoch = getCurrentEpoch(state).plus(1); + final IntList activeValidatorIndices = getActiveValidatorIndices(state, epoch); + final int activeValidatorCount = activeValidatorIndices.size(); + checkArgument(activeValidatorCount > 0, "Provided state has no active validators"); + + final Bytes32 seed = getSeed(state, epoch, Domain.SYNC_COMMITTEE); + final SszList validators = state.getValidators(); + final IntList syncCommitteeIndices = new IntArrayList(); + final int syncCommitteeSize = configElectra.getSyncCommitteeSize(); + final Sha256 sha256 = getSha256Instance(); + + int i = 0; + Bytes randomBytes = null; + while (syncCommitteeIndices.size() < syncCommitteeSize) { + if (i % 16 == 0) { + randomBytes = Bytes.wrap(sha256.digest(seed, uint64ToBytes(Math.floorDiv(i, 16L)))); + } + final int shuffledIndex = + miscHelpers.computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed); + final int candidateIndex = activeValidatorIndices.getInt(shuffledIndex); + final int offset = (i % 16) * 2; + final UInt64 randomValue = bytesToUInt64(randomBytes.slice(offset, 2)); + final UInt64 effectiveBalance = validators.get(candidateIndex).getEffectiveBalance(); + if (effectiveBalance + .times(MAX_RANDOM_VALUE) + .isGreaterThanOrEqualTo(maxEffectiveBalance.times(randomValue))) { + syncCommitteeIndices.add(candidateIndex); + } + i++; + } + + return syncCommitteeIndices; + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index e5c8869e9ad..387f1ecacb7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -13,13 +13,19 @@ package tech.pegasys.teku.spec.logic.versions.electra.helpers; +import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.infrastructure.crypto.Hash.getSha256Instance; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.bytesToUInt64; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.uint64ToBytes; import it.unimi.dsi.fastutil.ints.IntList; import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.crypto.Sha256; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.config.SpecConfigElectra; @@ -33,6 +39,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; public class MiscHelpersElectra extends MiscHelpersDeneb { + public static final UInt64 MAX_RANDOM_VALUE = UInt64.valueOf(65535); private final SpecConfigElectra specConfigElectra; private final PredicatesElectra predicatesElectra; @@ -68,6 +75,44 @@ public int computeProposerIndex( SpecConfigElectra.required(specConfig).getMaxEffectiveBalanceElectra()); } + @Override + protected int computeProposerIndex( + final BeaconState state, + final IntList indices, + final Bytes32 seed, + final UInt64 maxEffectiveBalance) { + checkArgument(!indices.isEmpty(), "compute_proposer_index indices must not be empty"); + + final Sha256 sha256 = getSha256Instance(); + + int i = 0; + final int total = indices.size(); + Bytes randomBytes = null; + while (true) { + final int candidateIndex = indices.getInt(computeShuffledIndex(i % total, total, seed)); + if (i % 16 == 0) { + randomBytes = Bytes.wrap(sha256.digest(seed, uint64ToBytes(Math.floorDiv(i, 16L)))); + } + final int offset = (i % 16) * 2; + final UInt64 randomValue = bytesToUInt64(randomBytes.slice(offset, 2)); + final UInt64 validatorEffectiveBalance = + state.getValidators().get(candidateIndex).getEffectiveBalance(); + if (validatorEffectiveBalance + .times(MAX_RANDOM_VALUE) + .isGreaterThanOrEqualTo(maxEffectiveBalance.times(randomValue))) { + return candidateIndex; + } + i++; + } + } + + @Override + public UInt64 getMaxEffectiveBalance(final Validator validator) { + return predicatesElectra.hasCompoundingWithdrawalCredential(validator) + ? specConfigElectra.getMaxEffectiveBalanceElectra() + : specConfigElectra.getMinActivationBalance(); + } + @Override public Validator getValidatorFromDeposit( final BLSPublicKey pubkey, final Bytes32 withdrawalCredentials, final UInt64 amount) { @@ -91,13 +136,6 @@ public Validator getValidatorFromDeposit( return validator.withEffectiveBalance(validatorEffectiveBalance); } - @Override - public UInt64 getMaxEffectiveBalance(final Validator validator) { - return predicatesElectra.hasCompoundingWithdrawalCredential(validator) - ? specConfigElectra.getMaxEffectiveBalanceElectra() - : specConfigElectra.getMinActivationBalance(); - } - @Override public boolean isFormerDepositMechanismDisabled(final BeaconState state) { // if the next deposit to be processed by Eth1Data poll has the index of the first deposit diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java index 86acdd78d47..a88afadbc15 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java @@ -129,7 +129,7 @@ void consolidatedValidatorsMoreLikelyToPropose() { proposerIndexCount++; } } - assertThat(proposerIndexCount).isEqualTo(4); + assertThat(proposerIndexCount).isEqualTo(5); } private BeaconState randomStateWithConsolidatedValidator(final int consolidationAmount) { diff --git a/fuzz/src/test/java/tech/pegasys/teku/fuzz/FuzzUtilTest.java b/fuzz/src/test/java/tech/pegasys/teku/fuzz/FuzzUtilTest.java index 796c56e94fd..3e69f57fc11 100644 --- a/fuzz/src/test/java/tech/pegasys/teku/fuzz/FuzzUtilTest.java +++ b/fuzz/src/test/java/tech/pegasys/teku/fuzz/FuzzUtilTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.core.JsonProcessingException; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -22,7 +23,6 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.junit.BouncyCastleExtension; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.xerial.snappy.Snappy; @@ -39,6 +39,8 @@ import tech.pegasys.teku.fuzz.input.SyncAggregateFuzzInput; import tech.pegasys.teku.fuzz.input.VoluntaryExitFuzzInput; import tech.pegasys.teku.fuzz.input.WithdrawalRequestFuzzInput; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; import tech.pegasys.teku.infrastructure.ssz.SszData; import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; import tech.pegasys.teku.spec.Spec; @@ -63,6 +65,7 @@ import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; @@ -92,7 +95,8 @@ class FuzzUtilTest { // https://github.com/ethereum/consensus-specs/tree/dev/tests/generators @Test - public void fuzzAttestation_minimal() { + @SuppressWarnings("unchecked") + public void fuzzAttestation_minimal() throws JsonProcessingException { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); final Path testCaseDir = @@ -102,21 +106,24 @@ public void fuzzAttestation_minimal() { testCaseDir.resolve("attestation.ssz_snappy"), spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions().getAttestationSchema()); final BeaconState preState = loadSsz(testCaseDir.resolve("pre.ssz_snappy"), beaconStateSchema); - final BeaconState postState = + final BeaconStateElectra postState = loadSsz(testCaseDir.resolve("post.ssz_snappy"), beaconStateSchema); - AttestationFuzzInput input = new AttestationFuzzInput(spec, preState, data); - byte[] rawInput = input.sszSerialize().toArrayUnsafe(); - Optional result = fuzzUtil.fuzzAttestation(rawInput).map(Bytes::wrap); + final AttestationFuzzInput input = new AttestationFuzzInput(spec, preState, data); + final byte[] rawInput = input.sszSerialize().toArrayUnsafe(); + final Optional result = fuzzUtil.fuzzAttestation(rawInput).map(Bytes::wrap); - Bytes expected = postState.sszSerialize(); assertThat(result).isNotEmpty(); - assertThat(result.get()).isEqualTo(expected); + final BeaconStateElectra resultState = + BeaconStateElectra.required(spec.deserializeBeaconState(result.get())); + DeserializableTypeDefinition t = + (DeserializableTypeDefinition) + resultState.getSchema().getJsonTypeDefinition(); + assertThat(JsonUtil.prettySerialize(resultState, t)) + .isEqualTo(JsonUtil.prettySerialize(postState, t)); } @Test - // TODO: re-enable when we merge #8916 - @Disabled("requires Use 16-bit random value in validator filter #8916") public void fuzzAttesterSlashing_minimal() { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); @@ -142,8 +149,6 @@ public void fuzzAttesterSlashing_minimal() { } @Test - // TODO: re-enable when we merge #8916 - @Disabled("requires Use 16-bit random value in validator filter #8916") public void fuzzBlock_minimal() { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); @@ -171,8 +176,6 @@ public void fuzzBlock_minimal() { } @Test - // TODO: re-enable when we merge #8916 - @Disabled("requires Use 16-bit random value in validator filter #8916") public void fuzzBlockHeader_minimal() { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); @@ -213,8 +216,6 @@ public void fuzzDeposit_minimal() { } @Test - // TODO: re-enable when we merge #8916 - @Disabled("requires Use 16-bit random value in validator filter #8916") public void fuzzProposerSlashing_minimal() { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); @@ -385,8 +386,6 @@ public void fuzzWithdrawalRequest_minimal() { assertThat(result.get()).isEqualTo(expected); } - // TODO fix as part of https://github.com/Consensys/teku/pull/8876 - @Disabled("Disabling until we have a fix for this") @Test public void fuzzConsolidationRequest_minimal() { final FuzzUtil fuzzUtil = new FuzzUtil(false, true); diff --git a/fuzz/src/test/resources/minimal/operations/attestation/pyspec_tests/one_basic_attestation/attestation.ssz_snappy b/fuzz/src/test/resources/minimal/operations/attestation/pyspec_tests/one_basic_attestation/attestation.ssz_snappy index 32c9853830a3cc5f25f45110f8a0b680c74aa5b6..4bc6673add4e792dc41db7cda9a68ef09df0f189 100644 GIT binary patch literal 150 zcmV;H0BQf`0R-g$MgahP#+8qNt_v?CCT+;b%7lG0Zg`dza;9=@d6G9evMjUlMlt|) z0RVnb0PtaeD3Hg6A>5EI$6H6!_vY}rrMTXA<}22T&xS_<%D2@O+kUhv&mgWW1!Kzg zbyYA8sIe@srT%DfHIDSvLx~Z(Slm_4E}$;H`UGAwyFVvuM#A@uS6WP9$5->a>B+6#s5}*WKI4`hM-n+FG^=^8=ANRKg6@09s}?8)ZnCFkAuyoG{yRR~pMq z3}ziWZnOcGb&gKLSY}C!sZ!?*>>&d*B)%fDj6l?F>89YOv-4|ba{o9v_xC!#d(S>K zzPe@Ip4srukEF~UF?IY!-$O-PO?Txq+i)(ciPzHF8~W>qJ(>HzEj3f)|5&?jR}Za> z9PK*QwNWq`{{2{kC>Z!SCv>gN>+TahVKmnb504&ieDZ+3U0oln-|uKGSr=MyX$GM| zI2m~LlSeX25FcSTBLsCk4D%WcRbmVd7K?fc%6zVE!2uK>goC=}?5V)D{K?)0>sMga zVz|j*=-^|<(=im&80Lgn;E8VF#YaG;$1tP7kgLMbEW=WNEWZ6h;5W;$ zuiFtnz8U((IM+Y+hx;wd{bpLJ8in!1UBALKS#&rSdau^zrUsxxfYXCS?avAdEJ_S# zh}?G~3`QY_7;!VP7+z3dwwbno??)o?|MS2vxJ?XWSQUDnOsYPhxO+1$J@t6Zkh zPj~n4n_Bo#Q)lojJaBvUzg#Rk`QyZ*w7ayY0LqxPP|hf_BUnJQTP@>a*~>2>v?!r0 zxyVK#YK@$ZL!Dq11enL9Xp0?5y44hYu0Ha!h=WDOC{n#YIl;_-6Wq&iP{|t8>E6>3 zuY`m1tX+VD(N#hYDuhN}d>gpZFt~(d>yLge=Ab}iPj1%>FoZ;e=h+k~A-r^P3T!3d zwPLdN$07~|2PecvIyNZf;O^4<8-%8kq@c~DkLM*Ru`wRLbX9VlN(%o=zN=44SYbk+ zA_KY*b%8?W6selj4<2QA1_v}8#AGSrPNK11ZsfFrAaC?(JqOp5uIlq2pGE3Idc$j)xts)poD5=~8`_LB~!A!7+{cie}WOG@+Vd7y@ z@yq!k{jn2A1&f`V_I+O;ofkj#>ui?heM#^yhUAkACimsHAH1e}#xFnFo6-IF>9we; zhENQ<4wa3G-#P>g)a!0!&PJF zey)wK%4aO-arl5q0S{Hh2q?IX@NGT?W)1~=-Caz5VhTDuC?MDrB*-ZE5dAlxTnfcZ zESJhbl)s`{kLvFsWiiS@JdxG$1McsR;y1@7J)bx-l!+rFQ|XhBK2Kp*W-MG7Pns{M zCc<+IyrNEbswVPESc+HNy?CLt6}G~0mM`S9xUYO{h7QVDKK#Ji`H|MJ+I!XOY7B1@ zdiagCmaWdkJewtqzxG2oa^gvls}R(pMz*=&S>!E^(5ysPqo|!A38F{!)bbC9hRtke zd>Iv5*%~h@|K*99TOI+a=fiTZ43(_$q~VB%lW!VbYl#QRl@-ZLI0eT0O&Fhshpw{> zB*5l4({PkVaY*6%)*U^SW~HsWdNj*cKEtD-h(mJ3m+Iy<&-WXfg*1$EG#s(zxFR-a z(DkgDEloH0%EXCo2lDXvN6?s>=va==3GIxPtxv=~<0`%a(+c6iH~#t2VH z_KsuyjbUBANr7e@f)1Qnc1zvYamKN^BGTxC#YSH&L?*bEzMY2l@7B?9 zN*18fVv^uB&;6GO?Ej@BFk<`QQE!EONyTujFcsX9rG*^0oT*#%J>3>+o|y|9vN;sZ cWrqSb%|7@zJc8s-?8!>dF)(#JmdU{W1G_iQ{{R30 diff --git a/fuzz/src/test/resources/minimal/operations/attestation/pyspec_tests/one_basic_attestation/pre.ssz_snappy b/fuzz/src/test/resources/minimal/operations/attestation/pyspec_tests/one_basic_attestation/pre.ssz_snappy index 7e2480031de461b2849a0544b52afe5e90de5a62..4a116a875d1458db49c4c1a79f8f53cc8907b521 100644 GIT binary patch delta 1025 zcmX9+du&rx7(chY>+bD?zF&Kl_13a2Fb{~#p&^Vw4WMOavr&d5Q$Q0K%n7p{ccrn^ z#9-F3&y6;cWu4XrQ43A9)Xh?iTWEp`d+tO7*rjvQv{rBa&zt{IWH}Lf6 zs`jgWrh2b#{^eYG=e^QruRsJ&w#`u9(}^7egSX{RRsb@tw@zq}%T zxc5ZwM#)s{{*h*x5c#w)dZ{z$8=jo_e8(n^G7NHS1 z9(m=nhbl^v8f7;l1Pvk#a|R4`att08%lb>ILf&n{FiMTU0n;+}MC4NO)WE#+8*myi zTxT$Ji!tN(1fxDG)ehyk7|Ll3kEB@QPplWEMnPxBFss2(q{Gmr!pd+mwf#)w566*j zyO21w8HVJzC_MhB?`_B3Hd?D2gNett!y4OE*}-J={kL2`Y8bjDI9DKQf7MXn&|)}A zjHju};m2lXdy}EPcOJ5KCzE|j90++QliZs%FI2k+aF$(%Z9-O*yak9q(m3mJm$ZI4> zT)%iK`Pm*Uj<-lbc0+bsM8;}E_Kp<9?izT#I6z`<^`HCqv4KMU(sToA+MIe=4p#(VyA-=*gAn znx-%ey9(9KN#8gmIJsDf^Y>sF*u7bpP_Q6e#X_7L3kgE>7O{{aXJJsrO3m~%K3X&J z)%E(AnnHY0Pr&;;7DT8kR>Hz9gl~yiFbi1Nzg1N7a5xGbuf zNSDh2#9wi3ME!S=GmSV%C#p7K$o=hc;>M)B?;}U1I%#a|LB{mMPg8l-nTzKplNT!4 zsffHHujo_V!BYigr0Ruq&z`Am#qDs6^o2rF_`<`-^Pz(D5eL>UjJ1c?->F?+XLyy; z!>{Bzz9tXzY?b4G?t=)F#FJuHA*e%xYj(NC^Ft{!NeTgcm2ym z27G~&Mj#q1V5A$`cl1@8)wbT+@oZb=tcZgW0VAL>X55MP zVC%m=uuc+45}jk#?^knBAYqidYh!j6*ec)ouQ7D!@t|q?6IoV9P8jMLl{kksA$5(^ zY1D^e=eb}ya%ghFQdAw5GB4AHsO@1XCZpYt!$N(2Q!h39Ox4eH6EM?aSm?Xvjc)Yvr~?4lWw>gyYGeOKG=&Zxi0_2=Monjwj<*Q^~X4(4gC zP+4{P+@aeF|CTO>g9f>CkVteDSLFWg1t2w}Bvx1saf^&Fg3 z2Ds@kdB|GmQXUQ6e=#e6$@f%8n0a