From e8e29607370e95ef4b19ef0666dcb7ce6026c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E5=98=89=E9=A2=96?= <568296125@qq.com> Date: Wed, 3 Jun 2026 23:29:09 +0800 Subject: [PATCH 1/2] [fix][committeeptc][node] match anchor BTA by init state --- .../EndorseServiceRepositoryImpl.java | 69 +++++++++++++++++-- .../repository/SystemConfigRepository.java | 28 +++++--- .../interfaces/IEndorseServiceRepository.java | 4 ++ .../service/impl/EndorserServiceImpl.java | 36 +++++++--- .../dal/EndorseServiceRepositoryTest.java | 3 + .../node/dal/SystemConfigRepositoryTest.java | 6 ++ .../node/server/CommitteeNodeServiceTest.java | 6 ++ .../node/service/EndorserServiceTest.java | 5 ++ 8 files changed, 134 insertions(+), 23 deletions(-) diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java index 8520acc8..69dd880b 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java @@ -21,6 +21,7 @@ import java.util.List; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import com.alipay.antchain.bridge.commons.core.base.CrossChainLane; import com.alipay.antchain.bridge.ptc.committee.node.commons.exception.DataAccessLayerException; @@ -86,6 +87,26 @@ public TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion) { } } + @Override + public TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion, int btaSubjectVersion) { + try { + var entityList = searchTpBta(lane, tpbtaVersion).stream() + .filter(entity -> entity.getBtaSubjectVersion() == btaSubjectVersion) + .toList(); + if (ObjectUtil.isEmpty(entityList)) { + return null; + } + return ConvertUtil.convertFrom( + entityList.stream().max(Comparator.comparingInt(TpBtaEntity::getTpbtaVersion)).get() + ); + } catch (Exception e) { + throw new DataAccessLayerException( + e, "Failed to get tpbta for lane {}, version {} and bta subject version {}", + lane.getLaneKey(), tpbtaVersion, btaSubjectVersion + ); + } + } + @Override public TpBtaWrapper getExactTpBta(CrossChainLane lane) { return getExactTpBta(lane, -1); @@ -117,14 +138,19 @@ public TpBtaWrapper getExactTpBta(CrossChainLane lane, int tpbtaVersion) { @Override public void setTpBta(TpBtaWrapper tpBtaWrapper) { + var lane = tpBtaWrapper.getCrossChainLane(); + var tpbtaVersion = tpBtaWrapper.getTpbta().getTpbtaVersion(); try { - if (hasTpBta(tpBtaWrapper.getCrossChainLane(), tpBtaWrapper.getTpbta().getTpbtaVersion())) { - throw new RuntimeException("tpBta already exists"); + if (hasTpBta(lane, tpbtaVersion)) { + return; } tpBtaMapper.insert((TpBtaEntity) ConvertUtil.convertFrom(tpBtaWrapper)); } catch (Exception e) { + if (hasTpBta(lane, tpbtaVersion)) { + return; + } throw new DataAccessLayerException( - e, "Failed to save tpbta for lane {}", tpBtaWrapper.getCrossChainLane().getLaneKey() + e, "Failed to save tpbta for lane {}", lane.getLaneKey() ); } } @@ -186,14 +212,42 @@ public BtaWrapper getBta(String domain, int subjectVersion) { } } + @Override + public BtaWrapper getBta(String domain, BigInteger initHeight, byte[] initBlockHash) { + try { + var entityList = btaMapper.selectList( + new LambdaQueryWrapper() + .eq(BtaEntity::getDomain, domain) + ); + if (ObjectUtil.isEmpty(entityList)) { + return null; + } + return entityList.stream() + .map(ConvertUtil::convertFrom) + .map(BtaWrapper.class::cast) + .filter(wrapper -> wrapper.getBta().getInitHeight().equals(initHeight)) + .filter(wrapper -> ArrayUtil.equals(wrapper.getBta().getInitBlockHash(), initBlockHash)) + .max(Comparator.comparingInt(BtaWrapper::getSubjectVersion)) + .orElse(null); + } catch (Exception e) { + throw new DataAccessLayerException( + e, "Failed to get bta for domain {}, init height {} and init block hash {}", + domain, initHeight, initBlockHash + ); + } + } + @Override public void setBta(BtaWrapper btaWrapper) { try { - if (hasBta(btaWrapper.getDomain(), btaWrapper.getBtaVersion())) { - throw new RuntimeException("bta already exists"); + if (hasBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion())) { + return; } btaMapper.insert((BtaEntity) ConvertUtil.convertFrom(btaWrapper)); } catch (Exception e) { + if (hasBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion())) { + return; + } throw new DataAccessLayerException( e, "Failed to save bta for domain {} and subject version {}", btaWrapper.getDomain(), btaWrapper.getSubjectVersion() ); @@ -272,10 +326,13 @@ public ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, public void setValidatedConsensusState(ValidatedConsensusStateWrapper validatedConsensusStateWrapper) { try { if (hasValidatedConsensusState(validatedConsensusStateWrapper.getDomain(), validatedConsensusStateWrapper.getHeight())) { - throw new RuntimeException("validated consensus state already exists"); + return; } validatedConsensusStatesMapper.insert((ValidatedConsensusStatesEntity) ConvertUtil.convertFrom(validatedConsensusStateWrapper)); } catch (Exception e) { + if (hasValidatedConsensusState(validatedConsensusStateWrapper.getDomain(), validatedConsensusStateWrapper.getHeight())) { + return; + } throw new DataAccessLayerException( e, "Failed to save validated consensus state for domain {} and height {}", validatedConsensusStateWrapper.getDomain(), diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/SystemConfigRepository.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/SystemConfigRepository.java index 5d79f0e1..0d52db37 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/SystemConfigRepository.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/SystemConfigRepository.java @@ -29,6 +29,7 @@ import com.alipay.antchain.bridge.ptc.committee.node.dal.mapper.SystemConfigMapper; import com.alipay.antchain.bridge.ptc.committee.node.dal.repository.interfaces.ISystemConfigRepository; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import jakarta.annotation.Resource; import lombok.Synchronized; import org.springframework.stereotype.Component; @@ -79,14 +80,7 @@ public boolean hasSystemConfig(String key) { @Override public void setSystemConfig(Map configs) { try { - configs.forEach((key, value) -> { - systemConfigMapper.insert( - SystemConfigEntity.builder() - .confKey(key) - .confValue(value) - .build() - ); - }); + configs.forEach(this::setSystemConfig); } catch (Exception e) { throw new DataAccessLayerException( e, "Failed to set system config with key: {}", JSON.toJSONString(configs) @@ -98,6 +92,10 @@ public void setSystemConfig(Map configs) { @Synchronized public void setSystemConfig(String key, String value) { try { + if (hasSystemConfig(key)) { + updateSystemConfig(key, value); + return; + } systemConfigMapper.insert( SystemConfigEntity.builder() .confKey(key) @@ -105,6 +103,10 @@ public void setSystemConfig(String key, String value) { .build() ); } catch (Exception e) { + if (hasSystemConfig(key)) { + updateSystemConfig(key, value); + return; + } throw new DataAccessLayerException( e, "Failed to set system config with key: {}", key ); @@ -135,4 +137,14 @@ public void setPtcTrustRoot(PTCTrustRoot ptcTrustRoot) { public PTCTrustRoot getPtcTrustRoot() { return PTCTrustRoot.decode(Base64.decode(getSystemConfig(CURRENT_PTC_TRUST_ROOT))); } + + private void updateSystemConfig(String key, String value) { + systemConfigMapper.update( + SystemConfigEntity.builder() + .confValue(value) + .build(), + new LambdaUpdateWrapper() + .eq(SystemConfigEntity::getConfKey, key) + ); + } } diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/interfaces/IEndorseServiceRepository.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/interfaces/IEndorseServiceRepository.java index b98fc799..048814db 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/interfaces/IEndorseServiceRepository.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/interfaces/IEndorseServiceRepository.java @@ -29,6 +29,8 @@ public interface IEndorseServiceRepository { TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion); + TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion, int btaSubjectVersion); + TpBtaWrapper getExactTpBta(CrossChainLane lane); TpBtaWrapper getExactTpBta(CrossChainLane lane, int tpbtaVersion); @@ -41,6 +43,8 @@ public interface IEndorseServiceRepository { BtaWrapper getBta(String domain, int subjectVersion); + BtaWrapper getBta(String domain, BigInteger initHeight, byte[] initBlockHash); + void setBta(BtaWrapper btaWrapper); boolean hasBta(String domain, int subjectVersion); diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java index f7ffc200..9dfe9bb2 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java @@ -138,15 +138,26 @@ public TpBtaWrapper verifyBta(AbstractCrossChainCertificate domainCert, IBlockch throw new InvalidBtaException("tpbta intersection check failed"); } + var committeeEndorseRoot = verifyBtaExtension.getCommitteeEndorseRoot().encode(); + var currentPtcAnchorVersion = systemConfigRepository.queryCurrentPtcAnchorVersion(); var latestTpBta = endorseServiceRepository.getExactTpBta(verifyBtaExtension.getCrossChainLane()); + var tpbtaVersion = ObjectUtil.isNull(latestTpBta) ? 1 : latestTpBta.getTpbta().getTpbtaVersion() + 1; + if ( + ObjectUtil.isNotNull(latestTpBta) + && latestTpBta.getTpbta().getBtaSubjectVersion() == bta.getSubjectVersion() + && ObjectUtil.equals(latestTpBta.getTpbta().getPtcVerifyAnchorVersion(), currentPtcAnchorVersion) + && ArrayUtil.equals(latestTpBta.getTpbta().getEndorseRoot(), committeeEndorseRoot) + ) { + tpbtaVersion = latestTpBta.getTpbta().getTpbtaVersion(); + } var tpbta = new ThirdPartyBlockchainTrustAnchorV1( - ObjectUtil.isNull(latestTpBta) ? 1 : latestTpBta.getTpbta().getTpbtaVersion() + 1, - systemConfigRepository.queryCurrentPtcAnchorVersion(), + tpbtaVersion, + currentPtcAnchorVersion, (PTCCredentialSubject) ptcCrossChainCert.getCredentialSubjectInstance(), verifyBtaExtension.getCrossChainLane(), bta.getSubjectVersion(), ucpHashAlgo, - verifyBtaExtension.getCommitteeEndorseRoot().encode(), + committeeEndorseRoot, new byte[]{} ); tpbta.setEndorseProof( @@ -177,13 +188,20 @@ private boolean checkIfTpBTAIntersection(CrossChainLane tpbtaLane) { @Override public ValidatedConsensusState commitAnchorState(CrossChainLane crossChainLane, ConsensusState anchorState) { - var tpbta = endorseServiceRepository.getMatchedTpBta(crossChainLane); - if (ObjectUtil.isNull(tpbta)) { - throw new InvalidConsensusStateException("tpbta not found for {}", crossChainLane.getLaneKey()); - } - var bta = endorseServiceRepository.getBta(crossChainLane.getSenderDomain().getDomain(), tpbta.getTpbta().getBtaSubjectVersion()); + var bta = endorseServiceRepository.getBta( + anchorState.getDomain().getDomain(), + anchorState.getHeight(), + anchorState.getHash() + ); if (ObjectUtil.isNull(bta)) { - throw new InvalidConsensusStateException("bta not found for {}", crossChainLane.getSenderDomain().getDomain()); + throw new InvalidConsensusStateException("bta not found for domain {}, height {} and hash {}", + anchorState.getDomain().getDomain(), anchorState.getHeight().toString(), anchorState.getHashHex()); + } + + var tpbta = endorseServiceRepository.getMatchedTpBta(crossChainLane, -1, bta.getSubjectVersion()); + if (ObjectUtil.isNull(tpbta)) { + throw new InvalidConsensusStateException("tpbta not found for {} and bta subject version {}", + crossChainLane.getLaneKey(), bta.getSubjectVersion()); } var hcdvs = hcdvsPluginService.getHCDVSService(bta.getProduct()); diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java index 839bb0af..6f437133 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java @@ -73,6 +73,7 @@ public void testBta() { var btaWrapper = new BtaWrapper(); btaWrapper.setBta(bta); endorseServiceRepository.setBta(btaWrapper); + endorseServiceRepository.setBta(btaWrapper); var btaWrapperFromDB = endorseServiceRepository.getBta(btaWrapper.getDomain()); Assert.assertEquals( @@ -122,6 +123,7 @@ public void testTpBta() { ); var tpBtaWrapper = new TpBtaWrapper(tpbta); + endorseServiceRepository.setTpBta(tpBtaWrapper); endorseServiceRepository.setTpBta(tpBtaWrapper); Assert.assertNotNull(endorseServiceRepository.getMatchedTpBta(crossChainLane)); @@ -143,6 +145,7 @@ public void testValidatedConsensusState() { ); var vcs = BeanUtil.copyProperties(cs, ValidatedConsensusStateV1.class); + endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs)); endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs)); Assert.assertTrue(endorseServiceRepository.hasValidatedConsensusState("test", BigInteger.valueOf(100L))); diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/SystemConfigRepositoryTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/SystemConfigRepositoryTest.java index 36e008d3..9853c33c 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/SystemConfigRepositoryTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/SystemConfigRepositoryTest.java @@ -69,5 +69,11 @@ public void testSystemConfig() { "test", systemConfigRepository.getSystemConfig("test") ); + + systemConfigRepository.setSystemConfig("test", "test2"); + Assert.assertEquals( + "test2", + systemConfigRepository.getSystemConfig("test") + ); } } diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/server/CommitteeNodeServiceTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/server/CommitteeNodeServiceTest.java index 42eb3937..b94d8cff 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/server/CommitteeNodeServiceTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/server/CommitteeNodeServiceTest.java @@ -65,6 +65,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -347,7 +348,9 @@ public void testCommitAnchorState() { var hcdvs = mock(IHeteroChainDataVerifierService.class); when(hcdvs.verifyAnchorConsensusState(any(), any())).thenReturn(VerifyResult.builder().success(true).build()); when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); + when(endorseServiceRepository.getMatchedTpBta(any(), anyInt(), anyInt())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), any(), any())).thenReturn(new BtaWrapper(bta)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class); @@ -397,7 +400,9 @@ public void testCommitConsensusState() { when(hcdvs.verifyConsensusState(any(), any())).thenReturn(VerifyResult.builder().success(true).build()); when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), anyInt())).thenReturn(new BtaWrapper(bta)); when(endorseServiceRepository.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(vcs)); + when(endorseServiceRepository.getValidatedConsensusState(anyString(), any(BigInteger.class))).thenReturn(new ValidatedConsensusStateWrapper(vcs)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); StreamRecorder responseObserver = StreamRecorder.create(); @@ -438,6 +443,7 @@ public void testVerifyCrossChainMessage() { when(hcdvs.parseMessageFromLedgerData(any())).thenReturn(ucp.getSrcMessage().getMessage()); when(endorseServiceRepository.getExactTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), anyInt())).thenReturn(new BtaWrapper(bta)); when(endorseServiceRepository.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(currVcs)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java index 6c64d69e..68d399ec 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java @@ -280,7 +280,9 @@ public void testCommitAnchorState() { var hcdvs = mock(IHeteroChainDataVerifierService.class); when(hcdvs.verifyAnchorConsensusState(any(), any())).thenReturn(VerifyResult.builder().success(true).build()); when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); + when(endorseServiceRepository.getMatchedTpBta(any(), anyInt(), anyInt())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), any(), any())).thenReturn(new BtaWrapper(bta)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class); @@ -313,7 +315,9 @@ public void testCommitConsensusState() { when(hcdvs.verifyConsensusState(any(), any())).thenReturn(VerifyResult.builder().success(true).build()); when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), anyInt())).thenReturn(new BtaWrapper(bta)); when(endorseServiceRepository.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(vcs)); + when(endorseServiceRepository.getValidatedConsensusState(anyString(), any(BigInteger.class))).thenReturn(new ValidatedConsensusStateWrapper(vcs)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); var vcsSigned = endorserService.commitConsensusState(crossChainLane, currState); @@ -337,6 +341,7 @@ public void testVerifyUcp() { when(hcdvs.parseMessageFromLedgerData(any())).thenReturn(ucp.getSrcMessage().getMessage()); when(endorseServiceRepository.getExactTpBta(any())).thenReturn(new TpBtaWrapper(tpbta)); when(endorseServiceRepository.getBta(anyString())).thenReturn(new BtaWrapper(bta)); + when(endorseServiceRepository.getBta(anyString(), anyInt())).thenReturn(new BtaWrapper(bta)); when(endorseServiceRepository.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(currVcs)); when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs); From ecd96e2c565d0bf7f8f86ed344962b5c656f3807 Mon Sep 17 00:00:00 2001 From: 0xstride <73103011+fengjy73@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:47:53 +0800 Subject: [PATCH 2/2] [fix][committeeptc] enforce trust anchor consistency --- .../EndorseServiceRepositoryImpl.java | 92 +++++++++++++++---- .../service/impl/EndorserServiceImpl.java | 9 +- .../dal/EndorseServiceRepositoryTest.java | 45 ++++++++- .../node/service/EndorserServiceTest.java | 17 +++- 4 files changed, 142 insertions(+), 21 deletions(-) diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java index 69dd880b..68b62e59 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/dal/repository/EndorseServiceRepositoryImpl.java @@ -90,9 +90,7 @@ public TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion) { @Override public TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion, int btaSubjectVersion) { try { - var entityList = searchTpBta(lane, tpbtaVersion).stream() - .filter(entity -> entity.getBtaSubjectVersion() == btaSubjectVersion) - .toList(); + var entityList = searchTpBta(lane, tpbtaVersion, btaSubjectVersion); if (ObjectUtil.isEmpty(entityList)) { return null; } @@ -140,13 +138,17 @@ public TpBtaWrapper getExactTpBta(CrossChainLane lane, int tpbtaVersion) { public void setTpBta(TpBtaWrapper tpBtaWrapper) { var lane = tpBtaWrapper.getCrossChainLane(); var tpbtaVersion = tpBtaWrapper.getTpbta().getTpbtaVersion(); + var existing = getExactTpBta(lane, tpbtaVersion); + if (ObjectUtil.isNotNull(existing)) { + assertSameTpBta(existing, tpBtaWrapper); + return; + } try { - if (hasTpBta(lane, tpbtaVersion)) { - return; - } tpBtaMapper.insert((TpBtaEntity) ConvertUtil.convertFrom(tpBtaWrapper)); } catch (Exception e) { - if (hasTpBta(lane, tpbtaVersion)) { + existing = getExactTpBta(lane, tpbtaVersion); + if (ObjectUtil.isNotNull(existing)) { + assertSameTpBta(existing, tpBtaWrapper); return; } throw new DataAccessLayerException( @@ -239,13 +241,17 @@ public BtaWrapper getBta(String domain, BigInteger initHeight, byte[] initBlockH @Override public void setBta(BtaWrapper btaWrapper) { + var existing = getBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion()); + if (ObjectUtil.isNotNull(existing)) { + assertSameBta(existing, btaWrapper); + return; + } try { - if (hasBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion())) { - return; - } btaMapper.insert((BtaEntity) ConvertUtil.convertFrom(btaWrapper)); } catch (Exception e) { - if (hasBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion())) { + existing = getBta(btaWrapper.getDomain(), btaWrapper.getSubjectVersion()); + if (ObjectUtil.isNotNull(existing)) { + assertSameBta(existing, btaWrapper); return; } throw new DataAccessLayerException( @@ -324,13 +330,23 @@ public ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, @Override public void setValidatedConsensusState(ValidatedConsensusStateWrapper validatedConsensusStateWrapper) { + var existing = getValidatedConsensusState( + validatedConsensusStateWrapper.getDomain(), + validatedConsensusStateWrapper.getHeight() + ); + if (ObjectUtil.isNotNull(existing)) { + assertSameValidatedConsensusState(existing, validatedConsensusStateWrapper); + return; + } try { - if (hasValidatedConsensusState(validatedConsensusStateWrapper.getDomain(), validatedConsensusStateWrapper.getHeight())) { - return; - } validatedConsensusStatesMapper.insert((ValidatedConsensusStatesEntity) ConvertUtil.convertFrom(validatedConsensusStateWrapper)); } catch (Exception e) { - if (hasValidatedConsensusState(validatedConsensusStateWrapper.getDomain(), validatedConsensusStateWrapper.getHeight())) { + existing = getValidatedConsensusState( + validatedConsensusStateWrapper.getDomain(), + validatedConsensusStateWrapper.getHeight() + ); + if (ObjectUtil.isNotNull(existing)) { + assertSameValidatedConsensusState(existing, validatedConsensusStateWrapper); return; } throw new DataAccessLayerException( @@ -357,12 +373,17 @@ public boolean hasValidatedConsensusState(String domain, BigInteger height) { } private List searchTpBta(CrossChainLane lane, int tpbtaVersion) { + return searchTpBta(lane, tpbtaVersion, null); + } + + private List searchTpBta(CrossChainLane lane, int tpbtaVersion, Integer btaSubjectVersion) { // search the blockchain level first var wrapper = new LambdaQueryWrapper() .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain()) .eq(TpBtaEntity::getReceiverDomain, "") .eq(TpBtaEntity::getSenderId, "") - .eq(TpBtaEntity::getReceiverId, ""); + .eq(TpBtaEntity::getReceiverId, "") + .eq(ObjectUtil.isNotNull(btaSubjectVersion), TpBtaEntity::getBtaSubjectVersion, btaSubjectVersion); var entityList = tpBtaMapper.selectList( tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion) ); @@ -378,7 +399,8 @@ private List searchTpBta(CrossChainLane lane, int tpbtaVersion) { .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain()) .eq(TpBtaEntity::getReceiverDomain, lane.getReceiverDomain().getDomain()) .eq(TpBtaEntity::getSenderId, "") - .eq(TpBtaEntity::getReceiverId, ""); + .eq(TpBtaEntity::getReceiverId, "") + .eq(ObjectUtil.isNotNull(btaSubjectVersion), TpBtaEntity::getBtaSubjectVersion, btaSubjectVersion); entityList = tpBtaMapper.selectList( tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion) ); @@ -395,7 +417,8 @@ private List searchTpBta(CrossChainLane lane, int tpbtaVersion) { .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain()) .eq(TpBtaEntity::getSenderId, lane.getSenderId().toHex()) .eq(TpBtaEntity::getReceiverDomain, lane.getReceiverDomain().getDomain()) - .eq(TpBtaEntity::getReceiverId, lane.getReceiverId().toHex()); + .eq(TpBtaEntity::getReceiverId, lane.getReceiverId().toHex()) + .eq(ObjectUtil.isNotNull(btaSubjectVersion), TpBtaEntity::getBtaSubjectVersion, btaSubjectVersion); entityList = tpBtaMapper.selectList( tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion) ); @@ -405,4 +428,37 @@ private List searchTpBta(CrossChainLane lane, int tpbtaVersion) { return ListUtil.empty(); } + + private void assertSameTpBta(TpBtaWrapper existing, TpBtaWrapper incoming) { + if (!ArrayUtil.equals(existing.getTpbta().encode(), incoming.getTpbta().encode())) { + throw new DataAccessLayerException( + "Conflicting tpbta for lane {} and version {}", + incoming.getCrossChainLane().getLaneKey(), incoming.getTpbta().getTpbtaVersion() + ); + } + } + + private void assertSameBta(BtaWrapper existing, BtaWrapper incoming) { + if (!ArrayUtil.equals(existing.getBta().encode(), incoming.getBta().encode())) { + throw new DataAccessLayerException( + "Conflicting bta for domain {} and subject version {}", + incoming.getDomain(), incoming.getSubjectVersion() + ); + } + } + + private void assertSameValidatedConsensusState( + ValidatedConsensusStateWrapper existing, + ValidatedConsensusStateWrapper incoming + ) { + if (!ArrayUtil.equals( + existing.getValidatedConsensusState().encode(), + incoming.getValidatedConsensusState().encode() + )) { + throw new DataAccessLayerException( + "Conflicting validated consensus state for domain {} and height {}", + incoming.getDomain(), incoming.getHeight() + ); + } + } } diff --git a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java index 9dfe9bb2..4cff162a 100644 --- a/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java +++ b/acb-committeeptc/node/src/main/java/com/alipay/antchain/bridge/ptc/committee/node/service/impl/EndorserServiceImpl.java @@ -188,8 +188,15 @@ private boolean checkIfTpBTAIntersection(CrossChainLane tpbtaLane) { @Override public ValidatedConsensusState commitAnchorState(CrossChainLane crossChainLane, ConsensusState anchorState) { + if (!ObjectUtil.equals(crossChainLane.getSenderDomain(), anchorState.getDomain())) { + throw new InvalidConsensusStateException( + "cross-chain lane sender domain {} does not match anchor state domain {}", + crossChainLane.getSenderDomain().getDomain(), anchorState.getDomain().getDomain() + ); + } + var bta = endorseServiceRepository.getBta( - anchorState.getDomain().getDomain(), + crossChainLane.getSenderDomain().getDomain(), anchorState.getHeight(), anchorState.getHash() ); diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java index 6f437133..9a0039a5 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/dal/EndorseServiceRepositoryTest.java @@ -32,6 +32,7 @@ import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum; import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum; import com.alipay.antchain.bridge.ptc.committee.node.TestBase; +import com.alipay.antchain.bridge.ptc.committee.node.commons.exception.DataAccessLayerException; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.BtaWrapper; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.TpBtaWrapper; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.ValidatedConsensusStateWrapper; @@ -45,8 +46,11 @@ import jakarta.annotation.Resource; import lombok.SneakyThrows; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; +@ExtendWith(SpringExtension.class) public class EndorseServiceRepositoryTest extends TestBase { @Resource @@ -75,6 +79,10 @@ public void testBta() { endorseServiceRepository.setBta(btaWrapper); endorseServiceRepository.setBta(btaWrapper); + var conflictingBta = BeanUtil.copyProperties(bta, BlockchainTrustAnchorV1.class); + conflictingBta.setSubjectIdentity("different".getBytes()); + assertConflictingWrite(() -> endorseServiceRepository.setBta(new BtaWrapper(conflictingBta))); + var btaWrapperFromDB = endorseServiceRepository.getBta(btaWrapper.getDomain()); Assert.assertEquals( HexUtil.encodeHexStr(btaWrapper.getBta().getSubjectIdentity()), @@ -126,7 +134,27 @@ public void testTpBta() { endorseServiceRepository.setTpBta(tpBtaWrapper); endorseServiceRepository.setTpBta(tpBtaWrapper); + var conflictingTpBta = ThirdPartyBlockchainTrustAnchorV1.decode(tpbta.encode()); + conflictingTpBta.setEndorseProof("different".getBytes()); + assertConflictingWrite(() -> endorseServiceRepository.setTpBta(new TpBtaWrapper(conflictingTpBta))); + + var blockchainLevelTpBta = new ThirdPartyBlockchainTrustAnchorV1( + 2, + BigInteger.ONE, + tpbta.getSignerPtcCredentialSubject(), + new CrossChainLane(crossChainLane.getSenderDomain()), + 2, + HashAlgoEnum.KECCAK_256, + tpbta.getEndorseRoot(), + tpbta.getEndorseProof() + ); + endorseServiceRepository.setTpBta(new TpBtaWrapper(blockchainLevelTpBta)); + Assert.assertNotNull(endorseServiceRepository.getMatchedTpBta(crossChainLane)); + Assert.assertEquals( + 1, + endorseServiceRepository.getMatchedTpBta(crossChainLane, -1, 1).getTpbta().getBtaSubjectVersion() + ); Assert.assertTrue(endorseServiceRepository.hasTpBta(crossChainLane, 1)); Assert.assertNotNull(endorseServiceRepository.getMatchedTpBta(crossChainLane)); } @@ -148,6 +176,12 @@ public void testValidatedConsensusState() { endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs)); endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs)); + var conflictingVcs = BeanUtil.copyProperties(vcs, ValidatedConsensusStateV1.class); + conflictingVcs.setHash(RandomUtil.randomBytes(32)); + assertConflictingWrite( + () -> endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(conflictingVcs)) + ); + Assert.assertTrue(endorseServiceRepository.hasValidatedConsensusState("test", BigInteger.valueOf(100L))); Assert.assertEquals( cs.getHashHex(), @@ -162,4 +196,13 @@ public void testValidatedConsensusState() { endorseServiceRepository.getValidatedConsensusState("test", cs.getHashHex()).getParentHash() ); } + + private static void assertConflictingWrite(Runnable write) { + try { + write.run(); + Assert.fail("conflicting payload should be rejected"); + } catch (DataAccessLayerException expected) { + Assert.assertTrue(expected.getMessage().contains("Conflicting")); + } + } } diff --git a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java index 68d399ec..798c6ac1 100644 --- a/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java +++ b/acb-committeeptc/node/src/test/java/com/alipay/antchain/bridge/ptc/committee/node/service/EndorserServiceTest.java @@ -39,6 +39,7 @@ import com.alipay.antchain.bridge.plugins.spi.ptc.IHeteroChainDataVerifierService; import com.alipay.antchain.bridge.plugins.spi.ptc.core.VerifyResult; import com.alipay.antchain.bridge.ptc.committee.node.TestBase; +import com.alipay.antchain.bridge.ptc.committee.node.commons.exception.InvalidConsensusStateException; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.BtaWrapper; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.DomainSpaceCertWrapper; import com.alipay.antchain.bridge.ptc.committee.node.commons.models.TpBtaWrapper; @@ -56,12 +57,15 @@ import com.alipay.antchain.bridge.ptc.committee.types.tpbta.VerifyBtaExtension; import jakarta.annotation.Resource; import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.mockito.Mockito.*; +@ExtendWith(SpringExtension.class) public class EndorserServiceTest extends TestBase { private static final ThirdPartyBlockchainTrustAnchorV1 tpbta; @@ -299,6 +303,17 @@ public void testCommitAnchorState() { ); } + @Test + public void testCommitAnchorStateRejectsMismatchedLaneDomain() { + var mismatchedLane = new CrossChainLane(new CrossChainDomain("other-domain")); + try { + endorserService.commitAnchorState(mismatchedLane, anchorState); + Assert.fail("mismatched lane and anchor domains should be rejected"); + } catch (InvalidConsensusStateException expected) { + Assert.assertTrue(expected.getMessage().contains("does not match")); + } + } + @Test public void testCommitConsensusState() { var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class);