diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml b/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml index 13847e02..067b25ef 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml @@ -233,6 +233,9 @@ **/*.sol + + lib/ptc/CommitteePtcVerifier.sol + diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCService.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCService.java index 12df3c6a..12c21472 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCService.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCService.java @@ -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, diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsService.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsService.java index 50ebbd11..9412b707 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsService.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsService.java @@ -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(); @@ -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); @@ -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()); diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/AcbEthClient.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/AcbEthClient.java index 532adb5a..058e16f6 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/AcbEthClient.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/AcbEthClient.java @@ -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)); diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusEndorsements.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusEndorsements.java index d5e36c25..ba6de3de 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusEndorsements.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusEndorsements.java @@ -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; @@ -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); @@ -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(); } } diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusStateData.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusStateData.java index 7354a565..e8c65243 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusStateData.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthConsensusStateData.java @@ -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(); @@ -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( @@ -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) { @@ -177,7 +185,7 @@ public String toJson() { return jsonObject.toJSONString(); } - private void validateLightClientUpdate( + private void validateLightClientUpdateInternal( LightClientUpdateWrapper lightClientUpdate, SyncCommittee currentSyncCommittee, Eth2ChainConfig eth2ChainConfig @@ -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, diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthSubjectIdentity.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthSubjectIdentity.java index 800ef023..934ffbef 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthSubjectIdentity.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/EthSubjectIdentity.java @@ -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; } @@ -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); } diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/eth/beacon/AcbBeaconClient.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/eth/beacon/AcbBeaconClient.java index 531b837b..9363cffa 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/eth/beacon/AcbBeaconClient.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/core/eth/beacon/AcbBeaconClient.java @@ -321,7 +321,12 @@ public Map getRemoteSpec() { switch (resp.statusCode()) { case 200 -> { var body = JSON.parseObject(resp.body(), ObjectAndMetaData.class); - return JSON.parseObject(body.getData(), new TypeReference<>(){}); + Map 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())); } diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCServiceTest.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCServiceTest.java index 10dc7cef..b91bc7ca 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCServiceTest.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumBBCServiceTest.java @@ -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); @@ -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()); diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsTest.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsTest.java index 086606a9..84f934be 100644 --- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsTest.java +++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/test/java/com/alipay/antchain/bridge/plugins/ethereum2/EthereumHcdvsTest.java @@ -15,6 +15,7 @@ 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; @@ -22,6 +23,7 @@ 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; @@ -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());