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,19 @@ 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()
);
this.acbEthClient.populateLightClientUpdateIfRequired(
ethConsensusData,
slot,
beaconBlockWithSyncAggregate.getSlot().bigIntegerValue()
);

return new ConsensusState(
slot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.web3j.tx.Contract;
import org.web3j.utils.Numeric;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.datastructures.state.SyncCommittee;

@HeteroChainDataVerifierService(pluginId = "plugin-ethereum2", products = "ethereum2")
public class EthereumHcdvsService extends AbstractHCDVSService {
Expand Down Expand Up @@ -53,7 +54,7 @@ public VerifyResult verifyAnchorConsensusState(IBlockchainTrustAnchor bta, Conse
ethSubjectIdentity.getCurrentSyncCommittee().getPubkeys().size()
);
try {
ethConsensusStateData.validate(ethSubjectIdentity.getCurrentSyncCommittee(), ethEndorsements, ethSubjectIdentity.getEth2ChainConfig());
verifyAndUpdateSyncCommittee(ethSubjectIdentity, ethConsensusStateData, ethEndorsements);
} catch (InvalidConsensusDataException e) {
getHCDVSLogger().error("failed to verify eth consensus state data (slot: {}, hash: {}) for domain {}",
anchorState.getHeight().toString(), anchorState.getHashHex(), bta.getDomain().toString(), e);
Expand All @@ -62,10 +63,6 @@ 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());
}
anchorState.setConsensusNodeInfo(ethSubjectIdentity.toJson().getBytes());
return VerifyResult.success();
}
Expand Down Expand Up @@ -123,37 +120,137 @@ public VerifyResult verifyConsensusState(ConsensusState stateToVerify, Consensus
new String(stateToVerify.getEndorsements()),
ethSubjectIdentity.getCurrentSyncCommittee().getPubkeys().size()
);
var syncPeriodLength = ethSubjectIdentity.getEth2ChainConfig().getSyncPeriodLength();
if (ObjectUtil.isNull(ethSubjectIdentity.getCurrentSyncCommitteePeriod())) {
var parentPeriod = parentConsensusData.getCurrSyncPeriod(syncPeriodLength).bigIntegerValue();
if (parentConsensusData.isLastSlotForCurrentPeriod(syncPeriodLength)
&& ObjectUtil.isNull(ethSubjectIdentity.getNextSyncCommittee())) {
parentPeriod = parentPeriod.add(BigInteger.ONE);
}
ethSubjectIdentity.setCurrentSyncCommitteePeriod(parentPeriod);
}

try {
ethConsensusStateData.validate(ethSubjectIdentity.getCurrentSyncCommittee(), ethEndorsements, ethSubjectIdentity.getEth2ChainConfig());
verifyAndUpdateSyncCommittee(ethSubjectIdentity, ethConsensusStateData, ethEndorsements);
} catch (InvalidConsensusDataException e) {
getHCDVSLogger().error("❌ failed to verify eth consensus state data (slot: {}, hash: {})",
stateToVerify.getHeight().toString(), stateToVerify.getHashHex(), e);
return VerifyResult.fail("failed to verify eth consensus state data: {}", e.getMessage());
}
}

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());
}

stateToVerify.setConsensusNodeInfo(ethSubjectIdentity.toJson().getBytes());

getHCDVSLogger().info("🌈 successful to verify consensus state (slot: {}, root: {}) now!",
stateToVerify.getHeight().toString(), stateToVerify.getHashHex());
return VerifyResult.success();
}

void verifyAndUpdateSyncCommittee(
EthSubjectIdentity subjectIdentity,
EthConsensusStateData consensusStateData,
EthConsensusEndorsements endorsements
) {
var syncPeriodLength = subjectIdentity.getEth2ChainConfig().getSyncPeriodLength();
var headerPeriod = consensusStateData.getCurrSyncPeriod(syncPeriodLength).bigIntegerValue();
var signatureSlot = endorsements.getSignatureSlotOrDefault(
consensusStateData.getBeaconBlockHeader().getSlot().increment()
);

advanceCurrentSyncCommitteeToPeriod(subjectIdentity, headerPeriod);
validateAndStoreNextSyncCommittee(subjectIdentity, consensusStateData);
consensusStateData.validateBlock(
getCommitteeForSignaturePeriod(
subjectIdentity,
signatureSlot.dividedBy(syncPeriodLength).bigIntegerValue()
),
endorsements,
subjectIdentity.getEth2ChainConfig()
);
rotateCommitteeAfterPeriodTail(subjectIdentity, consensusStateData);
}

