Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@
<includes>
<include>**/*.sol</include>
</includes>
<excludes>
<exclude>lib/ptc/CommitteePtcVerifier.sol</exclude>
</excludes>
</soliditySourceFiles>
<contract>
<includes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,14 @@ public ConsensusState readConsensusState(BigInteger slot) {
);
}

var beaconBlock = this.acbEthClient.getBeaconBlockBySlot(slot.add(BigInteger.ONE));
if (ObjectUtil.isNull(beaconBlock)) {
throw new RuntimeException("get a null result for next beacon block by slot: " + slot.add(BigInteger.ONE));
}
if (beaconBlock.getBody().getOptionalSyncAggregate().isEmpty()) {
throw new RuntimeException("has no sync aggregate in beacon block by slot " + slot.add(BigInteger.ONE));
if (beaconBlockWithSyncAggregate.getBody().getOptionalSyncAggregate().isEmpty()) {
throw new RuntimeException("has no sync aggregate in beacon block by slot " + beaconBlockWithSyncAggregate.getSlot());
}

var ethConsensusEndorsements = new EthConsensusEndorsements(beaconBlock.getBody().getOptionalSyncAggregate().get());
var ethConsensusEndorsements = new EthConsensusEndorsements(
beaconBlockWithSyncAggregate.getBody().getOptionalSyncAggregate().get(),
beaconBlockWithSyncAggregate.getSlot()
);

return new ConsensusState(
slot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ public VerifyResult verifyAnchorConsensusState(IBlockchainTrustAnchor bta, Conse
getHCDVSLogger().info("successful to verify anchor consensus state ⚓️ (slot: {}, hash: {}) for domain {} now!",
anchorState.getHeight().toString(), anchorState.getHashHex(), bta.getDomain().toString());
if (ethConsensusStateData.getLightClientUpdateWrapper() != null) {
getHCDVSLogger().info("light client update inside anchor consensus state, update the sync committee");
ethSubjectIdentity.setCurrentSyncCommittee(ethConsensusStateData.getLightClientUpdateWrapper().getNextSyncCommittee());
ethSubjectIdentity.setNextSyncCommittee(ethConsensusStateData.getLightClientUpdateWrapper().getNextSyncCommittee());
}
if (ethConsensusStateData.isLastSlotForCurrentPeriod(ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength())
&& ObjectUtil.isNotNull(ethSubjectIdentity.getNextSyncCommittee())) {
getHCDVSLogger().info("light client update inside last-slot anchor consensus state, switch current sync committee");
ethSubjectIdentity.setCurrentSyncCommittee(ethSubjectIdentity.getNextSyncCommittee());
ethSubjectIdentity.setNextSyncCommittee(null);
}
anchorState.setConsensusNodeInfo(ethSubjectIdentity.toJson().getBytes());
return VerifyResult.success();
Expand Down Expand Up @@ -123,8 +128,55 @@ public VerifyResult verifyConsensusState(ConsensusState stateToVerify, Consensus
new String(stateToVerify.getEndorsements()),
ethSubjectIdentity.getCurrentSyncCommittee().getPubkeys().size()
);
var syncPeriodLength = ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength();
var currPeriod = ethConsensusStateData.getCurrSyncPeriod(syncPeriodLength);
var signatureSlot = ethEndorsements.getSignatureSlotOrDefault(currSlot.increment());
var signaturePeriod = signatureSlot.dividedBy(syncPeriodLength);
var shouldRotateCommittee = ethConsensusStateData.isLastSlotForCurrentPeriod(syncPeriodLength);

if (shouldRotateCommittee) {
if (ethConsensusStateData.getLightClientUpdateWrapper() == null) {
getHCDVSLogger().error("❌ has none light client update for the last slot {} for current period {}",
ethConsensusStateData.getBeaconBlockHeader().getSlot().toString(),
currPeriod
);
return VerifyResult.fail("none light client update at last slot in period");
}
try {
ethConsensusStateData.validateLightClientUpdate(
ethSubjectIdentity.getCurrentSyncCommittee(),
ethSubjectIdentity.getEth2ChainConfig()
);
} catch (InvalidConsensusDataException e) {
getHCDVSLogger().error("❌ failed to verify light client update at last slot {}",
stateToVerify.getHeight().toString(), e);
return VerifyResult.fail("failed to verify light client update: {}", e.getMessage());
}
ethSubjectIdentity.setNextSyncCommittee(ethConsensusStateData.getLightClientUpdateWrapper().getNextSyncCommittee());
}

var committeeForBlockVerification = ethSubjectIdentity.getCurrentSyncCommittee();
if (signaturePeriod.equals(currPeriod)) {
committeeForBlockVerification = ethSubjectIdentity.getCurrentSyncCommittee();
} else if (signaturePeriod.equals(currPeriod.increment())) {
if (ObjectUtil.isNull(ethSubjectIdentity.getNextSyncCommittee())) {
getHCDVSLogger().error("❌ missing next sync committee for slot {}, while endorsements come from signature slot {}",
stateToVerify.getHeight().toString(), signatureSlot.toString());
return VerifyResult.fail("missing next sync committee for endorsements");
}
committeeForBlockVerification = ethSubjectIdentity.getNextSyncCommittee();
} else {
getHCDVSLogger().error("❌ unexpected endorsements signature slot {} for state slot {}",
signatureSlot.toString(), stateToVerify.getHeight().toString());
return VerifyResult.fail("unexpected endorsements signature slot");
}

try {
ethConsensusStateData.validate(ethSubjectIdentity.getCurrentSyncCommittee(), ethEndorsements, ethSubjectIdentity.getEth2ChainConfig());
ethConsensusStateData.validateBlock(
committeeForBlockVerification,
ethEndorsements,
ethSubjectIdentity.getEth2ChainConfig()
);
} catch (InvalidConsensusDataException e) {
getHCDVSLogger().error("❌ failed to verify eth consensus state data (slot: {}, hash: {})",
stateToVerify.getHeight().toString(), stateToVerify.getHashHex(), e);
Expand All @@ -133,18 +185,12 @@ public VerifyResult verifyConsensusState(ConsensusState stateToVerify, Consensus
}

if (ethConsensusStateData.isLastSlotForCurrentPeriod(ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength())) {
if (ethConsensusStateData.getLightClientUpdateWrapper() == null) {
getHCDVSLogger().error("❌ has none light client update for the last slot {} for current period {}",
ethConsensusStateData.getBeaconBlockHeader().getSlot().toString(),
ethConsensusStateData.getCurrSyncPeriod(ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength())
);
return VerifyResult.fail("none light client update at last slot in period");
}
getHCDVSLogger().info("🗳️ last slot {} for current period {}, update the sync committee",
ethConsensusStateData.getBeaconBlockHeader().getSlot().toString(),
ethConsensusStateData.getCurrSyncPeriod(ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength())
);
ethSubjectIdentity.setCurrentSyncCommittee(ethConsensusStateData.getLightClientUpdateWrapper().getNextSyncCommittee());
ethSubjectIdentity.setCurrentSyncCommittee(ethSubjectIdentity.getNextSyncCommittee());
ethSubjectIdentity.setNextSyncCommittee(null);
}

stateToVerify.setConsensusNodeInfo(ethSubjectIdentity.toJson().getBytes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,10 @@ public EthConsensusStateData getEthConsensusStateData(BigInteger slot, String am
ethConsensusStateData.setAmContractHex(amContract);
// last slot for this period
var currPeriod = currentSyncCommitteePeriod(slot);
var currPeriodEndSlot = currPeriod.multiply(BigInteger.valueOf(config.getEth2ChainConfig().getSyncPeriodLength()));
var currPeriodEndSlot = currPeriod
.add(BigInteger.ONE)
.multiply(BigInteger.valueOf(config.getEth2ChainConfig().getSyncPeriodLength()))
.subtract(BigInteger.ONE);
if (currPeriodEndSlot.equals(slot)) {
// fetch the sync committee update
getBbcLogger().info("get light client update for next period: {}", currPeriod.add(BigInteger.ONE));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.alipay.antchain.bridge.plugins.ethereum2.core;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.*;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.infrastructure.json.JsonUtil;
import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate;
import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregateSchema;
Expand All @@ -16,7 +18,8 @@ public static EthConsensusEndorsements fromJson(String json, int syncCommitteeSi
try {
JSONObject jsonObject = JSONObject.parseObject(json);
return new EthConsensusEndorsements(
JsonUtil.parse(jsonObject.getString("sync_aggregate"), SyncAggregateSchema.create(syncCommitteeSize).getJsonTypeDefinition())
JsonUtil.parse(jsonObject.getString("sync_aggregate"), SyncAggregateSchema.create(syncCommitteeSize).getJsonTypeDefinition()),
jsonObject.containsKey("signature_slot") ? UInt64.valueOf(jsonObject.getString("signature_slot")) : null
);
} catch (Exception e) {
throw new RuntimeException("failed to parse EthConsensusEndorsements from json: ", e);
Expand All @@ -25,10 +28,23 @@ public static EthConsensusEndorsements fromJson(String json, int syncCommitteeSi

private SyncAggregate syncAggregate;

private UInt64 signatureSlot;

public EthConsensusEndorsements(SyncAggregate syncAggregate) {
this.syncAggregate = syncAggregate;
}

public UInt64 getSignatureSlotOrDefault(UInt64 fallback) {
return ObjectUtil.defaultIfNull(signatureSlot, fallback);
}

@SneakyThrows
public String toJson() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("sync_aggregate", JsonUtil.serialize(syncAggregate, syncAggregate.getSchema().getJsonTypeDefinition()));
if (ObjectUtil.isNotNull(signatureSlot)) {
jsonObject.put("signature_slot", signatureSlot.toString());
}
return jsonObject.toJSONString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public void setAmContractHex(String contractHex) {
}

public void validate(SyncCommittee currSyncCommittee, EthConsensusEndorsements endorsements, Eth2ChainConfig eth2ChainConfig) {
validateBlock(currSyncCommittee, endorsements, eth2ChainConfig);
validateLightClientUpdate(currSyncCommittee, eth2ChainConfig);
}

public void validateBlock(SyncCommittee currSyncCommittee, EthConsensusEndorsements endorsements, Eth2ChainConfig eth2ChainConfig) {
if (ObjectUtil.isNotNull(this.beaconBlockHeader)) {
var schemaDefinitions = eth2ChainConfig.getCurrentSchemaDefinitions(this.beaconBlockHeader.getSlot().bigIntegerValue());
var bodySchema = schemaDefinitions.getBlindedBeaconBlockBodySchema();
Expand Down Expand Up @@ -127,9 +132,10 @@ public void validate(SyncCommittee currSyncCommittee, EthConsensusEndorsements e
}
}

var signatureSlot = endorsements.getSignatureSlotOrDefault(this.beaconBlockHeader.getSlot().increment());
var signingRoot = new SigningData(
this.beaconBlockHeader.getRoot(),
Bytes32.wrap(eth2ChainConfig.getForkBySlot(this.beaconBlockHeader.getSlot().bigIntegerValue()).getDomain())
Bytes32.wrap(eth2ChainConfig.getForkBySlot(signatureSlot.bigIntegerValue()).getDomain())
).hashTreeRoot();

if (!BLSSignatureVerifier.SIMPLE.verify(
Expand All @@ -140,14 +146,16 @@ public void validate(SyncCommittee currSyncCommittee, EthConsensusEndorsements e
throw new InvalidConsensusDataException("sync committee signature is invalid");
}
}
}

public void validateLightClientUpdate(SyncCommittee currSyncCommittee, Eth2ChainConfig eth2ChainConfig) {
if (lightClientUpdateWrapper != null) {
validateLightClientUpdate(lightClientUpdateWrapper, currSyncCommittee, eth2ChainConfig);
validateLightClientUpdateInternal(lightClientUpdateWrapper, currSyncCommittee, eth2ChainConfig);
}
}

public boolean isLastSlotForCurrentPeriod(long syncPeriodLength) {
return this.beaconBlockHeader.getSlot().mod(syncPeriodLength).equals(UInt64.ZERO);
return this.beaconBlockHeader.getSlot().mod(syncPeriodLength).equals(UInt64.valueOf(syncPeriodLength - 1L));
}

public UInt64 getCurrSyncPeriod(long syncPeriodLength) {
Expand Down Expand Up @@ -177,7 +185,7 @@ public String toJson() {
return jsonObject.toJSONString();
}

private void validateLightClientUpdate(
private void validateLightClientUpdateInternal(
LightClientUpdateWrapper lightClientUpdate,
SyncCommittee currentSyncCommittee,
Eth2ChainConfig eth2ChainConfig
Expand Down Expand Up @@ -216,7 +224,7 @@ private void validateLightClientUpdate(
// verify sync committee signature
var signingRoot = new SigningData(
attestedHeader.hashTreeRoot(),
Bytes32.wrap(eth2ChainConfig.getForkBySlot(this.beaconBlockHeader.getSlot().bigIntegerValue()).getDomain())
Bytes32.wrap(eth2ChainConfig.getForkBySlot(lightClientUpdate.getSignatureSlot().bigIntegerValue()).getDomain())
).hashTreeRoot();
if (!BLSSignatureVerifier.SIMPLE.verify(
contributionPubkeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public static EthSubjectIdentity fromJson(String json) {
if (subjectId.getCurrentSyncCommittee().getPubkeys().size() != subjectId.getEth2ChainConfig().getSyncCommitteeSize()) {
throw new RuntimeException("sync committee size not match with chain config");
}
if (subjectId.getNextSyncCommittee() != null
&& subjectId.getNextSyncCommittee().getPubkeys().size() != subjectId.getEth2ChainConfig().getSyncCommitteeSize()) {
throw new RuntimeException("next sync committee size not match with chain config");
}
return subjectId;
}

Expand Down Expand Up @@ -73,9 +77,17 @@ public void write(JSONSerializer serializer, Object object, Object fieldName, Ty
@JSONField(name = "current_sync_committee", deserializeUsing = SyncCommitteeDeserializer.class, serializeUsing = SyncCommitteeSerializer.class)
private SyncCommittee currentSyncCommittee;

@JSONField(name = "next_sync_committee", deserializeUsing = SyncCommitteeDeserializer.class, serializeUsing = SyncCommitteeSerializer.class)
private SyncCommittee nextSyncCommittee;

@JSONField(name = "eth2_chain_config", deserializeUsing = Eth2ChainConfigDeserializer.class)
private Eth2ChainConfig eth2ChainConfig;

public EthSubjectIdentity(SyncCommittee currentSyncCommittee, Eth2ChainConfig eth2ChainConfig) {
this.currentSyncCommittee = currentSyncCommittee;
this.eth2ChainConfig = eth2ChainConfig;
}

public String toJson() {
return JSON.toJSONString(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,12 @@ public Map<String, String> getRemoteSpec() {
switch (resp.statusCode()) {
case 200 -> {
var body = JSON.parseObject(resp.body(), ObjectAndMetaData.class);
return JSON.parseObject(body.getData(), new TypeReference<>(){});
Map<String, String> remoteSpec = JSON.parseObject(body.getData(), new TypeReference<>(){});
remoteSpec.putIfAbsent("GOSSIP_MAX_SIZE", "10485760");
remoteSpec.putIfAbsent("MAX_CHUNK_SIZE", "10485760");
remoteSpec.putIfAbsent("TTFB_TIMEOUT", "5");
remoteSpec.putIfAbsent("RESP_TIMEOUT", "10");
return remoteSpec;
}
default -> throw new RuntimeException(StrUtil.format("failed to spec config: {}", resp.body()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ public void testReadConsensusState() {
period = currSlot.divide(BigInteger.valueOf(syncPeriodLength));
log.info("found period {}", period);

currSlot = period.multiply(BigInteger.valueOf(syncPeriodLength));
currSlot = period.add(BigInteger.ONE).multiply(BigInteger.valueOf(syncPeriodLength)).subtract(BigInteger.ONE);
cs = ethereumBBCService.readConsensusState(currSlot);

currBlock = ethereumBBCService.getAcbEthClient().getBeaconBlockBySlot(currSlot);
Expand All @@ -756,6 +756,7 @@ public void testReadConsensusState() {
Assert.assertNotNull(stateData.getBeaconBlockHeader());
Assert.assertNotNull(stateData.getExecutionPayloadBranches());
Assert.assertEquals(period, stateData.getCurrSyncPeriod(syncPeriodLength).bigIntegerValue());
Assert.assertTrue(stateData.isLastSlotForCurrentPeriod(syncPeriodLength));

endorsements = EthConsensusEndorsements.fromJson(new String(cs.getEndorsements()), specConfig.getSyncCommitteeSize());
Assert.assertNotNull(endorsements.getSyncAggregate());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorFactory;
import com.alipay.antchain.bridge.commons.core.bta.IBlockchainTrustAnchor;
import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
import com.alipay.antchain.bridge.plugins.ethereum2.core.EthConsensusStateData;
import com.alipay.antchain.bridge.plugins.ethereum2.core.EthSubjectIdentity;
import com.alipay.antchain.bridge.plugins.ethereum2.tools.EthBbcTools;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.slf4j.Logger;
import org.web3j.utils.Numeric;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -147,6 +149,24 @@ public void testHcdvsVerifyCrossChainMessage() {
assertTrue(result.isSuccess());
}

@Test
public void testPeriodTailBoundary() {
var chainConfig = BTA_SUBJECT_IDENTITY.getEth2ChainConfig();
var parentStateData = EthConsensusStateData.fromJson(
new String(PARENT_CS_WHERE_PERIOD_END.getStateData()),
chainConfig.getCurrentSchemaDefinitions(PARENT_CS_WHERE_PERIOD_END.getHeight()),
chainConfig.getSpecConfig()
);
var currStateData = EthConsensusStateData.fromJson(
new String(CS_WHERE_PERIOD_END.getStateData()),
chainConfig.getCurrentSchemaDefinitions(CS_WHERE_PERIOD_END.getHeight()),
chainConfig.getSpecConfig()
);

assertTrue(parentStateData.isLastSlotForCurrentPeriod(chainConfig.getSyncPeriodLength()));
assertFalse(currStateData.isLastSlotForCurrentPeriod(chainConfig.getSyncPeriodLength()));
}

@Test
public void testHcdvsParseMessageFromLedgerData() {
var raw = ETHEREUM_HCDVS_SERVICE.parseMessageFromLedgerData(MSG1.getProvableData().getLedgerData());
Expand Down