void advanceCurrentSyncCommitteeToPeriod(EthSubjectIdentity subjectIdentity, BigInteger targetPeriod) {
if (ObjectUtil.isNull(subjectIdentity.getCurrentSyncCommitteePeriod())) {
subjectIdentity.setCurrentSyncCommitteePeriod(targetPeriod);
return;
}
if (subjectIdentity.getCurrentSyncCommitteePeriod().equals(targetPeriod)) {
return;
}
if (!subjectIdentity.getCurrentSyncCommitteePeriod().add(BigInteger.ONE).equals(targetPeriod)) {
throw new InvalidConsensusDataException("unexpected sync committee period transition");
}
if (ObjectUtil.isNull(subjectIdentity.getNextSyncCommittee())) {
throw new InvalidConsensusDataException("missing next sync committee for period transition");
}

subjectIdentity.setCurrentSyncCommittee(subjectIdentity.getNextSyncCommittee());
subjectIdentity.setNextSyncCommittee(null);
subjectIdentity.setCurrentSyncCommitteePeriod(targetPeriod);
}

private void validateAndStoreNextSyncCommittee(
EthSubjectIdentity subjectIdentity,
EthConsensusStateData consensusStateData
) {
if (ObjectUtil.isNull(consensusStateData.getLightClientUpdateWrapper())) {
return;
}

consensusStateData.validateLightClientUpdate(
subjectIdentity.getCurrentSyncCommittee(),
subjectIdentity.getEth2ChainConfig()
);
var authenticatedNext = consensusStateData.getLightClientUpdateWrapper().getNextSyncCommittee();
if (ObjectUtil.isNotNull(subjectIdentity.getNextSyncCommittee())
&& !subjectIdentity.getNextSyncCommittee().hashTreeRoot().equals(authenticatedNext.hashTreeRoot())) {
throw new InvalidConsensusDataException("conflicting next sync committee");
}
subjectIdentity.setNextSyncCommittee(authenticatedNext);
}

private SyncCommittee getCommitteeForSignaturePeriod(
EthSubjectIdentity subjectIdentity,
BigInteger signaturePeriod
) {
var currentPeriod = subjectIdentity.getCurrentSyncCommitteePeriod();
if (signaturePeriod.equals(currentPeriod)) {
return subjectIdentity.getCurrentSyncCommittee();
}
if (signaturePeriod.equals(currentPeriod.add(BigInteger.ONE))) {
if (ObjectUtil.isNull(subjectIdentity.getNextSyncCommittee())) {
throw new InvalidConsensusDataException("missing next sync committee for endorsements");
}
return subjectIdentity.getNextSyncCommittee();
}
throw new InvalidConsensusDataException("unexpected endorsements signature period");
}

private void rotateCommitteeAfterPeriodTail(
EthSubjectIdentity subjectIdentity,
EthConsensusStateData consensusStateData
) {
if (!consensusStateData.isLastSlotForCurrentPeriod(
subjectIdentity.getEth2ChainConfig().getSyncPeriodLength()
)) {
return;
}
if (ObjectUtil.isNull(subjectIdentity.getNextSyncCommittee())) {
throw new InvalidConsensusDataException("missing next sync committee at period tail");
}

getHCDVSLogger().info("🗳️ last slot {} for current period {}, update the sync committee",
consensusStateData.getBeaconBlockHeader().getSlot().toString(),
subjectIdentity.getCurrentSyncCommitteePeriod()
);
subjectIdentity.setCurrentSyncCommittee(subjectIdentity.getNextSyncCommittee());
subjectIdentity.setNextSyncCommittee(null);
subjectIdentity.setCurrentSyncCommitteePeriod(
subjectIdentity.getCurrentSyncCommitteePeriod().add(BigInteger.ONE)
);
}

@Override
public VerifyResult verifyCrossChainMessage(CrossChainMessage message, ConsensusState currState) {
if (new BigInteger(currState.getHash()).equals(BigInteger.ZERO)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,30 +510,12 @@ public List<CrossChainMessage> readAuthMessagesFromBlock(BigInteger slot, String
public EthConsensusStateData getEthConsensusStateData(BigInteger slot, String amContract) {
var ethConsensusStateData = new EthConsensusStateData();
ethConsensusStateData.setAmContractHex(amContract);
// last slot for this period
var currPeriod = currentSyncCommitteePeriod(slot);
var currPeriodEndSlot = currPeriod.multiply(BigInteger.valueOf(config.getEth2ChainConfig().getSyncPeriodLength()));
if (currPeriodEndSlot.equals(slot)) {
// fetch the sync committee update
getBbcLogger().info("get light client update for next period: {}", currPeriod.add(BigInteger.ONE));
var lightClientUpdate = getLightClientUpdate(slot);
if (ObjectUtil.isNull(lightClientUpdate)) {
getBbcLogger().error("none update found for period: {}", currPeriod);
throw new RuntimeException(StrUtil.format("none update found for period: {}", currPeriod.toString()));
}
ethConsensusStateData.setLightClientUpdateWrapper(lightClientUpdate);
}

getBbcLogger().info("has ccmsg on slot {} or already has no cache, will fetch the whole beacon block...", slot);
var signedBeaconBlock = getBeaconBlockBySlot(slot);
if (ObjectUtil.isNull(signedBeaconBlock)) {
return ethConsensusStateData;
}
if (signedBeaconBlock.getBeaconBlock().isEmpty()) {
getBbcLogger().warn("slot {} has no beacon block, could be empty", slot);
var beaconBlock = getBeaconBlockBySlot(slot);
if (ObjectUtil.isNull(beaconBlock)) {
return ethConsensusStateData;
}
var beaconBlock = signedBeaconBlock.getBeaconBlock().get();
if (beaconBlock.getBody().getOptionalExecutionPayloadHeader().isEmpty()) {
throw new RuntimeException("no execution payload found in beacon block as slot " + slot);
}
Expand All @@ -549,9 +531,56 @@ public EthConsensusStateData getEthConsensusStateData(BigInteger slot, String am
)
);

populateLightClientUpdateIfRequired(ethConsensusStateData, slot, null);

return ethConsensusStateData;
}

public void populateLightClientUpdateIfRequired(
EthConsensusStateData ethConsensusStateData,
BigInteger stateSlot,
BigInteger signatureSlot
) {
var syncPeriodLength = BigInteger.valueOf(config.getEth2ChainConfig().getSyncPeriodLength());
var statePeriod = stateSlot.divide(syncPeriodLength);
var signaturePeriod = ObjectUtil.isNull(signatureSlot) ? statePeriod : signatureSlot.divide(syncPeriodLength);
if (signaturePeriod.compareTo(statePeriod.add(BigInteger.ONE)) > 0) {
throw new RuntimeException(StrUtil.format(
"signature slot {} is more than one sync committee period ahead of state slot {}",
signatureSlot, stateSlot
));
}
if (!requiresLightClientUpdate(stateSlot, signatureSlot, syncPeriodLength.longValue())) {
return;
}
if (ObjectUtil.isNotNull(ethConsensusStateData.getLightClientUpdateWrapper())) {
return;
}

getBbcLogger().info("get light client update for next period: {}", statePeriod.add(BigInteger.ONE));
var lightClientUpdate = getLightClientUpdate(stateSlot);
if (ObjectUtil.isNull(lightClientUpdate)) {
getBbcLogger().error("none update found for period: {}", statePeriod);
throw new RuntimeException(StrUtil.format("none update found for period: {}", statePeriod.toString()));
}
ethConsensusStateData.setLightClientUpdateWrapper(lightClientUpdate);
}

public static boolean requiresLightClientUpdate(
BigInteger stateSlot,
BigInteger signatureSlot,
long syncPeriodLength
) {
var periodLength = BigInteger.valueOf(syncPeriodLength);
var statePeriod = stateSlot.divide(periodLength);
var periodEndSlot = statePeriod.add(BigInteger.ONE).multiply(periodLength).subtract(BigInteger.ONE);
if (periodEndSlot.equals(stateSlot)) {
return true;
}
return ObjectUtil.isNotNull(signatureSlot)
&& signatureSlot.divide(periodLength).compareTo(statePeriod) > 0;
}

public boolean hasTpBtaOnPtcHub(String ptcHubAddress, CrossChainLane tpbtaLane, int tpBtaVersion) {
try {
PtcHub ptcHub = PtcHub.load(ptcHubAddress, this.web3j, this.rawTransactionManager, null);
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();
}
}
Loading