From 6e6d8bf2f8ec7b4d20e36b5d4a951638d43eaeda Mon Sep 17 00:00:00 2001
From: AlphaSaraday
Date: Thu, 6 Nov 2025 16:58:45 +0800
Subject: [PATCH] [20251106] add monitor module
---
CHANGELOG.md | 170 +++++
acb-committeeptc/monitor-node-cli/README.md | 232 +++++++
.../monitor-node-cli/desc_tar.xml | 32 +
acb-committeeptc/monitor-node-cli/pom.xml | 173 ++++++
.../committee/monitor/node/cli/Launcher.java | 59 ++
.../node/cli/commands/BaseCommands.java | 56 ++
.../node/cli/commands/CoreCommands.java | 183 ++++++
.../node/cli/commands/UtilsCommands.java | 94 +++
.../node/cli/config/CustomPromptProvider.java | 30 +
.../src/main/resources/application.yml | 13 +
.../src/main/resources/banner.txt | 7 +
.../src/main/resources/start.sh | 63 ++
acb-committeeptc/monitor-node/.gitignore | 33 +
.../.mvn/wrapper/maven-wrapper.properties | 19 +
acb-committeeptc/monitor-node/README.md | 22 +
acb-committeeptc/monitor-node/desc_tar.xml | 48 ++
acb-committeeptc/monitor-node/mvnw | 259 ++++++++
acb-committeeptc/monitor-node/mvnw.cmd | 149 +++++
acb-committeeptc/monitor-node/pom.xml | 235 +++++++
.../monitor/node/NodeApplication.java | 38 ++
.../CrossChainServiceGrpcClientManager.java | 101 +++
.../MonitorSystemGrpcClientManager.java | 41 ++
.../node/commons/core/MonitorContract.java | 18 +
.../commons/core/MonitorSystemRespMsg.java | 26 +
.../node/commons/enums/BCDNSStateEnum.java | 33 +
.../node/commons/enums/MonitorTypeEnum.java | 21 +
.../BlockStateNotValidatedYetException.java | 38 ++
.../exception/CommitteeNodeErrorCodeEnum.java | 55 ++
.../exception/CommitteeNodeException.java | 43 ++
.../CommitteeNodeInternalException.java | 34 +
.../exception/DataAccessLayerException.java | 38 ++
.../exception/InvalidBtaException.java | 38 ++
.../InvalidConsensusStateException.java | 38 ++
.../InvalidCrossChainMessageException.java | 38 ++
.../exception/InvalidRequestException.java | 38 ++
.../node/commons/models/BCDNSServiceDO.java | 60 ++
.../node/commons/models/BtaWrapper.java | 52 ++
.../models/DomainSpaceCertWrapper.java | 46 ++
.../node/commons/models/TpBtaWrapper.java | 47 ++
.../ValidatedConsensusStateWrapper.java | 48 ++
.../monitor/node/config/CredentialConfig.java | 49 ++
.../monitor/node/config/ServerConfig.java | 56 ++
.../monitor/node/dal/convert/ConvertUtil.java | 132 ++++
.../node/dal/entities/BCDNSServiceEntity.java | 49 ++
.../monitor/node/dal/entities/BaseEntity.java | 58 ++
.../monitor/node/dal/entities/BtaEntity.java | 45 ++
.../dal/entities/DomainSpaceCertEntity.java | 45 ++
.../node/dal/entities/SystemConfigEntity.java | 38 ++
.../node/dal/entities/TpBtaEntity.java | 57 ++
.../ValidatedConsensusStatesEntity.java | 48 ++
.../node/dal/mapper/BCDNSServiceMapper.java | 23 +
.../monitor/node/dal/mapper/BtaMapper.java | 23 +
.../dal/mapper/DomainSpaceCertMapper.java | 23 +
.../node/dal/mapper/SystemConfigMapper.java | 23 +
.../monitor/node/dal/mapper/TpBtaMapper.java | 23 +
.../ValidatedConsensusStatesMapper.java | 28 +
.../dal/repository/BCDNSRepositoryImpl.java | 317 ++++++++++
.../EndorseServiceRepositoryImpl.java | 351 +++++++++++
.../repository/SystemConfigRepository.java | 138 +++++
.../interfaces/IBCDNSRepository.java | 56 ++
.../interfaces/IEndorseServiceRepository.java | 57 ++
.../interfaces/ISystemConfigRepository.java | 41 ++
.../monitor/node/server/AdminServiceImpl.java | 171 ++++++
.../node/server/MonitorNodeServiceImpl.java | 510 +++++++++++++++
.../node/server/MonitorOrderServiceImpl.java | 73 +++
.../interceptor/RequestTraceInterceptor.java | 33 +
.../node/service/IBCDNSManageService.java | 71 +++
.../node/service/IEndorserService.java | 49 ++
.../node/service/IHcdvsPluginService.java | 52 ++
.../monitor/node/service/IMonitorService.java | 9 +
.../node/service/IScheduledTaskService.java | 22 +
.../node/service/impl/BCDNSManageService.java | 359 +++++++++++
.../service/impl/EndorserServiceImpl.java | 505 +++++++++++++++
.../service/impl/HcdvsPluginServiceImpl.java | 227 +++++++
.../node/service/impl/MonitorServiceImpl.java | 110 ++++
.../impl/ScheduledTaskServiceImpl.java | 81 +++
.../src/main/proto/admingrpc.proto | 74 +++
.../src/main/proto/monitorSystemgrpc.proto | 71 +++
.../src/main/proto/pluginserver.proto | 432 +++++++++++++
.../src/main/resources/application.yml | 59 ++
.../src/main/resources/banner.txt | 7 +
.../monitor-node/src/main/resources/ddl.sql | 115 ++++
.../src/main/resources/logback-spring.xml | 123 ++++
.../main/resources/scripts/init_tls_certs.sh | 51 ++
.../resources/scripts/monitor-node.service | 28 +
.../src/main/resources/scripts/print.sh | 73 +++
.../src/main/resources/scripts/start.sh | 139 +++++
.../src/main/resources/scripts/stop.sh | 35 ++
.../ptc/committee/monitor/node/TestBase.java | 192 ++++++
.../monitor/node/dal/BCDNSRepositoryTest.java | 125 ++++
.../dal/EndorseServiceRepositoryTest.java | 162 +++++
.../node/dal/SystemConfigRepositoryTest.java | 73 +++
.../node/server/AdminServiceImplTest.java | 216 +++++++
.../node/server/MonitorNodeServiceTest.java | 580 ++++++++++++++++++
.../node/server/MonitorOrderServiceTest.java | 4 +
.../node/service/BCDNSManageServiceTest.java | 133 ++++
.../node/service/EndorserServiceTest.java | 401 ++++++++++++
.../node/service/HCDVSServiceTest.java | 88 +++
.../src/test/resources/application-test.yml | 43 ++
.../src/test/resources/data/ddl.sql | 93 +++
.../src/test/resources/data/drop_all.sql | 1 +
.../src/test/resources/private_key.pem | 5 +
.../monitor-node/src/test/resources/ptc.crt | 13 +
.../src/test/resources/public_key.pem | 4 +
acb-committeeptc/pom.xml | 2 +
.../server/CrossChainServiceImpl.java | 102 +++
.../server/exception/ServerErrorCodeEnum.java | 12 +
.../src/main/proto/pluginserver.proto | 49 ++
.../relayer/commons/model/BlockchainMeta.java | 4 +
.../manager/bbc/IMonitorClientContract.java | 13 +
.../manager/bbc/ISDPMsgClientContract.java | 2 +
...torClientContractHeteroBlockchainImpl.java | 34 +
.../bbc/SDPMsgClientHeteroBlockchainImpl.java | 5 +
.../manager/blockchain/BlockchainManager.java | 33 +
.../blockchain/AbstractBlockchainClient.java | 3 +
.../blockchain/HeteroBlockchainClient.java | 8 +
.../network/ws/client/generated/Request.java | 8 +-
.../ws/client/generated/RequestResponse.java | 8 +-
.../pluginserver/GRpcBBCServiceClient.java | 90 +++
.../r-core/src/main/proto/pluginserver.proto | 49 ++
.../facade/admin/types/SysContractsInfo.java | 3 +
.../admin/impl/BlockchainNamespace.java | 6 +
.../commons/bbc/AbstractBBCContext.java | 5 +
.../bbc/syscontract/MonitorContract.java | 13 +
.../core/monitor/AbstractMonitorMessage.java | 25 +
.../core/monitor/AbstractMonitorOrder.java | 74 +++
.../commons/core/monitor/IMonitorMessage.java | 12 +
.../commons/core/monitor/IMonitorOrder.java | 25 +
.../core/monitor/MonitorMessageFactory.java | 39 ++
.../core/monitor/MonitorMessageV1.java | 109 ++++
.../core/monitor/MonitorOrderFactory.java | 41 ++
.../commons/core/monitor/MonitorOrderV1.java | 123 ++++
.../exception/CommonsErrorCodeEnum.java | 17 +-
.../core/write/IAntChainBridgeDataWriter.java | 6 +-
.../spi/bbc/core/write/IMonitorWriter.java | 18 +
.../spi/bbc/core/write/ISDPWriter.java | 2 +
.../ethereum2/offchain-plugin/pom.xml | 2 +
.../plugins/ethereum2/EthereumBBCService.java | 204 +++++-
.../ethereum2/conf/EthereumConfig.java | 3 +
.../plugins/ethereum2/core/AcbEthClient.java | 419 ++++++++++++-
.../ethereum2/EthereumBBCServiceTest.java | 295 ++++++++-
.../solidity/sys/AppContract.sol | 23 +-
.../solidity/sys/CommitteePtcVerifier.sol | 4 +-
.../onchain-plugin/solidity/sys/Monitor.sol | 298 +++++++++
.../solidity/sys/MonitorVerifier.sol | 94 +++
.../onchain-plugin/solidity/sys/PtcHub.sol | 15 +-
.../onchain-plugin/solidity/sys/SDPMsg.sol | 33 +-
.../sys/interfaces/IContractUsingMonitor.sol | 10 +
.../solidity/sys/interfaces/IMonitor.sol | 77 +++
.../sys/interfaces/IMonitorVerifier.sol | 32 +
.../solidity/sys/interfaces/IPtcHub.sol | 2 +
.../solidity/sys/interfaces/IPtcVerifier.sol | 2 +-
.../solidity/sys/interfaces/ISDPMessage.sol | 4 +-
.../solidity/sys/interfaces/ISubProtocol.sol | 2 +
.../solidity/sys/lib/monitor/MonitorLib.sol | 136 ++++
.../solidity/sys/lib/ptc/CommitteeLib.sol | 71 ++-
...6\346\265\201\347\250\213\345\233\276.png" | Bin 0 -> 385054 bytes
...0\346\201\257\347\273\223\346\236\204.png" | Bin 0 -> 40790 bytes
...e\347\232\204\347\273\223\346\236\204.png" | Bin 0 -> 15492 bytes
159 files changed, 12658 insertions(+), 68 deletions(-)
create mode 100644 CHANGELOG.md
create mode 100755 acb-committeeptc/monitor-node-cli/README.md
create mode 100755 acb-committeeptc/monitor-node-cli/desc_tar.xml
create mode 100755 acb-committeeptc/monitor-node-cli/pom.xml
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/Launcher.java
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/BaseCommands.java
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/CoreCommands.java
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/UtilsCommands.java
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/config/CustomPromptProvider.java
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/resources/application.yml
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/resources/banner.txt
create mode 100755 acb-committeeptc/monitor-node-cli/src/main/resources/start.sh
create mode 100755 acb-committeeptc/monitor-node/.gitignore
create mode 100755 acb-committeeptc/monitor-node/.mvn/wrapper/maven-wrapper.properties
create mode 100755 acb-committeeptc/monitor-node/README.md
create mode 100755 acb-committeeptc/monitor-node/desc_tar.xml
create mode 100755 acb-committeeptc/monitor-node/mvnw
create mode 100755 acb-committeeptc/monitor-node/mvnw.cmd
create mode 100755 acb-committeeptc/monitor-node/pom.xml
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/NodeApplication.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/CrossChainServiceGrpcClientManager.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/MonitorSystemGrpcClientManager.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorContract.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorSystemRespMsg.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/BCDNSStateEnum.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/MonitorTypeEnum.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/BlockStateNotValidatedYetException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeErrorCodeEnum.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeInternalException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/DataAccessLayerException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidBtaException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidConsensusStateException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidCrossChainMessageException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidRequestException.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BCDNSServiceDO.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BtaWrapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/DomainSpaceCertWrapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/TpBtaWrapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/ValidatedConsensusStateWrapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/CredentialConfig.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/ServerConfig.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/convert/ConvertUtil.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BCDNSServiceEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BaseEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BtaEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/DomainSpaceCertEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/SystemConfigEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/TpBtaEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/ValidatedConsensusStatesEntity.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BCDNSServiceMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BtaMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/DomainSpaceCertMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/SystemConfigMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/TpBtaMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/ValidatedConsensusStatesMapper.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/BCDNSRepositoryImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/EndorseServiceRepositoryImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/SystemConfigRepository.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IBCDNSRepository.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IEndorseServiceRepository.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/ISystemConfigRepository.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/interceptor/RequestTraceInterceptor.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IBCDNSManageService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IEndorserService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IHcdvsPluginService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IMonitorService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IScheduledTaskService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/BCDNSManageService.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/EndorserServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/HcdvsPluginServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/MonitorServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/ScheduledTaskServiceImpl.java
create mode 100755 acb-committeeptc/monitor-node/src/main/proto/admingrpc.proto
create mode 100755 acb-committeeptc/monitor-node/src/main/proto/monitorSystemgrpc.proto
create mode 100755 acb-committeeptc/monitor-node/src/main/proto/pluginserver.proto
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/application.yml
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/banner.txt
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/ddl.sql
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/logback-spring.xml
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/scripts/init_tls_certs.sh
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/scripts/monitor-node.service
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/scripts/print.sh
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/scripts/start.sh
create mode 100755 acb-committeeptc/monitor-node/src/main/resources/scripts/stop.sh
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/TestBase.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/BCDNSRepositoryTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/EndorseServiceRepositoryTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/SystemConfigRepositoryTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImplTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/BCDNSManageServiceTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/EndorserServiceTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/HCDVSServiceTest.java
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/application-test.yml
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/data/ddl.sql
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/data/drop_all.sql
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/private_key.pem
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/ptc.crt
create mode 100755 acb-committeeptc/monitor-node/src/test/resources/public_key.pem
create mode 100755 acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IMonitorClientContract.java
create mode 100755 acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/MonitorClientContractHeteroBlockchainImpl.java
create mode 100755 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/syscontract/MonitorContract.java
create mode 100755 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorMessage.java
create mode 100644 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorOrder.java
create mode 100755 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorMessage.java
create mode 100644 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorOrder.java
create mode 100755 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageFactory.java
create mode 100755 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageV1.java
create mode 100644 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderFactory.java
create mode 100644 acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderV1.java
create mode 100755 acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IMonitorWriter.java
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/Monitor.sol
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/MonitorVerifier.sol
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IContractUsingMonitor.sol
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitor.sol
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitorVerifier.sol
create mode 100755 acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/monitor/MonitorLib.sol
create mode 100644 "docs/images/\345\220\253\347\233\221\347\256\241\347\232\204\350\267\250\351\223\276\346\265\201\347\250\213\345\233\276.png"
create mode 100644 "docs/images/\345\220\253\347\233\221\347\256\241\347\232\204\350\267\250\351\223\276\346\266\210\346\201\257\347\273\223\346\236\204.png"
create mode 100644 "docs/images/\347\233\221\347\256\241\346\214\207\344\273\244\347\261\273\345\236\213monitorOrderType\347\232\204\347\273\223\346\236\204.png"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..a53d9de4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,170 @@
+# [2025-11-06] 本PR所有改动总结
+
+## acb-committeeptc
+### monitor-node
+复制node模块并进行修改,改动如下:
+- monitor.node.client
+ - 与pluginserver和监管系统进行GPRC通信的客户端
+- monitor.node.commons.core 与 monitor.node.commons.enums
+ - 增加监管功能所需的一些配置
+- monitor.node.server
+ - 在MonitorNodeServiceImpl.verifyCrossChainMessage服务中增加事中监管功能
+ - 新增MonitorOrderServiceImpl服务,完成接收监管指令功能
+- monitor.node.service
+ - 完成上述功能的内部实现
+
+### monitor-node-cli
+复制node-cli模块,并只修改了配置文件以适配新模块名。
+
+## acb-relayer
+- 优化r-cli的命令**setup-contract**
+ - 能够一键部署AM SDP PTC Monitor相关的所有合约并完成初始化
+- 优化r-cli的命令**get-blockchain-contracts**
+ - 能够查询AM SDP 以及Monitor合约的地址,其中Monitor合约地址将代替SDP合约地址,在DAPP合约中进行初始化
+
+## acb-pluginserver
+增加了如下接口功能(BBC插件的新增功能与之对应)
+- **SetupMonitorMessage**
+ - 部署Monitor合约和MonitorVerifer合约,并在Monitor合约中初始化MonitorVerifer合约的地址
+- **SetMonitorContract**
+ - 在SDP合约中初始化Monitor合约地址
+- **SetProtocolInMonitor**
+ - 在Monitor合约中初始化SDP合约地址
+- **SetMonitorControl**
+ - 在Monitor合约中初始化变量monitorControl,控制监管开关(默认开)
+- **SetPtcHubInMonitorVerifier**
+ - 在MonitorVerifer合约中初始化PtcHub合约的地址
+- **RelayMonitorOrder**
+ - 将监管指令发送到Monitor合约
+
+## acb-sdk
+- antchain.bridge.commons
+ - 在BBCConext增加了监管合约地址和状态
+ - 增加了对监管合约的序列化和反序列化
+- antchain.bridge.spi
+ - 增加了监管合约所需接口
+- pluginset.ethereum2
+ - offchain
+ - 增加了支持监管合约部署、相关初始化、发送监管指令等接口
+ - onchain
+ - 新增monitor合约:在dapp和sdp合约层之间,完成事前监管,并能接收和存储监管指令,与monitorVerifer合约交互完成监管节点签名的验证
+ - 新增monitorVerifer合约,与monitor交互;接收跨链消息时从ptcHub合约获取监管节点签名
+ - 删除了sys/lib/ptc下的CommitteePtcVerifier.sol,原因如下:
+ - 并没有其他合约import该合约,
+ - 该合约的功能在同目录下的CommitteeLib.sol中已经实现
+ - 在添加功能代码时,如果不删除会导致编译失败
+
+
+## 注意事项
+**如果需要对一条链chain-B下达监管指令,该链必须先接收一条跨链消息。** 原因如下:
+- 监管指令上链时,是由committeeptc中的监管节点直接调用pluginserver的BBC服务来发送交易,不会经过relayer
+- 监管指令在链上需要验证监管节点签名来保证指令的真实性,所以链上需事先存储监管节点的公钥
+- 监管节点的公钥和其他节点一样,存储在ptchub合约接收的的tpbta中
+- 按照antchain的设计,当relayer接收到一条目的链为chain-B的跨链消息时,会检查是否已经上传chain-B最新的tpbta到链上,如果没有则上传
+- 所以如果chain-B没有接收过跨链消息,ptchub合约上就不会存储tpbta,从而无法获取监管节点公钥,无法完成监管指令的签名验证,导致无法成功接收监管指令
+
+
+## 含监管的流程图示
+- 下图为含监管的跨链流程图:
+
+
+
+
+## 其他说明
+
+### 跨链消息结构设计变更说明
+监管合约作为SDP上层合约,封装DApp消息的同时增加监管字段monitor_type和监管信息monitor_msg。
+
+
+
+| monitor_type值(uint32类型) | monitor_type含义 | monitor_msg含义(string类型) |
+| ---------------------- | ------------------------------------------------------------ | --------------------------- |
+| 1 | 发送方发出的不要求监管的跨链消息 | 可选 |
+| 2 | 对于发送方:发出要求监管的跨链消息对于接收方:成功接收到带监管的跨链消息 | 可选 |
+| 3 | 监管未通过,回滚到发送方的监管回滚消息 | 可选,如监管未通过的原因 |
+
+
+### 背书策略配置说明
+为加入antchain的区块链配置背书策略时,监管节点需要设置为true,举例说明如下。
+- 当监管开启时,监管节点会向监管系统请求跨链消息的合法性,**合法则返回一个正确签名,不合法则返回一个空签名**。
+- 当监管关闭时,监管节点的运行逻辑和其他节点完全相同,只是不会在PtcHub合约与monitorVerifier合约进行签名验证。
+```
+{
+ "committee_id": "default",
+ "endorsers": [
+ {
+ "node_id": "node1",
+ "node_public_key": {
+ "key_id": "default",
+ "public_key": ""
+ },
+ "required": true
+ },
+ {
+ "node_id": "monitor-node",
+ "node_public_key": {
+ "key_id": "default",
+ "public_key": ""
+ },
+ "required": true
+ }
+ ],
+ "policy": {
+ "threshold": ">=0"
+ }
+}
+```
+
+
+### 监管指令结构说明
+acb-committeeptc/monitor-node/src/main/proto/monitorSystemgrpc.proto中的监管指令结构如下:
+```
+message MonitorOrder {
+ string product = 1;
+ string domain = 2;
+ uint64 monitorOrderType = 3;
+ string senderDomain = 4;
+ string fromAddress = 5;
+ string receiverDomain = 6;
+ string toAddress = 7;
+ string transactionContent = 8;
+ string extra = 9;
+}
+```
+监管节点会解析出监管指令各字段,并构造包含监管指令的交易发送到指定区块链的监管合约,最终由监管合约更新监管规则。该结构各字段含义如下:
+- product
+ - 监管指令要下发到的区块链的类型,例如etherum2,fiscobcos等
+- domain
+ - 监管指令要下发到的区块链的域名
+- monitorOrderType
+ - 监管指令的类型。该字段长度为32bit,采用了分层编码的设计方式(如下图),分为主类型和子类型,每种主类型标识一种监管维度,每种主类型下分多种子类型
+ - 在当前设计中,每个主类型占1bit,每个子类型占3bit,即每种主类型共有8种子类型
+ - 主类型的具体含义由监管系统定义。以“黑名单”作为主类型来举例,该主类型的子类型可以包含:
+ - 禁止本区块链的应用合约a发送跨链交易;
+ - 禁止本区块链向区块链B的应用合约b发送跨链交易;
+ - 禁止本区块链向区块链B发送跨链交易等。
+
+
+
+- senderDomain
+ - 跨链过程中源区块链域名,处理方式随monitorOrderType含义而变化
+ - 例如监管指令是“禁止某区块链域名发送跨链消息”,则监管合约会把senderDomain加入黑名单,最终效果为该区块链无法在跨链系统发送跨链消息。
+- fromAddress
+ - 跨链过程中源区块链的应用合约地址,处理方式随monitorOrderType含义而变化
+ - 例如监管指令是“禁止某区块链的某应用合约发送跨链消息”,则监管合约会把fromAddress加入黑名单,最终效果为区块链的该应用合约无法在该跨链系统发送跨链消息。
+- receiverDomain
+ - 跨链过程中目的区块链域名,处理方式随monitorOrderType含义而变化
+- toAddress
+ - 跨链过程中目的区块链的应用合约地址,处理方式随monitorOrderType含义而变化。
+- transactionContent
+ - 针对可能要对跨链过程中的原始跨链消息内容本身进行审查而设计了该字段,用于在链上审查跨链消息内容的合规性。
+- extra
+ - 额外信息。用于存放监管系统希望在链上存储的一些监管指令描述,或者上述字段未充分考虑的情况等,也可为空。
+
+目前监管合约中对监管指令的支持,只完成了**合约黑名单**和**控制监管开关**两种功能。
+- 合约黑名单功能对应的监管指令,用二进制表示如下:
+ - **1000** 0000 0000 0000 0000 0000 0000 0000
+ - 即32bit中第一对4bit组表示“黑名单”及其子类型。子类型"000"表示加入黑名单,"001"表示移除出黑名单。
+- 控制监管开关的监管指令,用二进制表示如下:
+ - 0000 **1000** 0000 0000 0000 0000 0000 0000
+ - 即32bit中第二对4bit组表示“监管控制”及其子类型。子类型"000"表示关闭监管,"001"表示开启监管。
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/README.md b/acb-committeeptc/monitor-node-cli/README.md
new file mode 100755
index 00000000..663037e4
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/README.md
@@ -0,0 +1,232 @@
+
+

+
Monitor Node CLI
+
+
+# 介绍
+
+Monitor Node CLI目前功能和Node CLI相同。
+Monitor Node CLI工具是用于管理Committee Monitor Node的交互式命令行工具,它可以完成BCDNS服务注册等工作。
+
+# 使用
+
+## 编译
+
+**在开始之前,请您确保安装了maven和JDK,这里推荐使用[jdk-21](https://adoptium.net/zh-CN/temurin/releases/?version=21)版本**
+
+**确保安装了AntChain Bridge Plugin SDK,详情请[见](acb-sdk/README.md)**
+
+在monitor-node-cli模块根目录运行maven命令即可:
+
+```shell
+cd monitor-node-cli && mvn package -Dmaven.test.skip=true
+```
+
+在`monitor-node-cli/target`目录下会生成一个压缩包`monitor-node-cli-bin.tar.gz`,解压该压缩包即可使用。
+
+解压编译生成包后可以看到文件如下:
+
+```
+./monitor-node-cli
+├── README.md
+├── bin
+│ └── start.sh
+└── lib
+ └── monitor-node-cli.jar
+
+2 directories, 3 files
+```
+
+## 启动
+
+查看脚本帮助信息:
+
+```shell
+$ ./bin/start.sh -h
+
+ start.sh - Start the AntChain Bridge Committee Node Command Line Interface Tool
+
+ Usage:
+ start.sh
+
+ Examples:
+ 1. start with the default server address `localhost` and default port `10088`:
+ start.sh
+ 2. start with specific server address and port:
+ start.sh -H 0.0.0.0 -p 10088
+
+ Options:
+ -H admin server host of committee node.
+ -p admin server port of committee node.
+ -h print help information.
+
+```
+
+启动命令执行情况如下:
+
+```shell
+$ ./r-cli/bin/start.sh
+
+ _ _ ___ ____ _____
+ | \ | | / _ \ | _ \ | ____|
+ | \| || | | || | | || _|
+ | |\ || |_| || |_| || |___
+ |_| \_| \___/ |____/ |_____|
+
+ CLI 0.1.0-SNAPSHOT
+
+monitor-node:>
+```
+
+启动成功后即可在`monitor-node:>`启动符后执行cli命令。
+
+# 命令操作详情
+
+- 直接输入`help`可以查看支持命令概况
+- 直接输入`version`可以查看当前中继CLI工具版本
+- 直接输入`history`可以查询历史命令记录
+
+### add-ptc-trust-root
+
+手动增加PTC Trust Root 到Committee Node存储里。
+
+参数:
+
+- rawPtcTrustRootFile:文件路径,指向包含Base64格式的序列化PTC Trust Root内容的文件;
+
+执行:
+
+```
+add-ptc-trust-root --rawPtcTrustRootFile /path/to/ptctrustroot-file
+```
+
+### register-bcdnsservice
+
+在Committee Node中,注册特定的BCDNS服务,即BCDNS的客户端。
+
+命令参数如下:
+
+- `--bcdnsType`:(必选)BCDNS服务类型,提供`bif`和 `embedded`和两种类型。其中`bif`为目前可用的星火链网BCDNS服务,为中继外部依赖服务,`embedded`为嵌入式BCDNS服务(计划开发中,敬请期待);
+- `--domainSpace`:(可选)当前中继服务的域名空间名,一个域名空间名绑定一个BCDNS服务,该项默认为空字符串,即当前中继的根域名空间名默认为空字符串;
+- `--propFile`:(必选)配置文件路径,即初始化BCDNS服务存根所需的客户端配置文件路径,例如`/path/to/bif_bcdns_conf.json`,该配置文件可以使用`5.3 generate-bif-bcdns-conf`命令生成;
+- `--bcdnsCertPath`:(可选)BCDNS服务的证书路径,可不提供,若未提供该证书命令执行注册时会向BCDNS服务请求证书。
+
+用法如下:
+
+```shell
+relayer:> register-bcdnsservice --bcdnsType bif --propFile /path/to/bif_bcdns_conf.json
+success
+```
+
+### get-bcdnsservice
+
+用于查询指定域名空间中继所绑定的BCDNS服务信息。
+
+命令参数如下:
+
+- `--domainSpace`:(可选)中继的域名空间名,该项默认为空字符串。
+
+用法如下:
+
+```shell
+# 当前中继域名空间名为空字符串,故直接使用默认域名空间名
+relayer:> get-bcdnsservice
+{"domainSpace":"","domainSpaceCertWrapper":{"desc":"","domainSpace":"","domainSpaceCert":{"credentialSubject":"AADhAAAA...YTAifV19","credentialSubjectInstance":{"applicant":{"rawId":"ZGlkOmJp...RENwQw==","type":"BID"},"bcdnsRootOwner":{"$ref":"$.domainSpaceCertWrapper.domainSpaceCert.credentialSubjectInstance.applicant"},"bcdnsRootSubjectInfo":"eyJwdWJs...MCJ9XX0=","name":"root_verifiable_credential","rawSubjectPublicKey":"Q3hxGTc6...i3cJwqA=","subject":"eyJwdWJs...MCJ9XX0=","subjectPublicKey":{"algorithm":"Ed25519","encoded":"MCowBQYDK...i3cJwqA=","format":"X.509","pointEncoding":"Q3hxGTc6...i3cJwqA="}},"encodedToSign":"AACHAQAA...In1dfQ==","expirationDate":1733538286,"id":"did:bid:ef29QeET...2Mzdj8ph","issuanceDate":1702002286,"issuer":{"rawId":"ZGlkOmJp...ZUdNQw==","type":"BID"},"proof":{"certHash":"+9D7B4Eh...vA1cBaE=","hashAlgo":"SM3","rawProof":"RND0SpVq...C6aMDA==","sigAlgo":"Ed25519"},"type":"BCDNS_TRUST_ROOT_CERTIFICATE","version":"1"},"ownerOid":{"rawId":"ZGlkOmJp...RENwQw==","type":"BID"}},"ownerOid":{"rawId":"ZGlkOmJp...RENwQw==","type":"BID"},"properties":"ewogICJj...IH0KfQoK","state":"WORKING","type":"BIF"}
+```
+
+### delete-bcdnsservice
+
+用于删除指定域名空间的中继所绑定的BCDNS服务,删除后可重新绑定其他BCDNS服务。
+
+命令参数如下:
+
+- `--domainSpace`:(可选)中继的域名空间名,该项默认为空字符串。
+
+用法如下:
+```shell
+# 删除BCDNS服务
+relayer:> delete-bcdnsservice
+success
+
+# 查询BCDNS服务
+relayer:> get-bcdnsservice
+not found
+```
+
+### get-bcdnscertificate 查询BCDNS服务证书
+
+用于查询指定域名空间的中继所绑定的BCDNS服务的证书。
+
+命令参数如下:
+
+- `--domainSpace`:(可选)中继的域名空间名,该项默认为空字符串。
+
+用法如下:
+
+```shell
+relayer:> get-bcdnscertificate
+-----BEGIN BCDNS TRUST ROOT CERTIFICATE-----
+AAAVAgAAAAABAAAAMQEAKQAAAGRpZDpiaWQ6ZWYyOVFlRVRRcDVnOHdabXBLRTNR
+......
+pp1tvNQJKwumjAw=
+-----END BCDNS TRUST ROOT CERTIFICATE-----
+
+```
+
+### stop-bcdnsservice 停止BCDNS服务
+
+用于停止指定域名空间的中继所绑定的BCDNS服务的运行。
+
+命令参数如下:
+
+- `--domainSpace`:(可选)中继的域名空间名,该项默认为空字符串。
+
+用法如下:
+
+```shell
+# 停止BCDNS服务
+relayer:> stop-bcdnsservice
+success
+
+# 停止后查看BCDNS服务信息可以看到,信息详情中的状态为`FROZEN`
+relayer:> get-bcdnsservice
+{"domainSpace":"","domainSpaceCertWrapper":{......},"ownerOid":{......},"properties":"......","state":"FROZEN","type":"BIF"}
+```
+
+### restart-bcdnsservice 重启BCDNS服务
+
+用于重新启动指定域名空间的中继所绑定的BCDNS服务。
+
+命令参数如下:
+
+- `--domainSpace`:(可选)中继的域名空间名,该项默认为空字符串。
+
+用法如下:
+
+```shell
+# 重启BCDNS服务
+relayer:> restart-bcdnsservice
+success
+
+# 停止后查看BCDNS服务信息可以看到,信息详情中的状态为`WORKING`
+relayer:> get-bcdnsservice
+{"domainSpace":"","domainSpaceCertWrapper":{......},"ownerOid":{......},"properties":"......","state":"WORKING","type":"BIF"}
+```
+
+### generate-node-account
+
+Committee Node初始化时,用来生成节点需要的私钥和公钥文件,后续将用来完成背书签名✍️。
+
+参数:
+
+- keyAlgo:私钥的算法,支持:SECP256K1、RSA、ECDSA(secp256r1)、SM2、ED25519;
+- outDir:密钥文件存储的文件夹路径,默认当前路径;
+
+用法如下:
+
+```
+node:> generate-node-account --keyAlgo SECP256K1 --outDir ./
+private key path: /path/to/./private_key.pem
+public key path: /path/to/./public_key.pem
+```
+
diff --git a/acb-committeeptc/monitor-node-cli/desc_tar.xml b/acb-committeeptc/monitor-node-cli/desc_tar.xml
new file mode 100755
index 00000000..47e0f299
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/desc_tar.xml
@@ -0,0 +1,32 @@
+
+ bin
+
+ tar.gz
+
+ true
+
+
+ ${project.build.directory}
+ ${file.separator}lib
+
+ monitor-node-cli.jar
+
+
+
+ ${project.basedir}/src/main/resources
+ ${file.separator}bin
+
+ *.sh
+
+
+
+ ${project.basedir}
+ ${file.separator}
+
+ README.md
+
+
+
+
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/pom.xml b/acb-committeeptc/monitor-node-cli/pom.xml
new file mode 100755
index 00000000..38726a79
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/pom.xml
@@ -0,0 +1,173 @@
+
+
+
+
+ 4.0.0
+
+ com.alipay.antchain.bridge
+ committee-ptc
+ 0.1.0-SNAPSHOT
+
+
+ monitor-node-cli
+ 0.1.0-SNAPSHOT
+ monitor-node-cli
+ monitor node cli
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.shell
+ spring-shell-starter
+
+
+ cn.hutool
+ hutool-all
+
+
+ net.devh
+ grpc-client-spring-boot-starter
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-commons
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+
+
+
+
+ monitor-node-cli
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.5.0.Final
+
+
+
+
+ src/main/resources
+
+ **/application.yml
+ **/*.xml
+ **/banner.txt
+
+
+ true
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-plugin.version}
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+ ${project.basedir}/../monitor-node/src/main/proto
+
+
+ **/admingrpc.proto
+
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ com.alipay.antchain.bridge.ptc.committee.monitor.node.cli.Launcher
+
+
+
+ build-info
+
+ build-info
+
+
+
+ ${java.version}
+ ${project.description}
+
+
+
+
+
+ repackage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.1.0
+
+
+
+
+ desc_tar.xml
+
+
+ make-tar
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/Launcher.java b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/Launcher.java
new file mode 100755
index 00000000..8bcdb224
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/Launcher.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication(scanBasePackages = {"com.alipay.antchain.bridge.ptc.committee.monitor.node"})
+public class Launcher {
+ public static void main(String[] args) {
+ List propList = new ArrayList<>();
+ if (ObjectUtil.isNotEmpty(args)) {
+
+ List argsList = ListUtil.toList(args);
+
+ var port = argsList.stream().filter(x -> StrUtil.startWith(x, "--port")).findAny().orElse("");
+ var portNum = 10088;
+ if (StrUtil.isNotEmpty(port)) {
+ portNum = Integer.parseInt(StrUtil.split(port, "=").get(1));
+ }
+
+ var host = argsList.stream().filter(x -> StrUtil.startWith(x, "--host")).findAny().orElse("");
+ var hostVal = "127.0.0.1";
+ if (StrUtil.isNotEmpty(host)) {
+ hostVal = StrUtil.split(host, "=").get(1);
+ }
+
+ propList.add(StrUtil.format("grpc.client.admin.address=static://{}:{}", hostVal, portNum));
+
+ argsList = argsList.stream().filter(x -> !StrUtil.startWithAny(x, "--port", "--host")).toList();
+ args = argsList.toArray(new String[0]);
+ }
+ new SpringApplicationBuilder(Launcher.class)
+ .web(WebApplicationType.NONE)
+ .properties(propList.toArray(new String[0]))
+ .run(args);
+ }
+}
diff --git a/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/BaseCommands.java b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/BaseCommands.java
new file mode 100755
index 00000000..54445927
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/BaseCommands.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.cli.commands;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.shell.Availability;
+import org.springframework.shell.standard.ShellMethodAvailability;
+
+public abstract class BaseCommands {
+
+ public abstract String getAdminAddress();
+
+ public abstract boolean needAdminServer();
+
+ @ShellMethodAvailability
+ public Availability baseAvailability() {
+ if (needAdminServer()) {
+ var addrArr = StrUtil.split(StrUtil.split(getAdminAddress(), "//").get(1), ":");
+
+ if (!checkServerStatus(addrArr.get(0), Integer.parseInt(addrArr.get(1)))) {
+ return Availability.unavailable(
+ StrUtil.format("admin server {} is unreachable", getAdminAddress())
+ );
+ }
+ }
+
+ return Availability.available();
+ }
+
+ private boolean checkServerStatus(String host, int port) {
+ try {
+ Socket socket = new Socket(host, port);
+ socket.close();
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/CoreCommands.java b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/CoreCommands.java
new file mode 100755
index 00000000..ffcf23c9
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/CoreCommands.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.cli.commands;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.grpc.*;
+import com.google.protobuf.ByteString;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.shell.standard.*;
+
+@Getter
+@ShellCommandGroup(value = "Commands about core functions")
+@ShellComponent
+@Slf4j
+public class CoreCommands extends BaseCommands {
+
+ @Value("${grpc.client.admin.address:static://localhost:10088}")
+ private String adminAddress;
+
+ @GrpcClient("admin")
+ private AdminServiceGrpc.AdminServiceBlockingStub adminServiceBlockingStub;
+
+ @Override
+ public boolean needAdminServer() {
+ return true;
+ }
+
+ @ShellMethod(value = "Register a new BCDNS bound with specified domain space into Relayer")
+ Object registerBCDNSService(
+ @ShellOption(help = "The domain space owned by the BCDNS, default the root space \"\"", defaultValue = "") String domainSpace,
+ @ShellOption(help = "The type of the BCDNS, e.g. embedded, bif") String bcdnsType,
+ @ShellOption(valueProvider = FileValueProvider.class, help = "The properties file path needed to initialize the service stub, e.g. /path/to/bif_bcdns_conf.json") String propFile,
+ @ShellOption(valueProvider = FileValueProvider.class, help = "The path to BCDNS trust root certificate file if you have it", defaultValue = "") String bcdnsCertPath
+ ) {
+ try {
+ var resp = adminServiceBlockingStub.registerBcdnsService(
+ RegisterBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .setBcdnsType(bcdnsType)
+ .setConfig(ByteString.copyFrom(Files.readAllBytes(Paths.get(propFile))))
+ .setBcdnsRootCert(StrUtil.isEmpty(bcdnsCertPath) ? "" : Files.readString(Paths.get(bcdnsCertPath)))
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to register BCDNS service: " + resp.getErrorMsg();
+ }
+ return "success";
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Get the BCDNS data bound with specified domain space")
+ Object getBCDNSService(@ShellOption(help = "The domain space bound with BCDNS, default the root space", defaultValue = "") String domainSpace) {
+ try {
+ var resp = adminServiceBlockingStub.getBcdnsServiceInfo(
+ GetBcdnsServiceInfoRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to get BCDNS service info: " + resp.getErrorMsg();
+ }
+ return resp.getGetBcdnsServiceInfoResp().getInfoJson();
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Delete the BCDNS bound with specified domain space")
+ Object deleteBCDNSService(@ShellOption(help = "The domain space bound with BCDNS, default the root space", defaultValue = "") String domainSpace) {
+ try {
+ var resp = adminServiceBlockingStub.deleteBcdnsService(
+ DeleteBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to delete BCDNS service: " + resp.getErrorMsg();
+ }
+ return "success";
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Get the BCDNS trust root certificate bound with specified domain space")
+ Object getBCDNSCertificate(@ShellOption(help = "The domain space bound with BCDNS, default the root space", defaultValue = "") String domainSpace) {
+ try {
+ var resp = adminServiceBlockingStub.getBcdnsCertificate(
+ GetBcdnsCertificateRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to get BCDNS certificate: " + resp.getErrorMsg();
+ }
+ return resp.getGetBcdnsCertificateResp().getCertificate();
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Stop the local BCDNS service stub")
+ Object stopBCDNSService(@ShellOption(help = "The domain space bound with BCDNS, default the root space", defaultValue = "") String domainSpace) {
+ try {
+ var resp = adminServiceBlockingStub.stopBcdnsService(
+ StopBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to stop BCDNS: " + resp.getErrorMsg();
+ }
+ return "success";
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Restart the local BCDNS service stub from stop")
+ Object restartBCDNSService(@ShellOption(help = "domain space, default the root space", defaultValue = "") String domainSpace) {
+ try {
+ var resp = adminServiceBlockingStub.restartBcdnsService(
+ RestartBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(domainSpace)
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to restart BCDNS: " + resp.getErrorMsg();
+ }
+ return "success";
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+
+ @ShellMethod(value = "Add committee-ptc trust root manually")
+ Object addPtcTrustRoot(
+ @ShellOption(help = "file path leading to the serialized PTCTrustRoot which has been signed by supervisor")
+ String rawPtcTrustRootFile
+ ) {
+ try {
+ var filePath = Path.of(rawPtcTrustRootFile);
+ if (!Files.exists(filePath)) {
+ return "file not exists";
+ }
+
+ var resp = adminServiceBlockingStub.addPtcTrustRoot(
+ AddPtcTrustRootRequest.newBuilder()
+ .setRawTrustRoot(ByteString.copyFrom(Files.readAllBytes(filePath)))
+ .build()
+ );
+ if (resp.getCode() != 0) {
+ return "failed to add ptc trust root: " + resp.getErrorMsg();
+ }
+ return "success";
+ } catch (Throwable t) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", t);
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/UtilsCommands.java b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/UtilsCommands.java
new file mode 100755
index 00000000..f24caf70
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/commands/UtilsCommands.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.cli.commands;
+
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.*;
+
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.shell.standard.*;
+
+@Getter
+@ShellCommandGroup(value = "Utils Commands")
+@ShellComponent
+public class UtilsCommands extends BaseCommands {
+
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @Value("${grpc.client.admin.address:static://localhost:10088}")
+ private String adminAddress;
+
+ @Override
+ public boolean needAdminServer() {
+ return false;
+ }
+
+ @ShellMethod(value = "Generate PEM files for the node private and public key")
+ public String generateNodeAccount(
+ @ShellOption(help = "Key algorithm, default SECP256K1", defaultValue = "SECP256K1") String keyAlgo,
+ @ShellOption(valueProvider = FileValueProvider.class, help = "Directory path to save the keys", defaultValue = "") String outDir
+ ) {
+ try {
+ var keyPair = SignAlgoEnum.getSignAlgoByKeySuffix(keyAlgo).getSigner().generateKeyPair();
+
+ // dump the private key into pem
+ Path privatePath = Paths.get(outDir, "private_key.pem");
+ writePrivateKey(keyPair.getPrivate(), privatePath);
+
+ // dump the public key into pem
+ Path publicPath = Paths.get(outDir, "public_key.pem");
+ writePublicKey(keyPair.getPublic(), publicPath);
+
+ return StrUtil.format("private key path: {}\npublic key path: {}", privatePath.toAbsolutePath(), publicPath.toAbsolutePath());
+ } catch (Exception e) {
+ throw new RuntimeException("unexpected error please input stacktrace to check the detail", e);
+ }
+ }
+
+ @SneakyThrows
+ private void writePrivateKey(PrivateKey privateKey, Path outputFile) {
+ // dump the private key into pem
+ StringWriter stringWriter = new StringWriter(256);
+ JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(stringWriter);
+ jcaPEMWriter.writeObject(privateKey);
+ jcaPEMWriter.close();
+ String privatePem = stringWriter.toString();
+ Files.write(outputFile, privatePem.getBytes());
+ }
+
+ @SneakyThrows
+ private void writePublicKey(PublicKey publicKey, Path outputFile) {
+ // dump the public key into pem
+ StringWriter stringWriter = new StringWriter(256);
+ JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(stringWriter);
+ jcaPEMWriter.writeObject(publicKey);
+ jcaPEMWriter.close();
+ String pubkeyPem = stringWriter.toString();
+ Files.write(outputFile, pubkeyPem.getBytes());
+ }
+}
diff --git a/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/config/CustomPromptProvider.java b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/config/CustomPromptProvider.java
new file mode 100755
index 00000000..69392dee
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/cli/config/CustomPromptProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.cli.config;
+
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStyle;
+import org.springframework.shell.jline.PromptProvider;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CustomPromptProvider implements PromptProvider {
+ @Override
+ public AttributedString getPrompt() {
+ return new AttributedString("monitor-node:> ", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
+ }
+}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/src/main/resources/application.yml b/acb-committeeptc/monitor-node-cli/src/main/resources/application.yml
new file mode 100755
index 00000000..4f7580ed
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/resources/application.yml
@@ -0,0 +1,13 @@
+spring:
+ shell:
+ interactive:
+ enabled: true
+ history:
+ name: ".monitor-node-cli-history"
+logging:
+ level:
+ root: off
+grpc:
+ client:
+ admin:
+ negotiation-type: plaintext
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/src/main/resources/banner.txt b/acb-committeeptc/monitor-node-cli/src/main/resources/banner.txt
new file mode 100755
index 00000000..c75b2738
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/resources/banner.txt
@@ -0,0 +1,7 @@
+ __ __ ___ _ _ ___ _____ ___ ___ _ _ ___ ___ ___
+| \/ | / _ \ | \| | |_ _| |_ _| / _ \ | _ \ ___ | \| | / _ \ | \ | __|
+| |\/| | | (_) | | .` | | | | | | (_) | | / |___| | .` | | (_) | | |) | | _|
+|_| |_| \___/ |_|\_| |___| _|_|_ \___/ |_|_\ |_|\_| \___/ |___/ |___|
+
+${AnsiStyle.BOLD} CLI @project.version@
+${AnsiStyle.NORMAL}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node-cli/src/main/resources/start.sh b/acb-committeeptc/monitor-node-cli/src/main/resources/start.sh
new file mode 100755
index 00000000..1563ae2b
--- /dev/null
+++ b/acb-committeeptc/monitor-node-cli/src/main/resources/start.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+bin=`dirname "${BASH_SOURCE-$0}"`
+CLI_HOME=`cd "$bin"; pwd`
+
+Help=$(
+ cat <<-"HELP"
+
+ start.sh - Start the AntChain Bridge Monitor Node Command Line Interface Tool
+
+ Usage:
+ start.sh
+
+ Examples:
+ 1. start with the default server address `localhost` and default port `10088`:
+ start.sh
+ 2. start with specific server address and port:
+ start.sh -H 0.0.0.0 -p 10088
+
+ Options:
+ -H admin server host of monitor node.
+ -p admin server port of monitor node.
+ -h print help information.
+
+HELP
+)
+
+CURR_DIR="$(
+ cd $(dirname $0)
+ pwd
+)"
+
+while getopts "hH:p:" opt; do
+ case "$opt" in
+ "h")
+ echo "$Help"
+ exit 0
+ ;;
+ "H")
+ SERVER_HOST=$OPTARG
+ ;;
+ "p")
+ SERVER_PORT=$OPTARG
+ ;;
+ "?")
+ echo "invalid arguments. "
+ exit 1
+ ;;
+ *)
+ echo "Unknown error while processing options"
+ exit 1
+ ;;
+ esac
+done
+
+
+which java > /dev/null
+if [ $? -eq 1 ]; then
+ echo "no java installed. "
+ exit 1
+fi
+
+java -jar ${CLI_HOME}/../lib/monitor-node-cli.jar --port=${SERVER_PORT:-10088} --host=${SERVER_HOST:-"localhost"}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/.gitignore b/acb-committeeptc/monitor-node/.gitignore
new file mode 100755
index 00000000..549e00a2
--- /dev/null
+++ b/acb-committeeptc/monitor-node/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/acb-committeeptc/monitor-node/.mvn/wrapper/maven-wrapper.properties b/acb-committeeptc/monitor-node/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 00000000..8f96f52c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip
diff --git a/acb-committeeptc/monitor-node/README.md b/acb-committeeptc/monitor-node/README.md
new file mode 100755
index 00000000..32a90a12
--- /dev/null
+++ b/acb-committeeptc/monitor-node/README.md
@@ -0,0 +1,22 @@
+
+
+# 介绍
+
+Committee Monitor Node是存在于委员会中的监管节点,与本跨链系统外的监管系统提供交互接口,完成跨链消息事中监管和监管指令下达的服务。
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/desc_tar.xml b/acb-committeeptc/monitor-node/desc_tar.xml
new file mode 100755
index 00000000..575768b4
--- /dev/null
+++ b/acb-committeeptc/monitor-node/desc_tar.xml
@@ -0,0 +1,48 @@
+
+ ${version}
+
+ tar.gz
+
+ true
+
+
+ ${project.basedir}/target
+ ${file.separator}lib
+
+ ${artifactId}-${version}.jar
+
+
+
+ ${project.basedir}/src/main/resources/scripts
+ ${file.separator}bin
+
+ *.sh
+ monitor-node.service
+
+
+
+ ${project.basedir}/src/main/resources
+ ${file.separator}config
+
+ application.yml
+ ddl.sql
+
+
+
+ ${project.basedir}
+ ${file.separator}
+
+ README.md
+
+
+
+ ${project.basedir}/../monitor-node-cli/target
+ ${file.separator}
+
+ monitor-node-cli-bin.tar.gz
+
+
+
+
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/mvnw b/acb-committeeptc/monitor-node/mvnw
new file mode 100755
index 00000000..d7c358e5
--- /dev/null
+++ b/acb-committeeptc/monitor-node/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/acb-committeeptc/monitor-node/mvnw.cmd b/acb-committeeptc/monitor-node/mvnw.cmd
new file mode 100755
index 00000000..6f779cff
--- /dev/null
+++ b/acb-committeeptc/monitor-node/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/acb-committeeptc/monitor-node/pom.xml b/acb-committeeptc/monitor-node/pom.xml
new file mode 100755
index 00000000..2445721a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/pom.xml
@@ -0,0 +1,235 @@
+
+
+ 4.0.0
+
+ com.alipay.antchain.bridge
+ committee-ptc
+ 0.1.0-SNAPSHOT
+
+
+ monitor-node
+ 0.1.0-SNAPSHOT
+ monitor-node
+ monitor node
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 21
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-spi
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-ptc
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-bcdns
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-bcdns-factory
+
+
+ com.alipay.antchain.bridge
+ antchain-bridge-plugin-manager
+
+
+ com.alipay.antchain.bridge
+ committee-ptc-core
+
+
+ net.devh
+ grpc-server-spring-boot-starter
+
+
+ io.grpc
+ grpc-api
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ com.mysql
+ mysql-connector-j
+
+
+ protobuf-java
+ com.google.protobuf
+
+
+
+
+ cn.hutool
+ hutool-all
+
+
+ com.github.ulisesbocchio
+ jasypt-spring-boot-starter
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.baomidou
+ mybatis-plus-boot-starter-test
+ test
+
+
+ javax.annotation
+ javax.annotation-api
+
+
+ com.github.ulisesbocchio
+ jasypt-spring-boot-starter
+
+
+
+ junit
+ junit
+ test
+
+
+ com.h2database
+ h2
+ test
+
+
+ io.grpc
+ grpc-testing
+ test
+
+
+ net.devh
+ grpc-client-spring-boot-starter
+
+
+ io.grpc
+ grpc-api
+
+
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.0
+
+
+
+
+ src/main/resources
+
+ **/application.yml
+ **/*.xml
+ **/banner.txt
+
+
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.1.0
+
+
+
+ monitor-node
+
+ desc_tar.xml
+
+
+ make-tar
+ package
+
+ single
+
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-plugin.version}
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+ ${project.basedir}/src/main/proto/
+
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+ com.github.ulisesbocchio
+ jasypt-maven-plugin
+ 3.0.5
+
+
+
+
+
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/NodeApplication.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/NodeApplication.java
new file mode 100755
index 00000000..b686e208
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/NodeApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node;
+
+import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication(scanBasePackages = {"com.alipay.antchain.bridge.ptc.committee.monitor.node"})
+@MapperScan("com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper")
+@EnableEncryptableProperties
+@EnableScheduling
+public class NodeApplication {
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(NodeApplication.class)
+ .web(WebApplicationType.NONE)
+ .run(args);
+ }
+
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/CrossChainServiceGrpcClientManager.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/CrossChainServiceGrpcClientManager.java
new file mode 100755
index 00000000..ccce0f88
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/CrossChainServiceGrpcClientManager.java
@@ -0,0 +1,101 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.client;
+
+import java.io.FileInputStream;
+import java.util.*;
+
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.pluginserver.service.CrossChainServiceGrpc;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.TlsChannelCredentials;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.security.auth.x500.X500Principal;
+import java.io.InputStream;
+
+@Component
+public class CrossChainServiceGrpcClientManager {
+
+ @Value("${grpc.clients.plugin-server.host}")
+ private String host;
+
+ @Value("${grpc.clients.plugin-server.port}")
+ private int port;
+
+ @Value("${grpc.clients.plugin-server.ps-id}")
+ private String psId;
+
+ @Value("${grpc.clients.plugin-server.security.pluginServerCert}")
+ private Resource psCertResource;
+
+ @Value("${grpc.clients.plugin-server.security.certificate-chain}")
+ private Resource tlsCaResource;
+
+ @Value("${grpc.clients.plugin-server.security.private-key}")
+ private Resource tlsKeyResource;
+
+ private final Map blockingStubMap = new HashMap<>();
+
+ public CrossChainServiceGrpc.CrossChainServiceBlockingStub createStub(String clientName) {
+
+ String commonName = getPluginServerCertX509CommonName(psCertResource);
+ if (StrUtil.isEmpty(commonName)) {
+ throw new RuntimeException(
+ String.format("failed to get common name from x509 subject for plugin server %s", psId)
+ );
+ }
+
+ ManagedChannel channel;
+ try {
+ TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder();
+ tlsBuilder.keyManager(tlsCaResource.getInputStream(), tlsKeyResource.getInputStream());
+ tlsBuilder.trustManager(psCertResource.getInputStream());
+ channel = NettyChannelBuilder.forAddress(host, port, tlsBuilder.build())
+ .overrideAuthority(commonName)
+ .build();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to create client for psId %s", psId)
+ );
+ }
+
+ return CrossChainServiceGrpc.newBlockingStub(channel);
+ }
+
+ public CrossChainServiceGrpc.CrossChainServiceBlockingStub getStub(String clientName) {
+ if (this.blockingStubMap.containsKey(clientName)) {
+ return this.blockingStubMap.get(clientName);
+ }
+ this.blockingStubMap.put(clientName, createStub(clientName));
+ return this.blockingStubMap.get(clientName);
+ }
+
+ private static String getPluginServerCertX509CommonName(Resource certResource) {
+ try (InputStream is = certResource.getInputStream()) {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+
+ X500Principal principal = cert.getSubjectX500Principal();
+ String dn = principal.getName();
+
+ LdapName ldapName = new LdapName(dn);
+ String commonName = null;
+ for (Rdn rdn : ldapName.getRdns()) {
+ if ("CN".equalsIgnoreCase(rdn.getType())) {
+ commonName = rdn.getValue().toString();
+ break;
+ }
+ }
+ return commonName;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to get common name from certificate file", e);
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/MonitorSystemGrpcClientManager.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/MonitorSystemGrpcClientManager.java
new file mode 100755
index 00000000..fa0e1922
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/client/MonitorSystemGrpcClientManager.java
@@ -0,0 +1,41 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.client;
+
+import java.util.*;
+
+import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc.MonitorSystemServiceGrpc;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MonitorSystemGrpcClientManager {
+
+ @Value("${grpc.clients.monitor-system.host:localhost}")
+ private String host;
+
+ @Value("${grpc.clients.monitor-system.port:50051}")
+ private int port;
+
+ private final Map blockingStubMap = new HashMap<>();
+
+ public MonitorSystemServiceGrpc.MonitorSystemServiceBlockingStub createStub(String clientName) {
+
+ // 创建 gRPC 通道
+ ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
+ .usePlaintext() // 使用明文传输
+ .build();
+
+ // 创建并返回 gRPC 阻塞存根
+ return MonitorSystemServiceGrpc.newBlockingStub(channel);
+ }
+
+ public MonitorSystemServiceGrpc.MonitorSystemServiceBlockingStub getStub(String clientName) {
+ if (this.blockingStubMap.containsKey(clientName)) {
+ return this.blockingStubMap.get(clientName);
+ }
+ this.blockingStubMap.put(clientName, createStub(clientName));
+ return this.blockingStubMap.get(clientName);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorContract.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorContract.java
new file mode 100755
index 00000000..9b4d213c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorContract.java
@@ -0,0 +1,18 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.core;
+
+import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class MonitorContract {
+
+ private String monitorContractAddress;
+
+ private ContractStatusEnum status;
+}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorSystemRespMsg.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorSystemRespMsg.java
new file mode 100755
index 00000000..ee4d77b2
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/core/MonitorSystemRespMsg.java
@@ -0,0 +1,26 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.core;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class MonitorSystemRespMsg {
+ @JSONField(name = "result")
+ private int result;
+
+ @JSONField(name = "error_msg")
+ private String errorMsg;
+
+ public byte[] encode() {
+ return JSON.toJSONBytes(this);
+ }
+
+ public static MonitorSystemRespMsg decode(byte[] rawData) {
+ return JSON.parseObject(rawData, MonitorSystemRespMsg.class);
+ }
+}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/BCDNSStateEnum.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/BCDNSStateEnum.java
new file mode 100755
index 00000000..d85d814c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/BCDNSStateEnum.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum BCDNSStateEnum {
+
+ WORKING(0),
+
+ FROZEN(1);
+
+ @EnumValue
+ private final Integer code;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/MonitorTypeEnum.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/MonitorTypeEnum.java
new file mode 100755
index 00000000..50d3df2c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/enums/MonitorTypeEnum.java
@@ -0,0 +1,21 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum MonitorTypeEnum {
+
+ NONE(0),
+
+ MONITOR_CLOSE(1),
+
+ MONITOR_OPEN(2),
+
+ MONITOR_ROLLBACK(3),
+
+ MONITOR_ORDER(4);
+
+ private final int code;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/BlockStateNotValidatedYetException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/BlockStateNotValidatedYetException.java
new file mode 100755
index 00000000..5cc59eff
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/BlockStateNotValidatedYetException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class BlockStateNotValidatedYetException extends CommitteeNodeException {
+
+ public BlockStateNotValidatedYetException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_ENDORSE_BLOCK_STATE_ERROR, longMsg);
+ }
+
+ public BlockStateNotValidatedYetException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_ENDORSE_BLOCK_STATE_ERROR, StrUtil.format(formatStr, objects));
+ }
+
+ public BlockStateNotValidatedYetException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_ENDORSE_BLOCK_STATE_ERROR, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public BlockStateNotValidatedYetException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_ENDORSE_BLOCK_STATE_ERROR, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeErrorCodeEnum.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeErrorCodeEnum.java
new file mode 100755
index 00000000..8f639d4c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeErrorCodeEnum.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import lombok.Getter;
+
+@Getter
+public enum CommitteeNodeErrorCodeEnum {
+
+ UNKNOWN_INTERNAL_ERROR("0001", "internal error"),
+
+ DAL_ERROR("0101", "data access layer error"),
+
+ SERVER_INVALID_REQUEST("0201", "invalid request"),
+
+ SERVER_VERIFY_BTA_ERROR("0202", "verify bta error"),
+
+ SERVER_VERIFY_CONSENSUS_STATE_ERROR("0203", "verify consensus state error"),
+
+ SERVER_VERIFY_CROSSCHAIN_MESSAGE_ERROR("0204", "verify crosschain message error"),
+
+ SERVER_ENDORSE_BLOCK_STATE_ERROR("0205", "block state not validated yet");
+
+ /**
+ * Error code for errors happened in project {@code antchain-bridge-committee-node}
+ */
+ private final String errorCode;
+
+ private final int errorCodeNum;
+
+ /**
+ * Every code has a short message to describe the error stuff
+ */
+ private final String shortMsg;
+
+ CommitteeNodeErrorCodeEnum(String errorCode, String shortMsg) {
+ this.errorCode = errorCode;
+ this.errorCodeNum = Integer.parseInt(errorCode, 16);
+ this.shortMsg = shortMsg;
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeException.java
new file mode 100755
index 00000000..fb68d9f0
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.commons.exception.base.AntChainBridgeBaseException;
+
+public class CommitteeNodeException extends AntChainBridgeBaseException {
+
+ public CommitteeNodeException(CommitteeNodeErrorCodeEnum errorCode, String longMsg) {
+ super(errorCode.getErrorCode(), errorCode.getShortMsg(), longMsg);
+ }
+
+ public CommitteeNodeException(CommitteeNodeErrorCodeEnum errorCode, String formatStr, Object... objects) {
+ super(errorCode.getErrorCode(), errorCode.getShortMsg(), StrUtil.format(formatStr, objects));
+ }
+
+ public CommitteeNodeException(CommitteeNodeErrorCodeEnum errorCode, Throwable throwable, String formatStr, Object... objects) {
+ super(errorCode.getErrorCode(), errorCode.getShortMsg(), StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public CommitteeNodeException(CommitteeNodeErrorCodeEnum errorCode, String longMsg, Throwable throwable) {
+ super(errorCode.getErrorCode(), errorCode.getShortMsg(), longMsg, throwable);
+ }
+
+ public int getCodeNum() {
+ return Integer.parseInt(this.getCode(), 16);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeInternalException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeInternalException.java
new file mode 100755
index 00000000..71bbc898
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/CommitteeNodeInternalException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class CommitteeNodeInternalException extends CommitteeNodeException {
+
+ public CommitteeNodeInternalException(String message) {
+ super(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, message);
+ }
+
+ public CommitteeNodeInternalException(String format, Object... args) {
+ super(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, StrUtil.format(format, args));
+ }
+
+ public CommitteeNodeInternalException(Throwable t, String format, Object... args) {
+ super(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, StrUtil.format(format, args), t);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/DataAccessLayerException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/DataAccessLayerException.java
new file mode 100755
index 00000000..300fd760
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/DataAccessLayerException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class DataAccessLayerException extends CommitteeNodeException {
+
+ public DataAccessLayerException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.DAL_ERROR, longMsg);
+ }
+
+ public DataAccessLayerException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.DAL_ERROR, StrUtil.format(formatStr, objects));
+ }
+
+ public DataAccessLayerException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.DAL_ERROR, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public DataAccessLayerException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.DAL_ERROR, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidBtaException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidBtaException.java
new file mode 100755
index 00000000..7dccba1c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidBtaException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class InvalidBtaException extends CommitteeNodeException {
+
+ public InvalidBtaException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_BTA_ERROR, longMsg);
+ }
+
+ public InvalidBtaException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_BTA_ERROR, StrUtil.format(formatStr, objects));
+ }
+
+ public InvalidBtaException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_BTA_ERROR, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public InvalidBtaException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_BTA_ERROR, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidConsensusStateException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidConsensusStateException.java
new file mode 100755
index 00000000..9583e6d2
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidConsensusStateException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class InvalidConsensusStateException extends CommitteeNodeException {
+
+ public InvalidConsensusStateException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CONSENSUS_STATE_ERROR, longMsg);
+ }
+
+ public InvalidConsensusStateException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CONSENSUS_STATE_ERROR, StrUtil.format(formatStr, objects));
+ }
+
+ public InvalidConsensusStateException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CONSENSUS_STATE_ERROR, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public InvalidConsensusStateException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CONSENSUS_STATE_ERROR, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidCrossChainMessageException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidCrossChainMessageException.java
new file mode 100755
index 00000000..c4fe7dcd
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidCrossChainMessageException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class InvalidCrossChainMessageException extends CommitteeNodeException {
+
+ public InvalidCrossChainMessageException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CROSSCHAIN_MESSAGE_ERROR, longMsg);
+ }
+
+ public InvalidCrossChainMessageException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CROSSCHAIN_MESSAGE_ERROR, StrUtil.format(formatStr, objects));
+ }
+
+ public InvalidCrossChainMessageException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CROSSCHAIN_MESSAGE_ERROR, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public InvalidCrossChainMessageException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_VERIFY_CROSSCHAIN_MESSAGE_ERROR, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidRequestException.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidRequestException.java
new file mode 100755
index 00000000..d93de952
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/exception/InvalidRequestException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception;
+
+import cn.hutool.core.util.StrUtil;
+
+public class InvalidRequestException extends CommitteeNodeException {
+
+ public InvalidRequestException(String longMsg) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_INVALID_REQUEST, longMsg);
+ }
+
+ public InvalidRequestException(String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_INVALID_REQUEST, StrUtil.format(formatStr, objects));
+ }
+
+ public InvalidRequestException(Throwable throwable, String formatStr, Object... objects) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_INVALID_REQUEST, StrUtil.format(formatStr, objects), throwable);
+ }
+
+ public InvalidRequestException(String longMsg, Throwable throwable) {
+ super(CommitteeNodeErrorCodeEnum.SERVER_INVALID_REQUEST, longMsg, throwable);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BCDNSServiceDO.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BCDNSServiceDO.java
new file mode 100755
index 00000000..1500d5ee
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BCDNSServiceDO.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.JSONSerializer;
+import com.alibaba.fastjson.serializer.ObjectSerializer;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class BCDNSServiceDO {
+
+ private String domainSpace;
+
+ @JSONField(serialize = false, deserialize = false)
+ private ObjectIdentity ownerOid;
+
+ @JSONField(name = "domainSpaceCert", serializeUsing = DomainSpaceCertSerializer.class)
+ private DomainSpaceCertWrapper domainSpaceCertWrapper;
+
+ private BCDNSTypeEnum type;
+
+ private BCDNSStateEnum state;
+
+ private byte[] properties;
+
+ public static class DomainSpaceCertSerializer implements ObjectSerializer {
+ @Override
+ public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
+ serializer.write(CrossChainCertificateUtil.formatCrossChainCertificateToPem(((DomainSpaceCertWrapper) object).getDomainSpaceCert()));
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BtaWrapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BtaWrapper.java
new file mode 100755
index 00000000..688b0220
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/BtaWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.bta.IBlockchainTrustAnchor;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class BtaWrapper {
+
+ private IBlockchainTrustAnchor bta;
+
+ public String getDomain() {
+ return ObjectUtil.defaultIfNull(bta.getDomain(), new CrossChainDomain()).getDomain();
+ }
+
+ public String getProduct() {
+ return bta.getSubjectProduct();
+ }
+
+ public int getSubjectVersion() {
+ return bta.getSubjectVersion();
+ }
+
+ public int getBtaVersion() {
+ return bta.getVersion();
+ }
+
+
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/DomainSpaceCertWrapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/DomainSpaceCertWrapper.java
new file mode 100755
index 00000000..ee617c39
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/DomainSpaceCertWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models;
+
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class DomainSpaceCertWrapper {
+
+ public DomainSpaceCertWrapper(AbstractCrossChainCertificate domainSpaceCert) {
+ this.domainSpaceCert = domainSpaceCert;
+ this.domainSpace = CrossChainCertificateUtil.isBCDNSTrustRoot(domainSpaceCert) ?
+ CrossChainDomain.ROOT_DOMAIN_SPACE : CrossChainCertificateUtil.getCrossChainDomainSpace(domainSpaceCert).getDomain();
+ this.parentDomainSpace = CrossChainCertificateUtil.isBCDNSTrustRoot(domainSpaceCert) ?
+ null : CrossChainCertificateUtil.getParentDomainSpace(domainSpaceCert).getDomain();
+ this.ownerOid = domainSpaceCert.getCredentialSubjectInstance().getApplicant();
+ }
+
+ private String domainSpace;
+
+ private String parentDomainSpace;
+
+ private ObjectIdentity ownerOid;
+
+ private AbstractCrossChainCertificate domainSpaceCert;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/TpBtaWrapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/TpBtaWrapper.java
new file mode 100755
index 00000000..e1e0c56a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/TpBtaWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models;
+
+import com.alipay.antchain.bridge.commons.core.base.CrossChainLane;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeEndorseProof;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.CommitteeEndorseRoot;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class TpBtaWrapper {
+
+ private ThirdPartyBlockchainTrustAnchor tpbta;
+
+ public CommitteeEndorseRoot getEndorseRoot() {
+ return CommitteeEndorseRoot.decode(tpbta.getEndorseRoot());
+ }
+
+ public CommitteeEndorseProof getEndorseProof() {
+ return CommitteeEndorseProof.decode(tpbta.getEndorseProof());
+ }
+
+ public CrossChainLane getCrossChainLane() {
+ return tpbta.getCrossChainLane();
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/ValidatedConsensusStateWrapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/ValidatedConsensusStateWrapper.java
new file mode 100755
index 00000000..2c2015aa
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/commons/models/ValidatedConsensusStateWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models;
+
+import java.math.BigInteger;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.ptc.ValidatedConsensusState;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class ValidatedConsensusStateWrapper {
+
+ private ValidatedConsensusState validatedConsensusState;
+
+ public String getDomain() {
+ return ObjectUtil.defaultIfNull(validatedConsensusState.getDomain(), new CrossChainDomain()).getDomain();
+ }
+
+ public BigInteger getHeight() {
+ return validatedConsensusState.getHeight();
+ }
+
+ public String getParentHash() {
+ return validatedConsensusState.getParentHashHex();
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/CredentialConfig.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/CredentialConfig.java
new file mode 100755
index 00000000..2ae40338
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/CredentialConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.config;
+
+import java.security.PrivateKey;
+
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+
+@Configuration
+@Getter
+public class CredentialConfig {
+
+ @Bean
+ @SneakyThrows
+ public PrivateKey nodeKey(
+ @Value("${committee.node.credential.sign-algo:KECCAK256_WITH_SECP256K1}") SignAlgoEnum signAlgo,
+ @Value("${committee.node.credential.private-key-file}") Resource privateKeyFile) {
+ return signAlgo.getSigner().readPemPrivateKey(privateKeyFile.getContentAsByteArray());
+ }
+
+ @Bean
+ @SneakyThrows
+ public AbstractCrossChainCertificate ptcCrossChainCert(
+ @Value("${committee.node.credential.cert-file}") Resource certFile) {
+ return CrossChainCertificateUtil.readCrossChainCertificateFromPem(certFile.getContentAsByteArray());
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/ServerConfig.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/ServerConfig.java
new file mode 100755
index 00000000..1984f517
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/config/ServerConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.config;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.AdminServiceImpl;
+import io.grpc.Server;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Slf4j
+public class ServerConfig {
+
+ @Value("${committee.node.admin.host:localhost}")
+ private String adminHost;
+
+ @Value("${committee.node.admin.port:10088}")
+ private int adminPort;
+
+ @Bean
+ @SneakyThrows
+ public Server adminGrpcServer(@Autowired AdminServiceImpl adminService) {
+ log.info("Starting plugin managing server on port {}", adminPort);
+ return NettyServerBuilder.forAddress(
+ new InetSocketAddress(
+ StrUtil.isEmpty(adminHost) ? InetAddress.getLoopbackAddress() : InetAddress.getByName(adminHost),
+ adminPort
+ )
+ ).addService(adminService)
+ .build()
+ .start();
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/convert/ConvertUtil.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/convert/ConvertUtil.java
new file mode 100755
index 00000000..e67657de
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/convert/ConvertUtil.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.convert;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorFactory;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.core.ptc.ValidatedConsensusState;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.*;
+
+public class ConvertUtil {
+
+ @SuppressWarnings("unchecked")
+ public static T convertFrom(F from) {
+ return switch (from) {
+ case TpBtaEntity entity -> (T) convertFrom(entity);
+ case TpBtaWrapper wrapper -> (T) convertFrom(wrapper);
+ case BtaEntity entity -> (T) convertFrom(entity);
+ case BtaWrapper wrapper -> (T) convertFrom(wrapper);
+ case ValidatedConsensusStatesEntity entity -> (T) convertFrom(entity);
+ case ValidatedConsensusStateWrapper wrapper -> (T) convertFrom(wrapper);
+ case BCDNSServiceDO serviceDO -> (T) convertFrom(serviceDO);
+ case BCDNSServiceEntity entity -> (T) convertFrom(entity);
+ case DomainSpaceCertWrapper wrapper -> (T) convertFrom(wrapper);
+ case DomainSpaceCertEntity entity -> (T) convertFrom(entity);
+ default -> throw new IllegalArgumentException("Unsupported type: " + from.getClass().getName());
+ };
+ }
+
+ private static TpBtaWrapper convertFrom(TpBtaEntity entity) {
+ return new TpBtaWrapper(
+ ThirdPartyBlockchainTrustAnchor.decode(entity.getRawTpBta())
+ );
+ }
+
+ private static TpBtaEntity convertFrom(TpBtaWrapper wrapper) {
+ return TpBtaEntity.builder()
+ .tpbtaVersion(wrapper.getTpbta().getTpbtaVersion())
+ .ptcVerifyAnchorVersion(wrapper.getTpbta().getPtcVerifyAnchorVersion().longValue())
+ .btaSubjectVersion(wrapper.getTpbta().getBtaSubjectVersion())
+ .senderDomain(wrapper.getCrossChainLane().getSenderDomain().getDomain())
+ .senderId(ObjectUtil.isNull(wrapper.getCrossChainLane().getSenderId()) ? "" : wrapper.getCrossChainLane().getSenderIdHex())
+ .receiverDomain(ObjectUtil.isNull(wrapper.getCrossChainLane().getReceiverDomain()) ? "" : wrapper.getCrossChainLane().getReceiverDomain().getDomain())
+ .receiverId(ObjectUtil.isNull(wrapper.getCrossChainLane().getReceiverId()) ? "" : wrapper.getCrossChainLane().getReceiverIdHex())
+ .rawTpBta(wrapper.getTpbta().encode())
+ .build();
+ }
+
+ private static BtaWrapper convertFrom(BtaEntity entity) {
+ return new BtaWrapper(BlockchainTrustAnchorFactory.createBTA(entity.getRawBta()));
+ }
+
+ private static BtaEntity convertFrom(BtaWrapper wrapper) {
+ return BtaEntity.builder()
+ .domain(wrapper.getDomain())
+ .product(wrapper.getProduct())
+ .btaVersion(wrapper.getBtaVersion())
+ .subjectVersion(wrapper.getSubjectVersion())
+ .rawBta(wrapper.getBta().encode())
+ .build();
+ }
+
+ private static ValidatedConsensusStateWrapper convertFrom(ValidatedConsensusStatesEntity entity) {
+ return new ValidatedConsensusStateWrapper(ValidatedConsensusState.decode(entity.getRawVcs()));
+ }
+
+ private static ValidatedConsensusStatesEntity convertFrom(ValidatedConsensusStateWrapper wrapper) {
+ return ValidatedConsensusStatesEntity.builder()
+ .csVersion(wrapper.getValidatedConsensusState().getCsVersion())
+ .hash(HexUtil.encodeHexStr(wrapper.getValidatedConsensusState().getHash()))
+ .height(wrapper.getValidatedConsensusState().getHeight().toString())
+ .domain(wrapper.getDomain())
+ .rawVcs(wrapper.getValidatedConsensusState().encode())
+ .parentHash(wrapper.getParentHash())
+ .build();
+ }
+
+
+ private static BCDNSServiceEntity convertFrom(BCDNSServiceDO serviceDO) {
+ return BCDNSServiceEntity.builder()
+ .domainSpace(serviceDO.getDomainSpace())
+ .type(serviceDO.getType().getCode())
+ .parentSpace(serviceDO.getDomainSpaceCertWrapper().getParentDomainSpace())
+ .ownerOid(HexUtil.encodeHexStr(serviceDO.getOwnerOid().encode()))
+ .state(serviceDO.getState())
+ .properties(serviceDO.getProperties())
+ .build();
+ }
+
+ private static BCDNSServiceDO convertFrom(BCDNSServiceEntity entity) {
+ return new BCDNSServiceDO(
+ entity.getDomainSpace(),
+ ObjectIdentity.decode(HexUtil.decodeHex(entity.getOwnerOid())),
+ null,
+ BCDNSTypeEnum.parseFromValue(entity.getType()),
+ entity.getState(),
+ entity.getProperties()
+ );
+ }
+
+ private static DomainSpaceCertEntity convertFrom(DomainSpaceCertWrapper wrapper) {
+ return DomainSpaceCertEntity.builder()
+ .domainSpace(wrapper.getDomainSpace())
+ .parentSpace(wrapper.getParentDomainSpace())
+ .ownerOidHex(HexUtil.encodeHexStr(wrapper.getOwnerOid().encode()))
+ .domainSpaceCert(wrapper.getDomainSpaceCert().encode())
+ .build();
+ }
+
+ private static DomainSpaceCertWrapper convertFrom(DomainSpaceCertEntity entity) {
+ return new DomainSpaceCertWrapper(CrossChainCertificateFactory.createCrossChainCertificate(entity.getDomainSpaceCert()));
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BCDNSServiceEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BCDNSServiceEntity.java
new file mode 100755
index 00000000..cdb9e546
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BCDNSServiceEntity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName("bcdns_service")
+public class BCDNSServiceEntity extends BaseEntity {
+
+ @TableField("domain_space")
+ private String domainSpace;
+
+ @TableField("parent_space")
+ private String parentSpace;
+
+ @TableField("owner_oid")
+ private String ownerOid;
+
+ @TableField("type")
+ private String type;
+
+ @TableField("state")
+ private BCDNSStateEnum state;
+
+ @TableField("properties")
+ private byte[] properties;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BaseEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BaseEntity.java
new file mode 100755
index 00000000..0b8cb696
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BaseEntity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.FieldNameConstants;
+
+@Getter
+@Setter
+@FieldNameConstants
+public class BaseEntity {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 数据创建时间
+ */
+ @TableField("gmt_create")
+ private Date gmtCreate;
+
+ /**
+ * 数据更新时间
+ */
+ @TableField(value = "gmt_modified", update = "now()", updateStrategy = FieldStrategy.ALWAYS)
+ private Date gmtModified;
+
+ public void init() {
+ Date now = new Date();
+ this.setGmtCreate(now);
+ this.setGmtModified(now);
+ }
+
+ public void update() {
+ this.setGmtModified(new Date());
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BtaEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BtaEntity.java
new file mode 100755
index 00000000..fc27d1bd
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/BtaEntity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@TableName("bta")
+public class BtaEntity extends BaseEntity {
+
+ @TableField("product")
+ private String product;
+
+ @TableField("domain")
+ private String domain;
+
+ @TableField("bta_version")
+ private int btaVersion;
+
+ @TableField("subject_version")
+ private int subjectVersion;
+
+ @TableField("raw_bta")
+ private byte[] rawBta;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/DomainSpaceCertEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/DomainSpaceCertEntity.java
new file mode 100755
index 00000000..6fe98dcb
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/DomainSpaceCertEntity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("domain_space_cert")
+public class DomainSpaceCertEntity extends BaseEntity {
+
+ @TableField("domain_space")
+ private String domainSpace;
+
+ @TableField("parent_space")
+ private String parentSpace;
+
+ @TableField("owner_oid_hex")
+ private String ownerOidHex;
+
+ @TableField("description")
+ private String description;
+
+ @TableField("domain_space_cert")
+ private byte[] domainSpaceCert;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/SystemConfigEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/SystemConfigEntity.java
new file mode 100755
index 00000000..ca2e3bc2
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/SystemConfigEntity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Builder
+@AllArgsConstructor
+@TableName("system_config")
+public class SystemConfigEntity extends BaseEntity {
+ @TableField("conf_key")
+ private String confKey;
+
+ @TableField("conf_value")
+ private String confValue;
+}
+
+
+
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/TpBtaEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/TpBtaEntity.java
new file mode 100755
index 00000000..45c68c3e
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/TpBtaEntity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@TableName("tpbta")
+public class TpBtaEntity extends BaseEntity {
+
+ @TableField("version")
+ private int version;
+
+ @TableField("sender_domain")
+ private String senderDomain;
+
+ @TableField("bta_subject_version")
+ private int btaSubjectVersion;
+
+ @TableField("sender_id")
+ private String senderId;
+
+ @TableField("receiver_domain")
+ private String receiverDomain;
+
+ @TableField("receiver_id")
+ private String receiverId;
+
+ @TableField("tpbta_version")
+ private int tpbtaVersion;
+
+ @TableField("ptc_verify_anchor_version")
+ private long ptcVerifyAnchorVersion;
+
+ @TableField("raw_tpbta")
+ private byte[] rawTpBta;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/ValidatedConsensusStatesEntity.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/ValidatedConsensusStatesEntity.java
new file mode 100755
index 00000000..0f443f5a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/entities/ValidatedConsensusStatesEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@TableName("validated_consensus_states")
+public class ValidatedConsensusStatesEntity extends BaseEntity {
+
+ @TableField("cs_version")
+ private short csVersion;
+
+ @TableField("domain")
+ private String domain;
+
+ @TableField("height")
+ private String height;
+
+ @TableField("hash")
+ private String hash;
+
+ @TableField("parent_hash")
+ private String parentHash;
+
+ @TableField("raw_vcs")
+ private byte[] rawVcs;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BCDNSServiceMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BCDNSServiceMapper.java
new file mode 100755
index 00000000..b05c1512
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BCDNSServiceMapper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.BCDNSServiceEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface BCDNSServiceMapper extends BaseMapper {
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BtaMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BtaMapper.java
new file mode 100755
index 00000000..f5b615f2
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/BtaMapper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.BtaEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface BtaMapper extends BaseMapper {
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/DomainSpaceCertMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/DomainSpaceCertMapper.java
new file mode 100755
index 00000000..300107a0
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/DomainSpaceCertMapper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.DomainSpaceCertEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface DomainSpaceCertMapper extends BaseMapper {
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/SystemConfigMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/SystemConfigMapper.java
new file mode 100755
index 00000000..ce909a22
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/SystemConfigMapper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.SystemConfigEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface SystemConfigMapper extends BaseMapper {
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/TpBtaMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/TpBtaMapper.java
new file mode 100755
index 00000000..efaf17c7
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/TpBtaMapper.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.TpBtaEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+public interface TpBtaMapper extends BaseMapper {
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/ValidatedConsensusStatesMapper.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/ValidatedConsensusStatesMapper.java
new file mode 100755
index 00000000..bb28695e
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/mapper/ValidatedConsensusStatesMapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper;
+
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.ValidatedConsensusStatesEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+public interface ValidatedConsensusStatesMapper extends BaseMapper {
+
+ @Select("SELECT * FROM validated_consensus_states WHERE domain = #{domain} AND height = (SELECT MAX(height) FROM validated_consensus_states WHERE domain = #{domain})")
+ ValidatedConsensusStatesEntity getLatestValidatedConsensusState(@Param("domain") String domain);
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/BCDNSRepositoryImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/BCDNSRepositoryImpl.java
new file mode 100755
index 00000000..92aa2ca6
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/BCDNSRepositoryImpl.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.DataAccessLayerException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.convert.ConvertUtil;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.BCDNSServiceEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.DomainSpaceCertEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.BCDNSServiceMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.DomainSpaceCertMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Slf4j
+public class BCDNSRepositoryImpl implements IBCDNSRepository {
+
+ @Resource
+ private BCDNSServiceMapper bcdnsServiceMapper;
+
+ @Resource
+ private DomainSpaceCertMapper domainSpaceCertMapper;
+
+ @Override
+ public long countBCDNSService() {
+ return bcdnsServiceMapper.selectCount(null);
+ }
+
+ @Override
+ public boolean hasDomainSpaceCert(String domainSpace) {
+ return domainSpaceCertMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(DomainSpaceCertEntity::getDomainSpace, domainSpace)
+ );
+ }
+
+ @Override
+ public void saveDomainSpaceCert(DomainSpaceCertWrapper domainSpaceCertWrapper) {
+ if (hasDomainSpaceCert(domainSpaceCertWrapper.getDomainSpace())) {
+ log.info("domain space cert for {} already exists", domainSpaceCertWrapper.getDomainSpace());
+ return;
+ }
+
+ try {
+ domainSpaceCertMapper.insert((DomainSpaceCertEntity) ConvertUtil.convertFrom(domainSpaceCertWrapper));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "save domain space cert for {} failed", domainSpaceCertWrapper.getDomainSpace()
+ );
+ }
+ }
+
+ @Override
+ public DomainSpaceCertWrapper getDomainSpaceCert(String domainSpace) {
+ try {
+ var entity = domainSpaceCertMapper.selectOne(
+ new LambdaQueryWrapper()
+ .select(ListUtil.toList(DomainSpaceCertEntity::getDomainSpaceCert))
+ .eq(DomainSpaceCertEntity::getDomainSpace, domainSpace)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return new DomainSpaceCertWrapper(
+ CrossChainCertificateFactory.createCrossChainCertificate(entity.getDomainSpaceCert())
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "get domain space cert for {} failed", domainSpace
+ );
+ }
+ }
+
+ @Override
+ public DomainSpaceCertWrapper getDomainSpaceCert(ObjectIdentity ownerOid) {
+ try {
+ var entityList = domainSpaceCertMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(DomainSpaceCertEntity::getOwnerOidHex, HexUtil.encodeHexStr(ownerOid.encode()))
+ );
+ if (ObjectUtil.isEmpty(entityList)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(entityList.getFirst());
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to get domain space certificate by owner oid {}",
+ HexUtil.encodeHexStr(ownerOid.encode())
+ );
+ }
+ }
+
+ @Override
+ public Map getDomainSpaceCertChain(String leafDomainSpace) {
+ Map result = new HashMap<>();
+ try {
+ addDomainSpaceCert(leafDomainSpace, result);
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to get domain space certificate chain for space {}",
+ leafDomainSpace
+ );
+ }
+
+ return result;
+ }
+
+ @Override
+ public List getDomainSpaceChain(String leafDomainSpace) {
+ List result = ListUtil.toList(leafDomainSpace);
+ String currDomainSpace = leafDomainSpace;
+ try {
+ do {
+ if (StrUtil.equals(leafDomainSpace, CrossChainDomain.ROOT_DOMAIN_SPACE)) {
+ break;
+ }
+ DomainSpaceCertEntity entity = domainSpaceCertMapper.selectOne(
+ new LambdaQueryWrapper()
+ .select(ListUtil.toList(DomainSpaceCertEntity::getParentSpace))
+ .eq(DomainSpaceCertEntity::getDomainSpace, leafDomainSpace)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ throw new RuntimeException(StrUtil.format("none data found for domain space {}", currDomainSpace));
+ }
+ result.add(entity.getParentSpace());
+ currDomainSpace = entity.getParentSpace();
+ } while (StrUtil.isNotEmpty(currDomainSpace));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to get domain space certificate chain for space {}",
+ leafDomainSpace
+ );
+ }
+ return result;
+ }
+
+ @Override
+ public boolean hasBCDNSService(String domainSpace) {
+ try {
+ return bcdnsServiceMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(BCDNSServiceEntity::getDomainSpace, domainSpace)
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "check if bcdns service for {} exist failed", domainSpace
+ );
+ }
+ }
+
+ @Override
+ public BCDNSServiceDO getBCDNSServiceDO(String domainSpace) {
+ try {
+ var entity = bcdnsServiceMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(BCDNSServiceEntity::getDomainSpace, domainSpace)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ BCDNSServiceDO bcdnsServiceDO = ConvertUtil.convertFrom(entity);
+ bcdnsServiceDO.setDomainSpaceCertWrapper(getDomainSpaceCert(domainSpace));
+ return bcdnsServiceDO;
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "get bcdns service for {} failed", domainSpace
+ );
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = DataAccessLayerException.class)
+ public void deleteBCDNSServiceDO(String domainSpace) {
+ try {
+ domainSpaceCertMapper.delete(
+ new LambdaQueryWrapper()
+ .eq(DomainSpaceCertEntity::getDomainSpace, domainSpace)
+ );
+ bcdnsServiceMapper.delete(
+ new LambdaQueryWrapper()
+ .eq(BCDNSServiceEntity::getDomainSpace, domainSpace)
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to delete bcdns data for space [{}]",
+ domainSpace
+ );
+ }
+ }
+
+ @Override
+ public List getAllBCDNSDomainSpace() {
+ try {
+ var domainSpaceCertEntities = domainSpaceCertMapper.selectList(
+ new LambdaQueryWrapper()
+ .select(ListUtil.toList(DomainSpaceCertEntity::getDomainSpace))
+ );
+ if (ObjectUtil.isEmpty(domainSpaceCertEntities)) {
+ return new ArrayList<>();
+ }
+
+ return domainSpaceCertEntities.stream().map(DomainSpaceCertEntity::getDomainSpace).collect(Collectors.toList());
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to get all domain space for bcdns"
+ );
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = DataAccessLayerException.class)
+ public void saveBCDNSServiceDO(BCDNSServiceDO bcdnsServiceDO) {
+ try {
+ bcdnsServiceMapper.insert((BCDNSServiceEntity) ConvertUtil.convertFrom(bcdnsServiceDO));
+ domainSpaceCertMapper.insert((DomainSpaceCertEntity) ConvertUtil.convertFrom(bcdnsServiceDO.getDomainSpaceCertWrapper()));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "save bcdns service failed, domainSpace = {}",
+ bcdnsServiceDO.getDomainSpace()
+ );
+ }
+ }
+
+ @Override
+ public void updateBCDNSServiceState(String domainSpace, BCDNSStateEnum stateEnum) {
+ try {
+ bcdnsServiceMapper.update(
+ new LambdaUpdateWrapper()
+ .eq(BCDNSServiceEntity::getDomainSpace, domainSpace)
+ .set(BCDNSServiceEntity::getState, stateEnum.getCode())
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "update bcdns service state to {} failed, domainSpace = {}",
+ stateEnum.name(), domainSpace
+ );
+ }
+ }
+
+ @Override
+ public void updateBCDNSServiceProperties(String domainSpace, byte[] rawProp) {
+ try {
+ if (
+ bcdnsServiceMapper.update(
+ BCDNSServiceEntity.builder()
+ .properties(rawProp)
+ .build(),
+ new LambdaQueryWrapper()
+ .eq(BCDNSServiceEntity::getDomainSpace, domainSpace)
+ ) != 1
+ ) {
+ throw new RuntimeException("failed to update bcdns record");
+ }
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e,
+ "failed to update bcdns properties to {} for space {}",
+ Base64.encode(rawProp), domainSpace
+ );
+ }
+ }
+
+ private void addDomainSpaceCert(String currDomainSpace, Map result) {
+ DomainSpaceCertEntity entity = domainSpaceCertMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(DomainSpaceCertEntity::getDomainSpace, currDomainSpace)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ throw new RuntimeException(StrUtil.format("domain space cert not found for {}", currDomainSpace));
+ }
+ result.put(currDomainSpace, ConvertUtil.convertFrom(entity));
+ if (!StrUtil.equals(currDomainSpace, CrossChainDomain.ROOT_DOMAIN_SPACE)) {
+ addDomainSpaceCert(entity.getParentSpace(), result);
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/EndorseServiceRepositoryImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/EndorseServiceRepositoryImpl.java
new file mode 100755
index 00000000..be290dda
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/EndorseServiceRepositoryImpl.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository;
+
+import java.math.BigInteger;
+import java.util.Comparator;
+import java.util.List;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainLane;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.DataAccessLayerException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.convert.ConvertUtil;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.BtaEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.TpBtaEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.ValidatedConsensusStatesEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.BtaMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.TpBtaMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.ValidatedConsensusStatesMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EndorseServiceRepositoryImpl implements IEndorseServiceRepository {
+
+ @Resource
+ private BtaMapper btaMapper;
+
+ @Resource
+ private TpBtaMapper tpBtaMapper;
+
+ @Resource
+ private ValidatedConsensusStatesMapper validatedConsensusStatesMapper;
+
+ @Override
+ public TpBtaWrapper getMatchedTpBta(CrossChainLane lane) {
+ try {
+ var entityList = searchTpBta(lane, -1);
+ 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 {}", lane.getLaneKey()
+ );
+ }
+ }
+
+ @Override
+ public TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion) {
+ try {
+ var entityList = searchTpBta(lane, tpbtaVersion);
+ 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 {} and version {}", lane.getLaneKey(), tpbtaVersion
+ );
+ }
+ }
+
+ @Override
+ public TpBtaWrapper getExactTpBta(CrossChainLane lane) {
+ return getExactTpBta(lane, -1);
+ }
+
+ @Override
+ public TpBtaWrapper getExactTpBta(CrossChainLane lane, int tpbtaVersion) {
+ try {
+ var wrapper = new LambdaQueryWrapper()
+ .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain())
+ .eq(TpBtaEntity::getSenderId, ObjectUtil.isNull(lane.getSenderId()) ? "" : lane.getSenderId().toHex())
+ .eq(TpBtaEntity::getReceiverDomain, ObjectUtil.isNull(lane.getReceiverDomain()) ? "" : lane.getReceiverDomain().getDomain())
+ .eq(TpBtaEntity::getReceiverId, ObjectUtil.isNull(lane.getReceiverId()) ? "" : lane.getReceiverId().toHex());
+ var entityList = tpBtaMapper.selectList(
+ tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion)
+ );
+ 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 {} and version {}", lane.getLaneKey(), tpbtaVersion
+ );
+ }
+ }
+
+ @Override
+ public void setTpBta(TpBtaWrapper tpBtaWrapper) {
+ try {
+ if (hasTpBta(tpBtaWrapper.getCrossChainLane(), tpBtaWrapper.getTpbta().getTpbtaVersion())) {
+ throw new RuntimeException("tpBta already exists");
+ }
+ tpBtaMapper.insert((TpBtaEntity) ConvertUtil.convertFrom(tpBtaWrapper));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to save tpbta for lane {}", tpBtaWrapper.getCrossChainLane().getLaneKey()
+ );
+ }
+ }
+
+ @Override
+ public boolean hasTpBta(CrossChainLane lane, int tpbtaVersion) {
+ try {
+ return tpBtaMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain())
+ .eq(TpBtaEntity::getSenderId, ObjectUtil.isNull(lane.getSenderId()) ? "" : lane.getSenderId().toHex())
+ .eq(TpBtaEntity::getReceiverDomain, ObjectUtil.isNull(lane.getReceiverDomain()) ? "" : lane.getReceiverDomain().getDomain())
+ .eq(TpBtaEntity::getReceiverId, ObjectUtil.isNull(lane.getReceiverId()) ? "" : lane.getReceiverId().toHex())
+ .eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion)
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to check if tpbta for lane {} and version {} exist", lane.getLaneKey(), tpbtaVersion
+ );
+ }
+ }
+
+ @Override
+ public BtaWrapper getBta(String domain) {
+ try {
+ var entityList = btaMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(BtaEntity::getDomain, domain)
+ );
+ if (ObjectUtil.isEmpty(entityList)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(
+ entityList.stream().max(Comparator.comparingInt(BtaEntity::getSubjectVersion)).get()
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get bta for domain {}", domain
+ );
+ }
+ }
+
+ @Override
+ public BtaWrapper getBta(String domain, int subjectVersion) {
+ try {
+ var entity = btaMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(BtaEntity::getDomain, domain)
+ .eq(BtaEntity::getSubjectVersion, subjectVersion)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(entity);
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get bta for domain {} and version {}", domain, subjectVersion
+ );
+ }
+ }
+
+ @Override
+ public void setBta(BtaWrapper btaWrapper) {
+ try {
+ if (hasBta(btaWrapper.getDomain(), btaWrapper.getBtaVersion())) {
+ throw new RuntimeException("bta already exists");
+ }
+ btaMapper.insert((BtaEntity) ConvertUtil.convertFrom(btaWrapper));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to save bta for domain {} and subject version {}", btaWrapper.getDomain(), btaWrapper.getSubjectVersion()
+ );
+ }
+ }
+
+ @Override
+ public boolean hasBta(String domain, int subjectVersion) {
+ try {
+ return btaMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(BtaEntity::getDomain, domain)
+ .eq(BtaEntity::getSubjectVersion, subjectVersion)
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to check if bta for domain {} and version {} exist", domain, subjectVersion
+ );
+ }
+ }
+
+ @Override
+ public ValidatedConsensusStateWrapper getLatestValidatedConsensusState(String domain) {
+ try {
+ var entity = validatedConsensusStatesMapper.getLatestValidatedConsensusState(domain);
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(entity);
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get latest validated consensus state for domain {}", domain
+ );
+ }
+ }
+
+ @Override
+ public ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, BigInteger height) {
+ try {
+ var entity = validatedConsensusStatesMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(ValidatedConsensusStatesEntity::getDomain, domain)
+ .eq(ValidatedConsensusStatesEntity::getHeight, height.toString())
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(entity);
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get validated consensus state for domain {} and height {}", domain, height
+ );
+ }
+ }
+
+ @Override
+ public ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, String hash) {
+ try {
+ var entity = validatedConsensusStatesMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(ValidatedConsensusStatesEntity::getDomain, domain)
+ .eq(ValidatedConsensusStatesEntity::getHash, hash)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return ConvertUtil.convertFrom(entity);
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get validated consensus state for domain {} and hash {}", domain, hash
+ );
+ }
+ }
+
+ @Override
+ public void setValidatedConsensusState(ValidatedConsensusStateWrapper validatedConsensusStateWrapper) {
+ try {
+ if (hasValidatedConsensusState(validatedConsensusStateWrapper.getDomain(), validatedConsensusStateWrapper.getHeight())) {
+ throw new RuntimeException("validated consensus state already exists");
+ }
+ validatedConsensusStatesMapper.insert((ValidatedConsensusStatesEntity) ConvertUtil.convertFrom(validatedConsensusStateWrapper));
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to save validated consensus state for domain {} and height {}",
+ validatedConsensusStateWrapper.getDomain(),
+ validatedConsensusStateWrapper.getHeight()
+ );
+ }
+ }
+
+ @Override
+ public boolean hasValidatedConsensusState(String domain, BigInteger height) {
+ try {
+ return validatedConsensusStatesMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(ValidatedConsensusStatesEntity::getDomain, domain)
+ .eq(ValidatedConsensusStatesEntity::getHeight, height.toString())
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to check if validated consensus state for domain {} and height {} exist", domain, height
+ );
+ }
+ }
+
+ private List searchTpBta(CrossChainLane lane, int tpbtaVersion) {
+ // search the blockchain level first
+ var wrapper = new LambdaQueryWrapper()
+ .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain())
+ .eq(TpBtaEntity::getReceiverDomain, "")
+ .eq(TpBtaEntity::getSenderId, "")
+ .eq(TpBtaEntity::getReceiverId, "");
+ var entityList = tpBtaMapper.selectList(
+ tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion)
+ );
+ if (ObjectUtil.isNotEmpty(entityList)) {
+ return entityList;
+ }
+
+ if (ObjectUtil.isNull(lane.getReceiverDomain()) || ObjectUtil.isEmpty(lane.getReceiverDomain().getDomain())) {
+ return ListUtil.empty();
+ }
+ // search the channel level
+ wrapper = new LambdaQueryWrapper()
+ .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain())
+ .eq(TpBtaEntity::getReceiverDomain, lane.getReceiverDomain().getDomain())
+ .eq(TpBtaEntity::getSenderId, "")
+ .eq(TpBtaEntity::getReceiverId, "");
+ entityList = tpBtaMapper.selectList(
+ tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion)
+ );
+ if (ObjectUtil.isNotEmpty(entityList)) {
+ return entityList;
+ }
+
+ if (ObjectUtil.isNull(lane.getSenderId()) || ObjectUtil.isNull(lane.getReceiverId())
+ || ObjectUtil.isEmpty(lane.getSenderId().getRawID()) || ObjectUtil.isEmpty(lane.getReceiverId().getRawID())) {
+ return ListUtil.empty();
+ }
+ // search the lane level
+ wrapper = new LambdaQueryWrapper()
+ .eq(TpBtaEntity::getSenderDomain, lane.getSenderDomain().getDomain())
+ .eq(TpBtaEntity::getSenderId, lane.getSenderId().toHex())
+ .eq(TpBtaEntity::getReceiverDomain, lane.getReceiverDomain().getDomain())
+ .eq(TpBtaEntity::getReceiverId, lane.getReceiverId().toHex());
+ entityList = tpBtaMapper.selectList(
+ tpbtaVersion == -1 ? wrapper : wrapper.eq(TpBtaEntity::getTpbtaVersion, tpbtaVersion)
+ );
+ if (ObjectUtil.isNotEmpty(entityList)) {
+ return entityList;
+ }
+
+ return ListUtil.empty();
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/SystemConfigRepository.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/SystemConfigRepository.java
new file mode 100755
index 00000000..16811b14
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/SystemConfigRepository.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.DataAccessLayerException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.entities.SystemConfigEntity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.mapper.SystemConfigMapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import jakarta.annotation.Resource;
+import lombok.Synchronized;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class SystemConfigRepository implements ISystemConfigRepository {
+
+ private static final String CURRENT_PTC_ANCHOR_VERSION = "current_ptc_anchor_version";
+
+ private static final String CURRENT_PTC_TRUST_ROOT = "current_ptc_trust_root";
+
+ @Resource
+ private SystemConfigMapper systemConfigMapper;
+
+ @Override
+ public String getSystemConfig(String key) {
+ try {
+ var entity = systemConfigMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(SystemConfigEntity::getConfKey, key)
+ );
+ if (ObjectUtil.isNull(entity)) {
+ return null;
+ }
+ return entity.getConfValue();
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to get system config with key: {}", key
+ );
+ }
+ }
+
+ @Override
+ public boolean hasSystemConfig(String key) {
+ try {
+ return systemConfigMapper.exists(
+ new LambdaQueryWrapper()
+ .eq(SystemConfigEntity::getConfKey, key)
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to check existence of system config with key: {}", key
+ );
+ }
+ }
+
+ @Override
+ public void setSystemConfig(Map configs) {
+ try {
+ configs.forEach((key, value) -> {
+ systemConfigMapper.insert(
+ SystemConfigEntity.builder()
+ .confKey(key)
+ .confValue(value)
+ .build()
+ );
+ });
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to set system config with key: {}", JSON.toJSONString(configs)
+ );
+ }
+ }
+
+ @Override
+ @Synchronized
+ public void setSystemConfig(String key, String value) {
+ try {
+ systemConfigMapper.insert(
+ SystemConfigEntity.builder()
+ .confKey(key)
+ .confValue(value)
+ .build()
+ );
+ } catch (Exception e) {
+ throw new DataAccessLayerException(
+ e, "Failed to set system config with key: {}", key
+ );
+ }
+ }
+
+ @Override
+ public BigInteger queryCurrentPtcAnchorVersion() {
+ String curr = getSystemConfig(CURRENT_PTC_ANCHOR_VERSION);
+ return new BigInteger(StrUtil.isEmpty(curr) ? "-1" : curr);
+ }
+
+ @Override
+ public void setCurrentPtcAnchorVersion(BigInteger version) {
+ setSystemConfig(CURRENT_PTC_ANCHOR_VERSION, version.toString());
+ }
+
+ @Override
+ @Transactional(rollbackFor = DataAccessLayerException.class)
+ public void setPtcTrustRoot(PTCTrustRoot ptcTrustRoot) {
+ setSystemConfig(CURRENT_PTC_TRUST_ROOT, Base64.encode(ptcTrustRoot.encode()));
+ setCurrentPtcAnchorVersion(
+ ptcTrustRoot.getVerifyAnchorMap().keySet().stream().max(BigInteger::compareTo).orElse(BigInteger.ZERO)
+ );
+ }
+
+ @Override
+ public PTCTrustRoot getPtcTrustRoot() {
+ return PTCTrustRoot.decode(Base64.decode(getSystemConfig(CURRENT_PTC_TRUST_ROOT)));
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IBCDNSRepository.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IBCDNSRepository.java
new file mode 100755
index 00000000..230d915d
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IBCDNSRepository.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces;
+
+import java.util.List;
+import java.util.Map;
+
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+
+public interface IBCDNSRepository {
+
+ long countBCDNSService();
+
+ boolean hasDomainSpaceCert(String domainSpace);
+
+ void saveDomainSpaceCert(DomainSpaceCertWrapper domainSpaceCertWrapper);
+
+ DomainSpaceCertWrapper getDomainSpaceCert(String domainSpace);
+
+ DomainSpaceCertWrapper getDomainSpaceCert(ObjectIdentity ownerOid);
+
+ Map getDomainSpaceCertChain(String leafDomainSpace);
+
+ List getDomainSpaceChain(String leafDomainSpace);
+
+ boolean hasBCDNSService(String domainSpace);
+
+ BCDNSServiceDO getBCDNSServiceDO(String domainSpace);
+
+ void deleteBCDNSServiceDO(String domainSpace);
+
+ List getAllBCDNSDomainSpace();
+
+ void saveBCDNSServiceDO(BCDNSServiceDO bcdnsServiceDO);
+
+ void updateBCDNSServiceState(String domainSpace, BCDNSStateEnum stateEnum);
+
+ void updateBCDNSServiceProperties(String domainSpace, byte[] rawProp);
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IEndorseServiceRepository.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IEndorseServiceRepository.java
new file mode 100755
index 00000000..31badc48
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/IEndorseServiceRepository.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces;
+
+import java.math.BigInteger;
+
+import com.alipay.antchain.bridge.commons.core.base.CrossChainLane;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+
+public interface IEndorseServiceRepository {
+
+ TpBtaWrapper getMatchedTpBta(CrossChainLane lane);
+
+ TpBtaWrapper getMatchedTpBta(CrossChainLane lane, int tpbtaVersion);
+
+ TpBtaWrapper getExactTpBta(CrossChainLane lane);
+
+ TpBtaWrapper getExactTpBta(CrossChainLane lane, int tpbtaVersion);
+
+ void setTpBta(TpBtaWrapper tpBtaWrapper);
+
+ boolean hasTpBta(CrossChainLane lane, int tpbtaVersion);
+
+ BtaWrapper getBta(String domain);
+
+ BtaWrapper getBta(String domain, int subjectVersion);
+
+ void setBta(BtaWrapper btaWrapper);
+
+ boolean hasBta(String domain, int subjectVersion);
+
+ ValidatedConsensusStateWrapper getLatestValidatedConsensusState(String domain);
+
+ ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, BigInteger height);
+
+ ValidatedConsensusStateWrapper getValidatedConsensusState(String domain, String hash);
+
+ void setValidatedConsensusState(ValidatedConsensusStateWrapper validatedConsensusStateWrapper);
+
+ boolean hasValidatedConsensusState(String domain, BigInteger height);
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/ISystemConfigRepository.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/ISystemConfigRepository.java
new file mode 100755
index 00000000..6e2cfae1
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/repository/interfaces/ISystemConfigRepository.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+
+public interface ISystemConfigRepository {
+
+ String getSystemConfig(String key);
+
+ boolean hasSystemConfig(String key);
+
+ void setSystemConfig(Map configs);
+
+ void setSystemConfig(String key, String value);
+
+ BigInteger queryCurrentPtcAnchorVersion();
+
+ void setCurrentPtcAnchorVersion(BigInteger version);
+
+ void setPtcTrustRoot(PTCTrustRoot ptcTrustRoot);
+
+ PTCTrustRoot getPtcTrustRoot();
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImpl.java
new file mode 100755
index 00000000..0e15cdb6
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImpl.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTypeEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IBCDNSManageService;
+import io.grpc.stub.StreamObserver;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class AdminServiceImpl extends AdminServiceGrpc.AdminServiceImplBase {
+
+ @Resource
+ private IBCDNSManageService bcdnsManageService;
+
+ @Resource
+ private ISystemConfigRepository systemConfigRepository;
+
+ @Override
+ public void registerBcdnsService(RegisterBcdnsServiceRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("register bcdns service {}", request.getDomainSpace());
+ bcdnsManageService.registerBCDNSService(
+ request.getDomainSpace(),
+ BCDNSTypeEnum.parseFromValue(request.getBcdnsType()),
+ request.getConfig().toByteArray(),
+ StrUtil.isEmpty(request.getBcdnsRootCert()) ? null : CrossChainCertificateUtil.readCrossChainCertificateFromPem(request.getBcdnsRootCert().getBytes())
+ );
+ responseObserver.onNext(Response.newBuilder().setCode(0).build());
+ } catch (Throwable t) {
+ log.error("register bcdns service {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void getBcdnsServiceInfo(GetBcdnsServiceInfoRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("get bcdns service info {}", request.getDomainSpace());
+ var bcdnsServiceDO = bcdnsManageService.getBCDNSServiceData(request.getDomainSpace());
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setGetBcdnsServiceInfoResp(
+ GetBcdnsServiceInfoResp.newBuilder()
+ .setInfoJson(ObjectUtil.isNull(bcdnsServiceDO) ? "not found" : JSON.toJSONString(bcdnsServiceDO))
+ ).build()
+ );
+ } catch (Throwable t) {
+ log.error("get bcdns service info {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void deleteBcdnsService(DeleteBcdnsServiceRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("delete bcdns service {}", request.getDomainSpace());
+ bcdnsManageService.deleteBCDNSServiceDate(request.getDomainSpace());
+ responseObserver.onNext(Response.newBuilder().setCode(0).build());
+ } catch (Throwable t) {
+ log.error("delete bcdns service {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void getBcdnsCertificate(GetBcdnsCertificateRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("get bcdns service cert {}", request.getDomainSpace());
+ String cert = "";
+ var domainSpaceCertWrapper = bcdnsManageService.getDomainSpaceCert(request.getDomainSpace());
+ if (ObjectUtil.isNull(domainSpaceCertWrapper) || ObjectUtil.isNull(domainSpaceCertWrapper.getDomainSpaceCert())) {
+ cert = "not found";
+ } else {
+ cert = CrossChainCertificateUtil.formatCrossChainCertificateToPem(domainSpaceCertWrapper.getDomainSpaceCert());
+ }
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setGetBcdnsCertificateResp(
+ GetBcdnsCertificateResp.newBuilder()
+ .setCertificate(cert)
+ ).build()
+ );
+ } catch (Throwable t) {
+ log.error("get bcdns service cert {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void stopBcdnsService(StopBcdnsServiceRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("stop bcdns service {}", request.getDomainSpace());
+ bcdnsManageService.stopBCDNSService(request.getDomainSpace());
+ responseObserver.onNext(Response.newBuilder().setCode(0).build());
+ } catch (Throwable t) {
+ log.error("stop bcdns service {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void restartBcdnsService(RestartBcdnsServiceRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("restart bcdns service {}", request.getDomainSpace());
+ bcdnsManageService.restartBCDNSService(request.getDomainSpace());
+ responseObserver.onNext(Response.newBuilder().setCode(0).build());
+ } catch (Throwable t) {
+ log.error("restart bcdns service {} failed", request.getDomainSpace(), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void addPtcTrustRoot(AddPtcTrustRootRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("adding ptc trust root");
+ var trustRoot = PTCTrustRoot.decode(request.getRawTrustRoot().toByteArray());
+ if (trustRoot.getPtcCredentialSubject().getType() != PTCTypeEnum.COMMITTEE) {
+ throw new RuntimeException("ptc trust root type must be committee");
+ }
+ systemConfigRepository.setPtcTrustRoot(trustRoot);
+ responseObserver.onNext(Response.newBuilder().setCode(0).build());
+ } catch (Throwable t) {
+ log.error("add ptc root failed: {}", Base64.encode(request.getRawTrustRoot().toByteArray()), t);
+ responseObserver.onNext(Response.newBuilder().setCode(-1).setErrorMsg(t.getMessage()).build());
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceImpl.java
new file mode 100755
index 00000000..0e7a2a4f
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceImpl.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+import java.math.BigInteger;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory;
+import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
+import com.alipay.antchain.bridge.commons.core.am.IAuthMessage;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorFactory;
+import com.alipay.antchain.bridge.commons.core.bta.IBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.core.monitor.IMonitorMessage;
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorMessageFactory;
+import com.alipay.antchain.bridge.commons.core.sdp.ISDPMessage;
+import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
+import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.MonitorTypeEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.interceptor.RequestTraceInterceptor;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IEndorserService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IHcdvsPluginService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IMonitorService;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.google.protobuf.ByteString;
+import io.grpc.stub.StreamObserver;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+import org.springframework.beans.factory.annotation.Value;
+
+@Slf4j
+@GrpcService(interceptors = RequestTraceInterceptor.class)
+public class MonitorNodeServiceImpl extends CommitteeNodeServiceGrpc.CommitteeNodeServiceImplBase {
+
+ @Value("${committee.id}")
+ private String committeeId;
+
+ @Value("${committee.node.id}")
+ private String nodeId;
+
+ @Resource
+ private IHcdvsPluginService hcdvsPluginService;
+
+ @Resource
+ private IEndorserService endorserService;
+
+ @Resource
+ private IEndorseServiceRepository endorseServiceRepository;
+
+ @Override
+ public void heartbeat(HeartbeatRequest request, StreamObserver responseObserver) {
+ try {
+ responseObserver.onNext(
+ Response.newBuilder().setHeartbeatResp(
+ HeartbeatResponse.newBuilder()
+ .setCommitteeId(committeeId)
+ .setNodeId(nodeId)
+ .addAllProducts(hcdvsPluginService.getAvailableProducts())
+ ).build()
+
+ );
+ } catch (CommitteeNodeException e) {
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("process heartbeat failed", t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void queryTpBta(QueryTpBtaRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("query tpbta with lane (sender_domain:{}, receiver_domain:{}, sender_id:{}, receiver_id:{})",
+ request.getSenderDomain(), request.getReceiverDomain(), request.getSenderId(), request.getReceiverId());
+
+ if (StrUtil.isEmpty(request.getSenderDomain())) {
+ throw new InvalidRequestException("sender domain must not be empty");
+ }
+ TpBtaWrapper tpBtaWrapper = endorserService.queryMatchedTpBta(
+ new CrossChainLane(
+ new CrossChainDomain(request.getSenderDomain()),
+ StrUtil.isEmpty(request.getReceiverDomain()) ? null : new CrossChainDomain(request.getReceiverDomain()),
+ StrUtil.isEmpty(request.getSenderId()) ? null : CrossChainIdentity.fromHexStr(request.getSenderId()),
+ StrUtil.isEmpty(request.getReceiverId()) ? null : CrossChainIdentity.fromHexStr(request.getReceiverId())
+ )
+ );
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setQueryTpBtaResp(
+ QueryTpBtaResponse.newBuilder()
+ .setRawTpBta(ByteString.copyFrom(tpBtaWrapper.getTpbta().encode()))
+ .build()
+ ).build()
+ );
+ } catch (CommitteeNodeException e) {
+ log.error("query tpbta for lane (sender_domain:{}, receiver_domain:{}, sender_id:{}, receiver_id:{}) failed",
+ request.getSenderDomain(), request.getReceiverDomain(), request.getSenderId(), request.getReceiverId(), e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("query tpbta for lane (sender_domain:{}, receiver_domain:{}, sender_id:{}, receiver_id:{}) failed with unexpected error: ",
+ request.getSenderDomain(), request.getReceiverDomain(), request.getSenderId(), request.getReceiverId(), t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void verifyBta(VerifyBtaRequest request, StreamObserver responseObserver) {
+ IBlockchainTrustAnchor bta = null;
+ try {
+ AbstractCrossChainCertificate domainCert = CrossChainCertificateFactory.createCrossChainCertificate(request.getRawDomainCert().toByteArray());
+ if (ObjectUtil.isNull(domainCert)) {
+ throw new InvalidRequestException("domain cert is null");
+ }
+ bta = BlockchainTrustAnchorFactory.createBTA(request.getRawBta().toByteArray());
+ if (ObjectUtil.isNull(bta)) {
+ throw new InvalidRequestException("bta is null");
+ }
+
+ var tpBtaWrapper = endorserService.verifyBta(domainCert, bta);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setVerifyBtaResp(
+ VerifyBtaResponse.newBuilder()
+ .setRawTpBta(ByteString.copyFrom(tpBtaWrapper.getTpbta().encode()))
+ .build()
+ ).build()
+ );
+ } catch (InvalidBtaException e) {
+ log.error("bta for domain {} can't pass the verification: ",
+ ObjectUtil.isNull(bta) || ObjectUtil.isNull(bta.getDomain()) ? "unknown" : bta.getDomain().getDomain(), e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMessage())
+ .build()
+ );
+ } catch (CommitteeNodeException e) {
+ log.error("verify bta for domain {} failed",
+ ObjectUtil.isNull(bta) || ObjectUtil.isNull(bta.getDomain()) ? "unknown" : bta.getDomain().getDomain(), e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("verify bta for domain {} failed with unexpected error: ",
+ ObjectUtil.isNull(bta) || ObjectUtil.isNull(bta.getDomain()) ? "unknown" : bta.getDomain().getDomain(), t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void commitAnchorState(CommitAnchorStateRequest request, StreamObserver responseObserver) {
+ String domain = null;
+ BigInteger height = null;
+ String hash = null;
+ try {
+ var anchorState = ConsensusState.decode(request.getRawAnchorState().toByteArray());
+ if (ObjectUtil.isNull(anchorState)) {
+ throw new InvalidRequestException("anchor state is null");
+ }
+ var crossChainLane = CrossChainLane.decode(request.getCrossChainLane().toByteArray());
+ if (ObjectUtil.isNull(crossChainLane)) {
+ throw new InvalidRequestException("crossChainLane is null");
+ }
+
+ domain = anchorState.getDomain().getDomain();
+ height = anchorState.getHeight();
+ hash = HexUtil.encodeHexStr(anchorState.getHash());
+ log.info("commit anchor state with height {} and hash {} for domain {}", height, hash, domain);
+
+ var vcs = endorserService.commitAnchorState(crossChainLane, anchorState);
+
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setCommitAnchorStateResp(
+ CommitAnchorStateResponse.newBuilder()
+ .setRawValidatedConsensusState(ByteString.copyFrom(vcs.encode()))
+ .build()
+ ).build()
+ );
+ } catch (CommitteeNodeException e) {
+ log.error("commit anchor state with height {} and hash {} for domain {} failed", height, hash, domain, e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("commit anchor state with height {} and hash {} for domain {} failed with unexpected error: ",
+ height, hash, domain, t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void commitConsensusState(CommitConsensusStateRequest request, StreamObserver responseObserver) {
+ String domain = null;
+ BigInteger height = null;
+ String hash = null;
+ try {
+ var currState = ConsensusState.decode(request.getRawConsensusState().toByteArray());
+ if (ObjectUtil.isNull(currState)) {
+ throw new InvalidRequestException("consensus state is null");
+ }
+ var crossChainLane = CrossChainLane.decode(request.getCrossChainLane().toByteArray());
+ if (ObjectUtil.isNull(crossChainLane)) {
+ throw new InvalidRequestException("crossChainLane is null");
+ }
+
+ domain = currState.getDomain().getDomain();
+ height = currState.getHeight();
+ hash = HexUtil.encodeHexStr(currState.getHash());
+ log.info("commit consensus state with height {} and hash {} for domain {}", height, hash, domain);
+
+ var vcs = endorserService.commitConsensusState(crossChainLane, currState);
+
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setCommitConsensusStateResp(
+ CommitConsensusStateResponse.newBuilder()
+ .setRawValidatedConsensusState(ByteString.copyFrom(vcs.encode()))
+ .build()
+ ).build()
+ );
+ } catch (InvalidConsensusStateException e) {
+ log.error("verify consensus state with height {} and hash {} for domain {} failed: {}",
+ height, hash, domain, e.getMessage());
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMessage())
+ .build()
+ );
+ } catch (CommitteeNodeException e) {
+ log.error("commit consensus state with height {} and hash {} for domain {} failed", height, hash, domain, e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("commit anchor state with height {} and hash {} for domain {} failed with unexpected error: ",
+ height, hash, domain, t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void verifyCrossChainMessage(VerifyCrossChainMessageRequest request, StreamObserver responseObserver) {
+ String domain = null;
+ CrossChainMessage.CrossChainMessageType msgType = null;
+ String msgKey = null;
+ try {
+ var ucp = UniformCrosschainPacket.decode(request.getRawUcp().toByteArray());
+ if (ObjectUtil.isNull(ucp)) {
+ throw new InvalidRequestException("ucp is null");
+ }
+ var crossChainLane = CrossChainLane.decode(request.getCrossChainLane().toByteArray());
+ if (ObjectUtil.isNull(crossChainLane)) {
+ throw new InvalidRequestException("crossChainLane is null");
+ }
+
+ domain = ucp.getSrcDomain().getDomain();
+ msgType = ucp.getSrcMessage().getType();
+ msgKey = ObjectUtil.isNull(ucp.getCrossChainLane()) ?
+ HexUtil.encodeHexStr(ucp.getMessageHash(HashAlgoEnum.KECCAK_256))
+ : ucp.getCrossChainLane().getLaneKey();
+ log.info("verify crosschain message (type: {}, msg: {}) from domain {}", msgType, msgKey, domain);
+
+ IAuthMessage authMessage = AuthMessageFactory.createAuthMessage(ucp.getSrcMessage().getMessage());
+ ISDPMessage sdpMessage = SDPMessageFactory.createSDPMessage(authMessage.getPayload());
+ IMonitorMessage monitorMessage = MonitorMessageFactory.createMonitorMessage(sdpMessage.getPayload());
+
+ CommitteeNodeProof proof = null;
+
+ // 根据监管字段内容 判断是否需要监管
+ if (monitorMessage.getMonitorType() == MonitorTypeEnum.MONITOR_OPEN.getCode()) {
+ log.info("crosschain message: need monitor");
+ proof = endorserService.verifyUcpWithMonitorSystem(crossChainLane, ucp);
+ } else {
+ // 不监管 把跨链消息发给监管系统即可
+ log.info("crosschain message: don't need monitor");
+ endorserService.relayUcpToMonitorSystem(ucp);
+ proof = endorserService.verifyUcp(crossChainLane, ucp);
+ }
+
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setVerifyCrossChainMessageResp(
+ VerifyCrossChainMessageResponse.newBuilder()
+ .setRawNodeProof(ByteString.copyFrom(proof.encode()))
+ .build()
+ ).build()
+ );
+ } catch (InvalidCrossChainMessageException e) {
+ log.error("crosschain message (type: {}, msg: {}) from domain {} 's verification not passed: {}",
+ msgType, msgKey, domain, e.getMessage());
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMessage())
+ .build()
+ );
+ } catch (CommitteeNodeException e) {
+ log.error("verify crosschain message (type: {}, msg: {}) from domain {} failed", msgType, msgKey, domain, e);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("verify crosschain message (type: {}, msg: {}) from domain {} failed with unexpected error: ",
+ msgType, msgKey, domain, t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void querySupportedBlockchainProducts(QuerySupportedBlockchainProductsRequest request, StreamObserver responseObserver) {
+ try {
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(0)
+ .setQuerySupportedBlockchainProductsResp(
+ QuerySupportedBlockchainProductsResponse.newBuilder()
+ .addAllProducts(hcdvsPluginService.getAvailableProducts())
+ .build()
+ ).build()
+ );
+ } catch (Throwable t) {
+ log.error("query supported blockchain products failed with unexpected error: ", t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void queryBlockState(QueryBlockStateRequest request, StreamObserver responseObserver) {
+ try {
+ log.info("query latest block state for {}", request.getDomain());
+
+ var wrapper = endorseServiceRepository.getLatestValidatedConsensusState(request.getDomain());
+ if (ObjectUtil.isNotNull(wrapper)) {
+ log.debug("get latest block state {} for {}", wrapper.getHeight().toString(), request.getDomain());
+ responseObserver.onNext(
+ Response.newBuilder().setQueryBlockStateResp(
+ QueryBlockStateResponse.newBuilder().setRawValidatedBlockState(ByteString.copyFrom(
+ new BlockState(
+ wrapper.getValidatedConsensusState().getDomain(),
+ wrapper.getValidatedConsensusState().getHash(),
+ wrapper.getValidatedConsensusState().getHeight(),
+ wrapper.getValidatedConsensusState().getStateTimestamp()
+ ).encode()
+ ))
+ ).build()
+
+ );
+ }
+ } catch (CommitteeNodeException e) {
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("process queryBlockState failed", t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public void endorseBlockState(EndorseBlockStateRequest request, StreamObserver responseObserver) {
+ String receiverDomain;
+ String laneKey = null;
+ BigInteger height = null;
+ try {
+ receiverDomain = request.getReceiverDomain();
+ var tpbtaLane = CrossChainLane.decode(request.getCrossChainLane().toByteArray());
+ height = new BigInteger(request.getHeight());
+ laneKey = tpbtaLane.getLaneKey();
+
+ log.info("endorse block state {} for {} with receiver domain {}", height, laneKey, receiverDomain);
+
+ var resp = endorserService.endorseBlockState(tpbtaLane, receiverDomain, height);
+ responseObserver.onNext(
+ Response.newBuilder().setEndorseBlockStateResp(
+ EndorseBlockStateResponse.newBuilder()
+ .setBlockStateAuthMsg(ByteString.copyFrom(resp.getBlockStateAuthMsg().encode()))
+ .setCommitteeNodeProof(ByteString.copyFrom(resp.getCommitteeNodeProof().encode()))
+ .build()
+ ).build()
+ );
+ } catch (CommitteeNodeException e) {
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(e.getCodeNum())
+ .setErrorMsg(e.getMsg())
+ .build()
+ );
+ } catch (Throwable t) {
+ log.error("process endorseBlockState failed for {} height {}", laneKey, StrUtil.toString(height), t);
+ responseObserver.onNext(
+ Response.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceImpl.java
new file mode 100755
index 00000000..9d62fcb3
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceImpl.java
@@ -0,0 +1,73 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorOrderV1;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.CommitteeNodeErrorCodeEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.InvalidRequestException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.interceptor.RequestTraceInterceptor;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IMonitorService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc.*;
+import io.grpc.stub.StreamObserver;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+
+@Slf4j
+@GrpcService(interceptors = RequestTraceInterceptor.class)
+public class MonitorOrderServiceImpl extends MonitorOrderServiceGrpc.MonitorOrderServiceImplBase {
+
+ @Resource
+ private IMonitorService monitorService;
+
+ @Override
+ public void recvMonitorOrder(RecvMonitorOrderRequest request, StreamObserver responseObserver) {
+ try {
+ MonitorOrderV1 monitorOrder = convertFromGRpcMonitorOrder(request.getMonitorOrder());
+ if (ObjectUtil.isNull(monitorOrder)) {
+ throw new InvalidRequestException("monitorOrder is null");
+ }
+ log.info("receive monitor order: " +
+ "product: {}, domain: {}, monitor_order_type: {}, sender_domain: {}, sender_identity: {}, " +
+ "receive_domain: {}, receive_identity: {}, transaction_content: {}, extra: {}",
+ monitorOrder.getProduct(), monitorOrder.getDomain(), monitorOrder.getMonitorOrderType(),
+ monitorOrder.getSenderDomain(), monitorOrder.getFromAddress(),
+ monitorOrder.getReceiverDomain(), monitorOrder.getToAddress(),
+ monitorOrder.getTransactionContent(), monitorOrder.getExtra());
+
+ monitorService.recvMonitorOrder(monitorOrder);
+
+ responseObserver.onNext(
+ RecvMonitorOrderResponse.newBuilder()
+ .setCode(0)
+ .setErrorMsg("")
+ .build()
+ );
+
+ } catch (Throwable t) {
+ log.error("query supported blockchain products failed with unexpected error: ", t);
+ responseObserver.onNext(
+ RecvMonitorOrderResponse.newBuilder()
+ .setCode(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getErrorCodeNum())
+ .setErrorMsg(CommitteeNodeErrorCodeEnum.UNKNOWN_INTERNAL_ERROR.getShortMsg())
+ .build()
+ );
+ } finally {
+ responseObserver.onCompleted();
+ }
+ }
+
+ private static MonitorOrderV1 convertFromGRpcMonitorOrder(com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc.MonitorOrder grpcMonitorOrder) {
+ MonitorOrderV1 monitorOrder = new MonitorOrderV1();
+ monitorOrder.setProduct(grpcMonitorOrder.getProduct());
+ monitorOrder.setDomain(grpcMonitorOrder.getDomain());
+ monitorOrder.setMonitorOrderType(grpcMonitorOrder.getMonitorOrderType());
+ monitorOrder.setSenderDomain(grpcMonitorOrder.getSenderDomain());
+ monitorOrder.setFromAddress(grpcMonitorOrder.getFromAddress());
+ monitorOrder.setReceiverDomain(grpcMonitorOrder.getReceiverDomain());
+ monitorOrder.setToAddress(grpcMonitorOrder.getToAddress());
+ monitorOrder.setTransactionContent(grpcMonitorOrder.getTransactionContent());
+ monitorOrder.setExtra(grpcMonitorOrder.getExtra());
+ return monitorOrder;
+ }
+
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/interceptor/RequestTraceInterceptor.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/interceptor/RequestTraceInterceptor.java
new file mode 100755
index 00000000..e905f057
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/interceptor/RequestTraceInterceptor.java
@@ -0,0 +1,33 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server.interceptor;
+
+import java.net.InetSocketAddress;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import io.grpc.*;
+import lombok.extern.slf4j.Slf4j;
+
+// gRPC服务器端的拦截器,拦截gRPC请求并记录客户端的请求来源(IP地址和端口),主要用于请求追踪和日志记录。
+@Slf4j(topic = "req-trace")
+public class RequestTraceInterceptor implements ServerInterceptor {
+
+ @Override
+ public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) {
+ InetSocketAddress clientAddr = (InetSocketAddress) call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+ if (StrUtil.equalsIgnoreCase(call.getMethodDescriptor().getBareMethodName(), "heartbeat")) {
+ if (ObjectUtil.isNull(clientAddr)) {
+ log.info("heartbeat from client without address found");
+ } else {
+ log.info("heartbeat from client {}:{}", clientAddr.getHostString(), clientAddr.getPort());
+ }
+ } else {
+ if (ObjectUtil.isNull(clientAddr)) {
+ log.debug("crosschain biz call from client without address found");
+ } else {
+ log.debug("crosschain biz call from client {}:{}", clientAddr.getHostString(), clientAddr.getPort());
+ }
+ }
+
+ return next.startCall(call, headers);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IBCDNSManageService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IBCDNSManageService.java
new file mode 100755
index 00000000..90f6f121
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IBCDNSManageService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.bcdns.service.IBlockChainDomainNameService;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+
+public interface IBCDNSManageService {
+
+ long countBCDNSService();
+
+ IBlockChainDomainNameService getBCDNSClient(String domainSpace);
+
+ void registerBCDNSService(String domainSpace, BCDNSTypeEnum bcdnsType, byte[] config, AbstractCrossChainCertificate bcdnsRootCert);
+
+ IBlockChainDomainNameService startBCDNSService(BCDNSServiceDO bcdnsServiceDO);
+
+ void restartBCDNSService(String domainSpace);
+
+ void stopBCDNSService(String domainSpace);
+
+ void saveBCDNSServiceData(BCDNSServiceDO bcdnsServiceDO);
+
+ BCDNSServiceDO getBCDNSServiceData(String domainSpace);
+
+ void deleteBCDNSServiceDate(String domainSpace);
+
+ List getAllBCDNSDomainSpace();
+
+ boolean hasBCDNSServiceData(String domainSpace);
+
+ Map getTrustRootCertChain(String domainSpace);
+
+ List getDomainSpaceChain(String domainSpace);
+
+ AbstractCrossChainCertificate getTrustRootCertForRootDomain();
+
+ boolean validateCrossChainCertificate(AbstractCrossChainCertificate certificate);
+
+ boolean validateCrossChainCertificate(
+ AbstractCrossChainCertificate certificate,
+ Map domainSpaceCertPath
+ );
+
+ DomainSpaceCertWrapper getDomainSpaceCert(String domainSpace);
+
+ DomainSpaceCertWrapper getDomainSpaceCert(ObjectIdentity ownerOid);
+
+ void saveDomainSpaceCerts(Map domainSpaceCerts);
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IEndorserService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IEndorserService.java
new file mode 100755
index 00000000..debddff4
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IEndorserService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import java.math.BigInteger;
+
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.core.base.ConsensusState;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainLane;
+import com.alipay.antchain.bridge.commons.core.base.UniformCrosschainPacket;
+import com.alipay.antchain.bridge.commons.core.bta.IBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.core.ptc.ValidatedConsensusState;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.InvalidBtaException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.EndorseBlockStateResp;
+
+public interface IEndorserService {
+
+ TpBtaWrapper queryMatchedTpBta(CrossChainLane lane);
+
+ TpBtaWrapper verifyBta(AbstractCrossChainCertificate domainCert, IBlockchainTrustAnchor bta) throws InvalidBtaException;
+
+ ValidatedConsensusState commitAnchorState(CrossChainLane crossChainLane, ConsensusState anchorState);
+
+ ValidatedConsensusState commitConsensusState(CrossChainLane crossChainLane, ConsensusState currState);
+
+ CommitteeNodeProof verifyUcp(CrossChainLane crossChainLane, UniformCrosschainPacket ucp);
+
+ CommitteeNodeProof verifyUcpWithMonitorSystem(CrossChainLane crossChainLane, UniformCrosschainPacket ucp);
+
+ void relayUcpToMonitorSystem(UniformCrosschainPacket ucp);
+
+ EndorseBlockStateResp endorseBlockState(CrossChainLane crossChainLane, String receiverDomain, BigInteger height);
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IHcdvsPluginService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IHcdvsPluginService.java
new file mode 100755
index 00000000..f9c92c7d
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IHcdvsPluginService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import com.alipay.antchain.bridge.plugins.manager.core.IAntChainBridgePlugin;
+import com.alipay.antchain.bridge.plugins.spi.ptc.IHeteroChainDataVerifierService;
+
+import java.util.List;
+
+public interface IHcdvsPluginService {
+ void loadPlugins();
+
+ void startPlugins();
+
+ void loadPlugin(String path);
+
+ void startPlugin(String path);
+
+ void stopPlugin(String product);
+
+ void startPluginFromStop(String product);
+
+ void reloadPlugin(String product);
+
+ void reloadPlugin(String product, String path);
+
+ IAntChainBridgePlugin getPlugin(String product);
+
+ boolean hasPlugin(String product);
+
+ IHeteroChainDataVerifierService createHCDVSService(String product);
+
+ IHeteroChainDataVerifierService getHCDVSService(String product);
+
+ boolean hasProduct(String product);
+
+ List getAvailableProducts();
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IMonitorService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IMonitorService.java
new file mode 100755
index 00000000..f3a18c4f
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IMonitorService.java
@@ -0,0 +1,9 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorOrderV1;
+
+public interface IMonitorService {
+
+ void recvMonitorOrder(MonitorOrderV1 monitorOrder);
+
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IScheduledTaskService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IScheduledTaskService.java
new file mode 100755
index 00000000..1c2a601f
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/IScheduledTaskService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+public interface IScheduledTaskService {
+
+ void listenPtcTrustRoot();
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/BCDNSManageService.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/BCDNSManageService.java
new file mode 100755
index 00000000..38fb9d0a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/BCDNSManageService.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service.impl;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.bcdns.factory.BlockChainDomainNameServiceFactory;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.bcdns.service.IBlockChainDomainNameService;
+import com.alipay.antchain.bridge.bcdns.types.resp.QueryBCDNSTrustRootCertificateResponse;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.CommitteeNodeException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.CommitteeNodeInternalException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IBCDNSManageService;
+import jakarta.annotation.Resource;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Slf4j
+public class BCDNSManageService implements IBCDNSManageService {
+
+ @Resource
+ private IBCDNSRepository bcdnsRepository;
+
+ private final Map bcdnsClientMap = new ConcurrentHashMap<>();
+
+ @Override
+ public long countBCDNSService() {
+ return bcdnsRepository.countBCDNSService();
+ }
+
+ @Override
+ public IBlockChainDomainNameService getBCDNSClient(String domainSpace) {
+ if (bcdnsClientMap.containsKey(domainSpace)) {
+ return bcdnsClientMap.get(domainSpace);
+ }
+
+ BCDNSServiceDO bcdnsServiceDO = getBCDNSServiceData(domainSpace);
+ if (ObjectUtil.isNull(bcdnsServiceDO)) {
+ log.warn("none bcdns data found for domain space {}", domainSpace);
+ return null;
+ }
+ if (bcdnsServiceDO.getState() != BCDNSStateEnum.WORKING) {
+ throw new CommitteeNodeInternalException("BCDNS with domain space {} is not working now", domainSpace);
+ }
+
+ return startBCDNSService(bcdnsServiceDO);
+ }
+
+ @Override
+ @Synchronized
+ @Transactional(rollbackFor = CommitteeNodeException.class)
+ public void registerBCDNSService(String domainSpace, BCDNSTypeEnum bcdnsType, byte[] config, AbstractCrossChainCertificate bcdnsRootCert) {
+ try {
+ if (hasBCDNSServiceData(domainSpace)) {
+ throw new RuntimeException("bcdns already registered");
+ }
+
+ var bcdnsServiceDO = new BCDNSServiceDO();
+ bcdnsServiceDO.setType(bcdnsType);
+ bcdnsServiceDO.setDomainSpace(domainSpace);
+ bcdnsServiceDO.setProperties(config);
+
+ if (ObjectUtil.isNotNull(bcdnsRootCert)) {
+ if (CrossChainCertificateUtil.isBCDNSTrustRoot(bcdnsRootCert)
+ && !StrUtil.equals(domainSpace, CrossChainDomain.ROOT_DOMAIN_SPACE)) {
+ throw new RuntimeException("the space name of bcdns trust root certificate supposed to have the root space name bug got : " + domainSpace);
+ } else if (!CrossChainCertificateUtil.isBCDNSTrustRoot(bcdnsRootCert)
+ && !CrossChainCertificateUtil.isDomainSpaceCert(bcdnsRootCert)) {
+ throw new RuntimeException("expected bcdns trust root or domain space type certificate bug got : " + bcdnsRootCert.getType().name());
+ }
+ bcdnsServiceDO.setDomainSpaceCertWrapper(
+ new DomainSpaceCertWrapper(bcdnsRootCert)
+ );
+ bcdnsServiceDO.setOwnerOid(bcdnsRootCert.getCredentialSubjectInstance().getApplicant());
+ }
+
+ startBCDNSService(bcdnsServiceDO);
+ saveBCDNSServiceData(bcdnsServiceDO);
+ } catch (CommitteeNodeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CommitteeNodeInternalException(
+ e,
+ "failed to register bcdns service client for [{}]",
+ domainSpace
+ );
+ }
+ }
+
+ @Override
+ @Synchronized
+ public IBlockChainDomainNameService startBCDNSService(BCDNSServiceDO bcdnsServiceDO) {
+ log.info("starting the bcdns service ( type: {}, domain_space: {} )",
+ bcdnsServiceDO.getType().getCode(), bcdnsServiceDO.getDomainSpace());
+ try {
+ IBlockChainDomainNameService service = BlockChainDomainNameServiceFactory.create(bcdnsServiceDO.getType(), bcdnsServiceDO.getProperties());
+ if (ObjectUtil.isNull(service)) {
+ throw new CommitteeNodeInternalException("bcdns {} start failed", bcdnsServiceDO.getDomainSpace());
+ }
+ if (
+ ObjectUtil.isNull(bcdnsServiceDO.getDomainSpaceCertWrapper())
+ || ObjectUtil.isNull(bcdnsServiceDO.getDomainSpaceCertWrapper().getDomainSpaceCert())
+ ) {
+ QueryBCDNSTrustRootCertificateResponse response = service.queryBCDNSTrustRootCertificate();
+ if (ObjectUtil.isNull(response) || ObjectUtil.isNull(response.getBcdnsTrustRootCertificate())) {
+ throw new CommitteeNodeInternalException("query empty root cert from bcdns {}", bcdnsServiceDO.getDomainSpace());
+ }
+ bcdnsServiceDO.setOwnerOid(response.getBcdnsTrustRootCertificate().getCredentialSubjectInstance().getApplicant());
+ bcdnsServiceDO.setDomainSpaceCertWrapper(
+ new DomainSpaceCertWrapper(response.getBcdnsTrustRootCertificate())
+ );
+ }
+ bcdnsServiceDO.setState(BCDNSStateEnum.WORKING);
+ bcdnsClientMap.put(bcdnsServiceDO.getDomainSpace(), service);
+
+ return service;
+ } catch (CommitteeNodeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CommitteeNodeInternalException(e, "failed to start bcdns service client for [{}]", bcdnsServiceDO.getDomainSpace());
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = CommitteeNodeException.class)
+ @Synchronized
+ public void restartBCDNSService(String domainSpace) {
+ log.info("restarting the bcdns service ( domain_space: {} )", domainSpace);
+ try {
+ var bcdnsServiceDO = getBCDNSServiceData(domainSpace);
+ if (ObjectUtil.isNull(bcdnsServiceDO)) {
+ throw new RuntimeException(StrUtil.format("bcdns {} not exist", domainSpace));
+ }
+ if (bcdnsServiceDO.getState() != BCDNSStateEnum.FROZEN) {
+ throw new RuntimeException(StrUtil.format("bcdns {} already in state {}", domainSpace, bcdnsServiceDO.getState().getCode()));
+ }
+ var service = BlockChainDomainNameServiceFactory.create(bcdnsServiceDO.getType(), bcdnsServiceDO.getProperties());
+ if (ObjectUtil.isNull(service)) {
+ throw new CommitteeNodeInternalException("bcdns {} start failed", bcdnsServiceDO.getDomainSpace());
+ }
+ bcdnsRepository.updateBCDNSServiceState(domainSpace, BCDNSStateEnum.WORKING);
+ bcdnsClientMap.put(bcdnsServiceDO.getDomainSpace(), service);
+
+ } catch (CommitteeNodeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CommitteeNodeInternalException(
+ e,
+ "failed to restart bcdns service client for {}",
+ domainSpace
+ );
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = CommitteeNodeException.class)
+ @Synchronized
+ public void stopBCDNSService(String domainSpace) {
+ log.info("stopping the bcdns service ( domain_space: {} )", domainSpace);
+ try {
+ if (!hasBCDNSServiceData(domainSpace)) {
+ throw new RuntimeException("bcdns not exist for " + domainSpace);
+ }
+ bcdnsRepository.updateBCDNSServiceState(domainSpace, BCDNSStateEnum.FROZEN);
+ bcdnsClientMap.remove(domainSpace);
+ } catch (CommitteeNodeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CommitteeNodeInternalException(
+ e,
+ "failed to stop bcdns service client for {}",
+ domainSpace
+ );
+ }
+ }
+
+ @Override
+ public void saveBCDNSServiceData(BCDNSServiceDO bcdnsServiceDO) {
+ if (bcdnsRepository.hasBCDNSService(bcdnsServiceDO.getDomainSpace())) {
+ throw new CommitteeNodeInternalException(
+ "bcdns {} not exist or data incomplete",
+ bcdnsServiceDO.getDomainSpace()
+ );
+ }
+ bcdnsRepository.saveBCDNSServiceDO(bcdnsServiceDO);
+ }
+
+ @Override
+ public BCDNSServiceDO getBCDNSServiceData(String domainSpace) {
+ return bcdnsRepository.getBCDNSServiceDO(domainSpace);
+ }
+
+ @Override
+ @Synchronized
+ public void deleteBCDNSServiceDate(String domainSpace) {
+ bcdnsRepository.deleteBCDNSServiceDO(domainSpace);
+ bcdnsClientMap.remove(domainSpace);
+ }
+
+ @Override
+ public List getAllBCDNSDomainSpace() {
+ return bcdnsRepository.getAllBCDNSDomainSpace();
+ }
+
+ @Override
+ public boolean hasBCDNSServiceData(String domainSpace) {
+ return bcdnsRepository.hasBCDNSService(domainSpace);
+ }
+
+ @Override
+ public Map getTrustRootCertChain(String domainSpace) {
+ return bcdnsRepository.getDomainSpaceCertChain(domainSpace).entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().getDomainSpaceCert()
+ ));
+ }
+
+ @Override
+ public List getDomainSpaceChain(String domainSpace) {
+ return bcdnsRepository.getDomainSpaceChain(domainSpace);
+ }
+
+ @Override
+ public AbstractCrossChainCertificate getTrustRootCertForRootDomain() {
+ DomainSpaceCertWrapper wrapper = bcdnsRepository.getDomainSpaceCert(CrossChainDomain.ROOT_DOMAIN_SPACE);
+ if (ObjectUtil.isNull(wrapper)) {
+ return null;
+ }
+ return wrapper.getDomainSpaceCert();
+ }
+
+ @Override
+ public boolean validateCrossChainCertificate(AbstractCrossChainCertificate certificate) {
+ DomainSpaceCertWrapper trustRootCert = bcdnsRepository.getDomainSpaceCert(certificate.getIssuer());
+ if (ObjectUtil.isNull(trustRootCert)) {
+ log.warn(
+ "none trust root found for {} to verify for relayer cert: {}",
+ HexUtil.encodeHexStr(certificate.getIssuer().encode()),
+ CrossChainCertificateUtil.formatCrossChainCertificateToPem(certificate)
+ );
+ return false;
+ }
+ return trustRootCert.getDomainSpaceCert().getCredentialSubjectInstance().verifyIssueProof(
+ certificate.getEncodedToSign(),
+ certificate.getProof()
+ );
+ }
+
+ @Override
+ public DomainSpaceCertWrapper getDomainSpaceCert(String domainSpace) {
+ return bcdnsRepository.getDomainSpaceCert(domainSpace);
+ }
+
+ @Override
+ public DomainSpaceCertWrapper getDomainSpaceCert(ObjectIdentity ownerOid) {
+ return bcdnsRepository.getDomainSpaceCert(ownerOid);
+ }
+
+ @Override
+ public boolean validateCrossChainCertificate(AbstractCrossChainCertificate certificate, Map domainSpaceCertPath) {
+ try {
+ List sequentialCerts = domainSpaceCertPath.entrySet().stream().sorted(
+ Comparator.comparing(o -> StrUtil.reverse(o.getKey()))
+ ).map(Map.Entry::getValue)
+ .collect(Collectors.toList());
+
+ DomainSpaceCertWrapper bcdnsRootCert = bcdnsRepository.getDomainSpaceCert(CrossChainDomain.ROOT_DOMAIN_SPACE);
+ if (ObjectUtil.isNull(bcdnsRootCert)) {
+ throw new RuntimeException("none root domain space cert set in DB");
+ }
+ sequentialCerts.set(0, bcdnsRootCert.getDomainSpaceCert());
+
+ if (sequentialCerts.size() > 1) {
+ verifyCertPath(sequentialCerts, 0);
+ saveDomainSpaceCerts(domainSpaceCertPath);
+ }
+
+ return sequentialCerts.get(sequentialCerts.size() - 1)
+ .getCredentialSubjectInstance().verifyIssueProof(
+ certificate.getEncodedToSign(),
+ certificate.getProof()
+ );
+ } catch (Exception e) {
+ log.error("failed to verify crosschain cert (type: {}, cert_id: {})", certificate.getType().name(), certificate.getId(), e);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveDomainSpaceCerts(Map domainSpaceCerts) {
+ for (Map.Entry entry : domainSpaceCerts.entrySet()) {
+ try {
+ if (bcdnsRepository.hasDomainSpaceCert(entry.getKey())) {
+ log.info("DomainSpace {} already exists", entry.getKey());
+ continue;
+ }
+ bcdnsRepository.saveDomainSpaceCert(new DomainSpaceCertWrapper(entry.getValue()));
+ log.info("successful to save domain space cert for {}", entry.getKey());
+ } catch (Exception e) {
+ log.error("failed to save domain space certs for space {} : ", entry.getKey(), e);
+ }
+ }
+ }
+
+ private void verifyCertPath(List certPath, int currIndex) {
+ if (currIndex == certPath.size() - 2) {
+ if (
+ !certPath.get(currIndex).getCredentialSubjectInstance().verifyIssueProof(
+ certPath.get(currIndex + 1).getEncodedToSign(),
+ certPath.get(currIndex + 1).getProof()
+ )
+ ) {
+ throw new RuntimeException(
+ StrUtil.format(
+ "failed to verify {} cert with its parent {}",
+ CrossChainCertificateUtil.getCrossChainDomain(certPath.get(currIndex)),
+ CrossChainCertificateUtil.getCrossChainDomain(certPath.get(currIndex + 1))
+ )
+ );
+ }
+ return;
+ }
+
+ verifyCertPath(certPath, currIndex + 1);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/EndorserServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/EndorserServiceImpl.java
new file mode 100755
index 00000000..9171ac34
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/EndorserServiceImpl.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service.impl;
+
+import java.math.BigInteger;
+import java.security.PrivateKey;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.*;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject;
+import com.alipay.antchain.bridge.commons.bcdns.PTCCredentialSubject;
+import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
+import com.alipay.antchain.bridge.commons.core.am.IAuthMessage;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.bta.IBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.core.monitor.IMonitorMessage;
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorMessageFactory;
+import com.alipay.antchain.bridge.commons.core.ptc.*;
+import com.alipay.antchain.bridge.commons.core.sdp.ISDPMessage;
+import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
+import com.alipay.antchain.bridge.commons.exception.IllegalCrossChainCertException;
+import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import com.alipay.antchain.bridge.plugins.spi.ptc.core.VerifyResult;
+import com.alipay.antchain.bridge.pluginserver.service.CallBBCRequest;
+import com.alipay.antchain.bridge.pluginserver.service.CrossChainServiceGrpc;
+import com.alipay.antchain.bridge.pluginserver.service.Response;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.client.CrossChainServiceGrpcClientManager;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.client.MonitorSystemGrpcClientManager;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IEndorserService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IHcdvsPluginService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeEndorseProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.EndorseBlockStateResp;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.CommitteeEndorseRoot;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.VerifyBtaExtension;
+import com.google.protobuf.ByteString;
+import jakarta.annotation.Resource;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class EndorserServiceImpl implements IEndorserService {
+
+ @Resource
+ private IHcdvsPluginService hcdvsPluginService;
+
+ @Resource
+ private IEndorseServiceRepository endorseServiceRepository;
+
+ @Resource
+ private ISystemConfigRepository systemConfigRepository;
+
+ @Resource
+ private IBCDNSRepository bcdnsRepository;
+
+ @Resource
+ private PrivateKey nodeKey;
+
+ @Resource
+ private AbstractCrossChainCertificate ptcCrossChainCert;
+
+ @Resource
+ private MonitorSystemGrpcClientManager monitorSystemGrpcClientManager;
+
+ @Resource
+ private CrossChainServiceGrpcClientManager crossChainServiceGrpcClientManager;
+
+ @Value("${committee.node.endorse.ucp_hash_algo:KECCAK_256}")
+ private HashAlgoEnum ucpHashAlgo;
+
+ @Value("${committee.node.endorse.sign_algo:KECCAK256_WITH_SECP256K1}")
+ private SignAlgoEnum nodeSignAlgo;
+
+ @Value("${committee.node.id}")
+ private String committeeNodeId;
+
+ @Value("${committee.id}")
+ private String committeeId;
+
+ @Override
+ public TpBtaWrapper queryMatchedTpBta(CrossChainLane lane) {
+ return endorseServiceRepository.getMatchedTpBta(lane);
+ }
+
+ @Override
+ public TpBtaWrapper verifyBta(AbstractCrossChainCertificate domainCert, IBlockchainTrustAnchor bta) throws InvalidBtaException {
+ log.info("verify BTA for domain {} now", bta.getDomain().getDomain());
+ var credentialSubject = (DomainNameCredentialSubject) domainCert.getCredentialSubjectInstance();
+
+ var domainSpaceCertWrapper = bcdnsRepository.getDomainSpaceCert(credentialSubject.getParentDomainSpace().getDomain());
+ if (ObjectUtil.isNull(domainSpaceCertWrapper)) {
+ throw new InvalidBtaException("domain space cert for {} not found", credentialSubject.getParentDomainSpace().getDomain());
+ }
+ if (!ArrayUtil.equals(domainSpaceCertWrapper.getOwnerOid().getRawId(), domainCert.getIssuer().getRawId())) {
+ throw new InvalidBtaException("illegal domain space cert issuer for {}", credentialSubject.getParentDomainSpace().getDomain());
+ }
+
+ try {
+ domainCert.validate(domainSpaceCertWrapper.getDomainSpaceCert().getCredentialSubjectInstance());
+ } catch (IllegalCrossChainCertException e) {
+ throw new InvalidBtaException("domain cert validation failed: {}", e.getMessage());
+ }
+
+ if (!bta.getDomain().equals(credentialSubject.getDomainName())) {
+ throw new InvalidBtaException("domain name mismatch");
+ }
+ if (!ArrayUtil.equals(credentialSubject.getSubjectPublicKey().getEncoded(), bta.getBcOwnerPublicKeyObj().getEncoded())) {
+ throw new InvalidBtaException("owner public key mismatch");
+ }
+ if (ObjectUtil.isEmpty(bta.getAmId())) {
+ throw new InvalidBtaException("am id is empty");
+ }
+ if (!bta.validate()) {
+ throw new InvalidBtaException("bta sig verification failed");
+ }
+
+ var verifyBtaExtension = VerifyBtaExtension.decode(bta.getExtension());
+ if (ObjectUtil.isNull(verifyBtaExtension)) {
+ throw new InvalidBtaException("extension decode failed");
+ }
+ if (!verifyBtaExtension.getCrossChainLane().isValidated()) {
+ throw new InvalidBtaException("cross chain lane is invalid");
+ }
+ if (!checkIfTpBTAIntersection(verifyBtaExtension.getCrossChainLane())) {
+ throw new InvalidBtaException("tpbta intersection check failed");
+ }
+
+ var latestTpBta = endorseServiceRepository.getExactTpBta(verifyBtaExtension.getCrossChainLane());
+ var tpbta = new ThirdPartyBlockchainTrustAnchorV1(
+ ObjectUtil.isNull(latestTpBta) ? 1 : latestTpBta.getTpbta().getTpbtaVersion() + 1,
+ systemConfigRepository.queryCurrentPtcAnchorVersion(),
+ (PTCCredentialSubject) ptcCrossChainCert.getCredentialSubjectInstance(),
+ verifyBtaExtension.getCrossChainLane(),
+ bta.getSubjectVersion(),
+ ucpHashAlgo,
+ verifyBtaExtension.getCommitteeEndorseRoot().encode(),
+ new byte[]{}
+ );
+ tpbta.setEndorseProof(
+ CommitteeEndorseProof.builder()
+ .committeeId(committeeId)
+ .sigs(ListUtil.toList(
+ CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(nodeSignAlgo.getSigner().sign(nodeKey, tpbta.getEncodedToSign()))
+ .build()
+ )).build().encode()
+ );
+ var tpBtaWrapper = new TpBtaWrapper(tpbta);
+ endorseServiceRepository.setBta(new BtaWrapper(bta));
+ endorseServiceRepository.setTpBta(tpBtaWrapper);
+
+ return tpBtaWrapper;
+ }
+
+ private boolean checkIfTpBTAIntersection(CrossChainLane tpbtaLane) {
+ var wrapper = endorseServiceRepository.getMatchedTpBta(tpbtaLane);
+ if (ObjectUtil.isEmpty(wrapper) || wrapper.getCrossChainLane().equals(tpbtaLane)) {
+ return true;
+ }
+ return wrapper.getTpbta().type().ordinal() > ThirdPartyBlockchainTrustAnchor.TypeEnum.parseFrom(tpbtaLane).ordinal();
+ }
+
+ @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());
+ if (ObjectUtil.isNull(bta)) {
+ throw new InvalidConsensusStateException("bta not found for {}", crossChainLane.getSenderDomain().getDomain());
+ }
+
+ var hcdvs = hcdvsPluginService.getHCDVSService(bta.getProduct());
+ if (ObjectUtil.isNull(hcdvs)) {
+ throw new CommitteeNodeInternalException("hcdvs not found for {}", bta.getProduct());
+ }
+
+ if (!bta.getBta().getInitHeight().equals(anchorState.getHeight())) {
+ throw new InvalidConsensusStateException("invalid height: bta's is {} and yours {}",
+ bta.getBta().getInitHeight().toString(), anchorState.getHeight().toString());
+ }
+ if (!ArrayUtil.equals(bta.getBta().getInitBlockHash(), anchorState.getHash())) {
+ throw new InvalidConsensusStateException("invalid block hash: bta's is {} and yours {}",
+ HexUtil.encodeHexStr(bta.getBta().getInitBlockHash()), anchorState.getHashHex());
+ }
+
+ return processValidatedConsensusState(anchorState, tpbta, hcdvs.verifyAnchorConsensusState(bta.getBta(), anchorState));
+ }
+
+ @Override
+ public ValidatedConsensusState commitConsensusState(CrossChainLane crossChainLane, ConsensusState currState) {
+ 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());
+ if (ObjectUtil.isNull(bta)) {
+ throw new InvalidConsensusStateException("bta not found for {}", crossChainLane.getSenderDomain().getDomain());
+ }
+ var parentConsensusState = endorseServiceRepository.getValidatedConsensusState(
+ currState.getDomain().getDomain(),
+ currState.getHeight().subtract(BigInteger.ONE)
+ );
+ if (ObjectUtil.isNull(parentConsensusState)) {
+ throw new InvalidConsensusStateException("parent consensus state not found for {}", currState.getParentHashHex());
+ }
+
+ var hcdvs = hcdvsPluginService.getHCDVSService(bta.getProduct());
+ if (ObjectUtil.isNull(hcdvs)) {
+ throw new CommitteeNodeInternalException("hcdvs not found for {}", bta.getProduct());
+ }
+
+ return processValidatedConsensusState(currState, tpbta, hcdvs.verifyConsensusState(currState, parentConsensusState.getValidatedConsensusState()));
+ }
+
+ @Override
+ public CommitteeNodeProof verifyUcp(CrossChainLane crossChainLane, UniformCrosschainPacket ucp) {
+ var tpbta = endorseServiceRepository.getExactTpBta(crossChainLane);
+ if (ObjectUtil.isNull(tpbta)) {
+ throw new InvalidCrossChainMessageException("tpbta not found for {}", crossChainLane.getLaneKey());
+ }
+ var bta = endorseServiceRepository.getBta(crossChainLane.getSenderDomain().getDomain(), tpbta.getTpbta().getBtaSubjectVersion());
+ if (ObjectUtil.isNull(bta)) {
+ throw new InvalidCrossChainMessageException("bta not found for {}", crossChainLane.getSenderDomain().getDomain());
+ }
+ var consensusState = endorseServiceRepository.getValidatedConsensusState(
+ ucp.getSrcDomain().getDomain(),
+ ucp.getSrcMessage().getProvableData().getBlockHashHex()
+ );
+ if (ObjectUtil.isNull(consensusState)) {
+ throw new InvalidCrossChainMessageException("consensus state not found for {}", ucp.getSrcMessage().getProvableData().getBlockHashHex());
+ }
+ if (!ArrayUtil.equals(consensusState.getValidatedConsensusState().getHash(), ucp.getSrcMessage().getProvableData().getBlockHash())) {
+ throw new InvalidCrossChainMessageException("expected block hash {} but get {}",
+ consensusState.getValidatedConsensusState().getHash(), ucp.getSrcMessage().getProvableData().getBlockHash());
+ }
+ if (!ObjectUtil.equals(consensusState.getValidatedConsensusState().getHeight(), ucp.getSrcMessage().getProvableData().getHeightVal())) {
+ throw new InvalidCrossChainMessageException("expected block height {} but get {}",
+ consensusState.getValidatedConsensusState().getHeight(), ucp.getSrcMessage().getProvableData().getHeightVal());
+ }
+
+ var hcdvs = hcdvsPluginService.getHCDVSService(bta.getProduct());
+ if (ObjectUtil.isNull(hcdvs)) {
+ throw new CommitteeNodeInternalException("hcdvs not found for {}", bta.getProduct());
+ }
+ if (!ArrayUtil.equals(
+ ucp.getSrcMessage().getMessage(),
+ hcdvs.parseMessageFromLedgerData(ucp.getSrcMessage().getProvableData().getLedgerData())
+ )) {
+ throw new InvalidCrossChainMessageException("message decoded from ledger data not equal to message inside UCP");
+ }
+
+ var verifyResult = hcdvs.verifyCrossChainMessage(ucp.getSrcMessage(), consensusState.getValidatedConsensusState());
+ if (ObjectUtil.isNull(verifyResult) || !verifyResult.isSuccess()) {
+ throw new InvalidCrossChainMessageException("cross chain message verification failed: {}", verifyResult.getErrorMsg());
+ }
+
+ return CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(nodeSignAlgo.getSigner().sign(
+ nodeKey,
+ ThirdPartyProof.create(
+ tpbta.getTpbta().getTpbtaVersion(),
+ ucp.getSrcMessage().getMessage(),
+ crossChainLane
+ ).getEncodedToSign()
+ )).build();
+ }
+
+ @Override
+ public CommitteeNodeProof verifyUcpWithMonitorSystem(CrossChainLane crossChainLane, UniformCrosschainPacket ucp) {
+
+ var tpbta = endorseServiceRepository.getExactTpBta(crossChainLane);
+ if (ObjectUtil.isNull(tpbta)) {
+ throw new InvalidCrossChainMessageException("tpbta not found for {}", crossChainLane.getLaneKey());
+ }
+ var bta = endorseServiceRepository.getBta(crossChainLane.getSenderDomain().getDomain(), tpbta.getTpbta().getBtaSubjectVersion());
+ if (ObjectUtil.isNull(bta)) {
+ throw new InvalidCrossChainMessageException("bta not found for {}", crossChainLane.getSenderDomain().getDomain());
+ }
+
+ log.info("verify ucp with monitor system for domain {} now", bta.getDomain());
+ // log.info("crosslane[senderDomain-recvDomain-senderID-recvID]: {}, {}, {}, {}",
+ // crossChainLane.getSenderDomain().getDomain(), crossChainLane.getReceiverDomain().getDomain(),
+ // crossChainLane.getSenderIdHex(), crossChainLane.getReceiverIdHex());
+// log.info("verify ucp with monitor system for domain {} now", crossChainLane.getSenderDomain().getDomain());
+
+ MonitorSystemServiceGrpc.MonitorSystemServiceBlockingStub monitorSystemServiceBlockingStub = monitorSystemGrpcClientManager.getStub("monitor-system");
+
+ MonitorSystemResponse responseFromMonitorSystem = monitorSystemServiceBlockingStub.verifyCrossChainMessageInMonitorSystem(
+ VerifyCrossChainMessageInMonitorSystemRequest.newBuilder()
+ .setRawUcp(ByteString.copyFrom(ucp.encode()))
+ .build()
+ );
+
+ if (ObjectUtil.isNull(responseFromMonitorSystem)) {
+ throw new RuntimeException("null response from monitor system");
+ }
+ if (responseFromMonitorSystem.getCode() != 0) {
+ throw new RuntimeException(String.format("[MonitorSystemGRpcClient] verifyCrossChainMessageInMonitorSystem request failed for plugin server: %s",
+ responseFromMonitorSystem.getErrorMsg()));
+ }
+ if (responseFromMonitorSystem.getVerifyCrossChainMessageInMonitorSystemResp().getResult() == 0) {
+ // 监管通过 流程正常
+// log.info("verify ucp with monitor system for domain {}: success", bta.getDomain());
+ log.info("verify ucp with monitor system for domain {}: success", crossChainLane.getSenderDomain().getDomain());
+ return CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(nodeSignAlgo.getSigner().sign(
+ nodeKey,
+ ThirdPartyProof.create(
+ tpbta.getTpbta().getTpbtaVersion(),
+ ucp.getSrcMessage().getMessage(),
+ crossChainLane
+ ).getEncodedToSign()
+ )).build();
+ } else {
+ // [监管回滚的v1版本逻辑]
+ // 监管未通过 直接向监管合约发送回滚交易 并且不跑出异常 而是返回一个签名
+ // 目前是返回一个正确的签名, 保证在监管不通过时系统的稳定运行; 在8~9月开发的最终版本中会返回一个空签名, 实现完整的逻辑
+ log.info("verify ucp with monitor system for domain {}: failure", bta.getDomain());
+ // log.info("verify ucp with monitor system for domain {}: failure", crossChainLane.getSenderDomain().getDomain());
+ // IAuthMessage authMessage = AuthMessageFactory.createAuthMessage(ucp.getSrcMessage().getMessage());
+ // ISDPMessage sdpMessage = SDPMessageFactory.createSDPMessage(authMessage.getPayload());
+ // byte[] monitorMessage = sdpMessage.getPayload();
+
+ // CrossChainServiceGrpc.CrossChainServiceBlockingStub crossChainServiceBlockingStub = crossChainServiceGrpcClientManager.getStub("plugin-server");
+ // Response responseFromPS = crossChainServiceBlockingStub.bbcCall(
+ // CallBBCRequest.newBuilder()
+ // .setProduct(bta.getProduct())
+ // .setDomain(bta.getDomain())
+ // .setRelayMonitorRollbackMessageReq(
+ // RelayMonitorRollbackMessageRequest.newBuilder()
+ // .setReceiverDomain(ucp.getSrcDomain().getDomain())
+ // .setToAddress(ucp.getCrossChainLane().getSenderIdHex())
+ // // .setToAddress(authMessage.getIdentity().toHex())
+ // .setRawMessage(ByteString.copyFrom(monitorMessage))
+ // ).build()
+ // );
+
+ // if (ObjectUtil.isNull(responseFromPS)) {
+ // throw new RuntimeException("null response from plugin server");
+ // }
+ // if (responseFromPS.getCode() != 0) {
+ // throw new RuntimeException(
+ // String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] relayMonitorRollbackMessage request failed for plugin server: %s",
+ // bta.getDomain(), bta.getProduct(), responseFromPS.getErrorMsg())
+ // );
+ // }
+
+ // CrossChainMessageReceipt crossChainMessageReceipt = convertFromGRpcCrossChainMessageReceipt(responseFromPS.getBbcResp().getRelayMonitorRollbackMessageResp().getReceipt());
+
+ // 如果回滚消息未成功上链 目前仅抛出异常
+ // if (!crossChainMessageReceipt.isSuccessful()) {
+ // throw new RuntimeException(StrUtil.format("failed to commit monitor rollback message: ( error_msg: {})",
+ // crossChainMessageReceipt.getErrorMsg()));
+ // }
+
+ // [监管回滚的v2版本逻辑]
+ // 返回一个ethereum格式(65字节)的空签名 由目的链的监管合约验证签名时识别为监管失败 构造监管回滚消息
+ return CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(new byte[65]).build();
+
+ // throw new InvalidCrossChainMessageException("[monitor system] illegal crosschain message(block hash: {}): {}",
+ // ucp.getSrcMessage().getProvableData().getBlockHashHex(), responseFromMonitorSystem.getVerifyCrossChainMessageInMonitorSystemResp().getMsg());
+ }
+ }
+
+ @Override
+ public void relayUcpToMonitorSystem(UniformCrosschainPacket ucp) {
+ MonitorSystemServiceGrpc.MonitorSystemServiceBlockingStub monitorSystemServiceBlockingStub = monitorSystemGrpcClientManager.getStub("monitor-system");
+
+ MonitorSystemResponse responseFromMonitorSystem = monitorSystemServiceBlockingStub.relayUcpToMonitorSystem(
+ RelayUcpToMonitorSystemRequest.newBuilder()
+ .setRawUcp(ByteString.copyFrom(ucp.encode()))
+ .build()
+ );
+
+ if (ObjectUtil.isNull(responseFromMonitorSystem)) {
+ throw new RuntimeException("null response from monitor system");
+ }
+ if (responseFromMonitorSystem.getCode() != 0) {
+ throw new RuntimeException(String.format("[MonitorSystemGRpcClient] relayUcpToMonitorSystem request failed: %s",
+ responseFromMonitorSystem.getErrorMsg()));
+ }
+ }
+
+ @Override
+ public EndorseBlockStateResp endorseBlockState(CrossChainLane crossChainLane, String receiverDomain, BigInteger height) {
+ var tpbta = endorseServiceRepository.getExactTpBta(crossChainLane);
+ if (ObjectUtil.isNull(tpbta)) {
+ throw new InvalidCrossChainMessageException("tpbta not found for {}", crossChainLane.getLaneKey());
+ }
+
+ if (!endorseServiceRepository.hasValidatedConsensusState(crossChainLane.getSenderDomain().toString(), height)) {
+ throw new BlockStateNotValidatedYetException("no block validated for height {}", height.toString());
+ }
+
+ var vcs = endorseServiceRepository.getValidatedConsensusState(crossChainLane.getSenderDomain().toString(), height);
+ IAuthMessage am = AuthMessageFactory.createAuthMessage(
+ 1,
+ CrossChainIdentity.ZERO_ID.getRawID(),
+ 0,
+ SDPMessageFactory.createValidatedBlockStateSDPMsg(
+ new CrossChainDomain(receiverDomain),
+ new BlockState(
+ crossChainLane.getSenderDomain(),
+ vcs.getValidatedConsensusState().getHash(),
+ vcs.getHeight(),
+ vcs.getValidatedConsensusState().getStateTimestamp()
+ )
+ ).encode()
+ );
+ return new EndorseBlockStateResp(
+ am,
+ CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(nodeSignAlgo.getSigner().sign(
+ nodeKey,
+ ThirdPartyProof.create(
+ tpbta.getTpbta().getTpbtaVersion(),
+ am.encode(),
+ crossChainLane
+ ).getEncodedToSign()
+ )).build()
+ );
+ }
+
+ @NonNull
+ private ValidatedConsensusState processValidatedConsensusState(ConsensusState currState, TpBtaWrapper tpbta, VerifyResult verifyResult) {
+ if (ObjectUtil.isNull(verifyResult) || !verifyResult.isSuccess()) {
+ throw new InvalidConsensusStateException("consensus state verification failed: {}", verifyResult.getErrorMsg());
+ }
+
+ var vcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ vcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ vcs.setTpbtaVersion(tpbta.getTpbta().getTpbtaVersion());
+ vcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ if (!endorseServiceRepository.hasValidatedConsensusState(currState.getDomain().getDomain(), currState.getHeight())) {
+ endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs));
+ }
+
+ var nodeProof = CommitteeNodeProof.builder()
+ .nodeId(committeeNodeId)
+ .signAlgo(nodeSignAlgo)
+ .signature(nodeSignAlgo.getSigner().sign(nodeKey, vcs.getEncodedToSign()))
+ .build();
+ var proof = new CommitteeEndorseProof();
+ proof.setCommitteeId(committeeId);
+ proof.setSigs(ListUtil.toList(nodeProof));
+ vcs.setPtcProof(proof.encode());
+
+ return vcs;
+ }
+
+ private static CrossChainMessageReceipt convertFromGRpcCrossChainMessageReceipt(com.alipay.antchain.bridge.pluginserver.service.CrossChainMessageReceipt crossChainMessageReceipt) {
+ CrossChainMessageReceipt receipt = new CrossChainMessageReceipt();
+ receipt.setConfirmed(crossChainMessageReceipt.getConfirmed());
+ receipt.setSuccessful(crossChainMessageReceipt.getSuccessful());
+ receipt.setTxhash(crossChainMessageReceipt.getTxhash());
+ receipt.setErrorMsg(crossChainMessageReceipt.getErrorMsg());
+ receipt.setTxTimestamp(crossChainMessageReceipt.getTxTimestamp());
+ receipt.setRawTx(crossChainMessageReceipt.getRawTx().toByteArray());
+
+ return receipt;
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/HcdvsPluginServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/HcdvsPluginServiceImpl.java
new file mode 100755
index 00000000..3043295b
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/HcdvsPluginServiceImpl.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
+import ch.qos.logback.core.util.FileSize;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.plugins.manager.AntChainBridgePluginManagerFactory;
+import com.alipay.antchain.bridge.plugins.manager.core.IAntChainBridgePlugin;
+import com.alipay.antchain.bridge.plugins.manager.core.IAntChainBridgePluginManager;
+import com.alipay.antchain.bridge.plugins.spi.ptc.IHeteroChainDataVerifierService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IHcdvsPluginService;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.ClassLoadingStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class HcdvsPluginServiceImpl implements IHcdvsPluginService {
+
+ private final IAntChainBridgePluginManager manager;
+
+ private final String hcdvsLoggerDir;
+
+ @Value("${committee.plugin.log.hcdvs.max_history:3}")
+ private int maxHCDVSLogHistory;
+
+ @Value("${committee.plugin.log.hcdvs.max_file_size:30mb}")
+ private String maxHCDVSLogFileSize;
+
+ @Value("${committee.plugin.log.hcdvs.level:info}")
+ private String hcdvsLogLevel;
+
+ @Value("${committee.plugin.log.hcdvs.on:true}")
+ private boolean isHCDVSLogOn;
+
+ private final Map hcdvsLoggerMap = new HashMap<>();
+
+ public HcdvsPluginServiceImpl(
+ @Value("${committee.plugin.repo}") String path,
+ @Value("${logging.file.path}") String hcdvsLogDir,
+ @Value("${committee.plugin.policy.classloader.resource.ban-with-prefix.APPLICATION:}") String[] resourceBannedPrefixOnAppLevel
+ ) {
+ log.info("plugins path: {}", Paths.get(path).toAbsolutePath());
+ this.hcdvsLoggerDir = Paths.get(hcdvsLogDir, "hcdvs").toAbsolutePath().toString();
+ log.info("hcdvs logger base dir: {}", Paths.get(hcdvsLoggerDir).toAbsolutePath());
+
+ this.manager = AntChainBridgePluginManagerFactory.createPluginManager(
+ path,
+ ObjectUtil.defaultIfNull(convertPathPrefixBannedMap(resourceBannedPrefixOnAppLevel), new HashMap<>())
+ );
+ loadPlugins();
+ startPlugins();
+ }
+
+ private Map> convertPathPrefixBannedMap(
+ String[] resourceBannedPrefixOnAppLevel
+ ) {
+ Map> result = new HashMap<>();
+
+ Set appSet = new HashSet<>(ListUtil.of(resourceBannedPrefixOnAppLevel));
+ result.put(ClassLoadingStrategy.Source.APPLICATION, appSet);
+
+ return result;
+ }
+
+ @Override
+ public void loadPlugins() {
+ manager.loadPlugins();
+ }
+
+ @Override
+ public void startPlugins() {
+ manager.startPlugins();
+ }
+
+ @Override
+ public void loadPlugin(String path) {
+ manager.loadPlugin(Paths.get(path));
+ }
+
+ @Override
+ public void startPlugin(String path) {
+ manager.startPlugin(Paths.get(path));
+ }
+
+ @Override
+ public void stopPlugin(String product) {
+ manager.stopPlugin(product);
+ }
+
+ @Override
+ public void startPluginFromStop(String product) {
+ manager.startPluginFromStop(product);
+ }
+
+ @Override
+ public void reloadPlugin(String product) {
+ manager.reloadPlugin(product);
+ }
+
+ @Override
+ public void reloadPlugin(String product, String path) {
+ manager.reloadPlugin(product, Paths.get(path));
+ }
+
+ @Override
+ public IAntChainBridgePlugin getPlugin(String product) {
+ return manager.getPlugin(product);
+ }
+
+ @Override
+ public boolean hasPlugin(String product) {
+ return manager.hasPlugin(product);
+ }
+
+ @Override
+ public IHeteroChainDataVerifierService createHCDVSService(String product) {
+ return manager.createHCDVSService(product, createHCDVSServiceLogger(product));
+ }
+
+ @Override
+ public IHeteroChainDataVerifierService getHCDVSService(String product) {
+ var service = manager.getHCDVSService(product);
+ if (ObjectUtil.isNotNull(service)) {
+ return service;
+ }
+ return createHCDVSService(product);
+ }
+
+ private Logger createHCDVSServiceLogger(String product) {
+ if (!isHCDVSLogOn) {
+ return null;
+ }
+ String loggerName = product;
+ if (hcdvsLoggerMap.containsKey(loggerName) && ObjectUtil.isNotNull(hcdvsLoggerMap.get(loggerName))) {
+ return hcdvsLoggerMap.get(loggerName);
+ }
+ Path logFile = Paths.get(hcdvsLoggerDir, product + ".log");
+ Logger logger = LoggerFactory.getLogger(loggerName);
+ if (logger instanceof ch.qos.logback.classic.Logger) {
+ log.debug("using logback for hcdvs logger");
+
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+ encoder.setContext(context);
+ encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n");
+ encoder.setCharset(StandardCharsets.UTF_8);
+ encoder.start();
+
+ SizeAndTimeBasedRollingPolicy rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
+ rollingPolicy.setContext(context);
+ rollingPolicy.setFileNamePattern(logFile + ".%d{yyyy-MM-dd}.%i");
+ rollingPolicy.setMaxHistory(maxHCDVSLogHistory);
+ rollingPolicy.setMaxFileSize(FileSize.valueOf(maxHCDVSLogFileSize));
+
+ RollingFileAppender appender = new RollingFileAppender<>();
+ appender.setContext(context);
+ appender.setEncoder(encoder);
+ appender.setFile(logFile.toString());
+ appender.setRollingPolicy(rollingPolicy);
+
+ rollingPolicy.setParent(appender);
+ rollingPolicy.start();
+ appender.start();
+
+ AsyncAppender asyncAppender = new AsyncAppender();
+ asyncAppender.setContext(context);
+ asyncAppender.setName(loggerName);
+ asyncAppender.addAppender(appender);
+ asyncAppender.start();
+
+ ch.qos.logback.classic.Logger loggerLogback = (ch.qos.logback.classic.Logger) logger;
+ loggerLogback.setLevel(Level.toLevel(hcdvsLogLevel));
+ loggerLogback.setAdditive(false);
+ loggerLogback.addAppender(asyncAppender);
+
+ hcdvsLoggerMap.put(loggerName, loggerLogback);
+ log.info("hcdvs logger {} created", loggerName);
+
+ return loggerLogback;
+ }
+
+ log.debug("logger library not support for now");
+ return null;
+ }
+
+ @Override
+ public boolean hasProduct(String product) {
+ return manager.hasProduct(product);
+ }
+
+ @Override
+ public List getAvailableProducts() {
+ return manager.allSupportProducts();
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/MonitorServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/MonitorServiceImpl.java
new file mode 100755
index 00000000..5c259a6a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/MonitorServiceImpl.java
@@ -0,0 +1,110 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt;
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorOrderV1;
+import com.alipay.antchain.bridge.pluginserver.service.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.client.CrossChainServiceGrpcClientManager;
+import com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IMonitorService;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.google.protobuf.ByteString;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.slf4j.Logger;
+import org.slf4j.helpers.NOPLogger;
+
+import java.security.PrivateKey;
+import java.util.Map;
+import java.util.concurrent.*;
+
+@Service
+@Slf4j
+public class MonitorServiceImpl implements IMonitorService {
+
+ @Value("${committee.id}")
+ private String committeeId;
+
+ //Keccak256WithSecp256k1
+ @Value("${committee.node.endorse.sign_algo:KECCAK256_WITH_SECP256K1}")
+ private SignAlgoEnum nodeSignAlgo;
+
+ @Resource
+ private PrivateKey nodeKey;
+
+ @Resource
+ private CrossChainServiceGrpcClientManager crossChainServiceGrpcClientManager;
+
+ @Override
+ public void recvMonitorOrder(MonitorOrderV1 monitorOrder) {
+
+ // sign
+ byte[] encodedMonitorOrder = monitorOrder.encode();
+ byte[] signature = nodeSignAlgo.getSigner().sign(nodeKey, encodedMonitorOrder);
+
+ CrossChainServiceGrpc.CrossChainServiceBlockingStub crossChainServiceBlockingStub = crossChainServiceGrpcClientManager.getStub("plugin-server");
+ Response responseFromPS = crossChainServiceBlockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(monitorOrder.getProduct())
+ .setDomain(monitorOrder.getDomain())
+ .setRelayMonitorOrderReq(
+ RelayMonitorOrderRequest.newBuilder()
+ .setCommitteeId(committeeId)
+ .setSignAlgo(nodeSignAlgo.getName())
+ .setRawProof(ByteString.copyFrom(signature))
+ .setRawMonitorOrder(ByteString.copyFrom(encodedMonitorOrder))
+ )
+ .build()
+ );
+// Response responseFromPS = crossChainServiceBlockingStub.bbcCall(
+// CallBBCRequest.newBuilder()
+// .setProduct(monitorOrder.getProduct())
+// .setDomain(monitorOrder.getDomain())
+// .setRelayMonitorOrderReq(
+// RelayMonitorOrderRequest.newBuilder()
+// .setMonitorOrderType(monitorOrder.getMonitorOrderType())
+// .setSenderDomain(monitorOrder.getSenderDomain())
+// .setFromAddress(monitorOrder.getFromAddress())
+// .setReceiverDomain(monitorOrder.getReceiveDomain())
+// .setToAddress(monitorOrder.getToAddress())
+// .setTransactionContent(monitorOrder.getTransactionContent())
+// .setExtra(monitorOrder.getExtra())
+// ).build()
+// );
+
+ if (ObjectUtil.isNull(responseFromPS)) {
+ throw new RuntimeException("null response from plugin server");
+ }
+ if (responseFromPS.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] relayMonitorOrder request failed for plugin server: %s",
+ monitorOrder.getDomain(), monitorOrder.getProduct(), responseFromPS.getErrorMsg())
+ );
+ }
+
+ CrossChainMessageReceipt crossChainMessageReceipt = convertFromGRpcCrossChainMessageReceipt(responseFromPS.getBbcResp().getRelayMonitorOrderResp().getReceipt());
+
+ // 如果监管指令消息未成功上链 目前仅抛出异常
+ if (!crossChainMessageReceipt.isSuccessful()) {
+ throw new RuntimeException(StrUtil.format("failed to commit monitor order: (error_msg: {})",
+ crossChainMessageReceipt.getErrorMsg()));
+ }
+ }
+
+ private static CrossChainMessageReceipt convertFromGRpcCrossChainMessageReceipt(com.alipay.antchain.bridge.pluginserver.service.CrossChainMessageReceipt crossChainMessageReceipt) {
+ CrossChainMessageReceipt receipt = new CrossChainMessageReceipt();
+ receipt.setConfirmed(crossChainMessageReceipt.getConfirmed());
+ receipt.setSuccessful(crossChainMessageReceipt.getSuccessful());
+ receipt.setTxhash(crossChainMessageReceipt.getTxhash());
+ receipt.setErrorMsg(crossChainMessageReceipt.getErrorMsg());
+ receipt.setTxTimestamp(crossChainMessageReceipt.getTxTimestamp());
+ receipt.setRawTx(crossChainMessageReceipt.getRawTx().toByteArray());
+
+ return receipt;
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/ScheduledTaskServiceImpl.java b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/ScheduledTaskServiceImpl.java
new file mode 100755
index 00000000..80120dc0
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/impl/ScheduledTaskServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service.impl;
+
+import java.math.BigInteger;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.exception.CommitteeNodeInternalException;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IBCDNSManageService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IScheduledTaskService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class ScheduledTaskServiceImpl implements IScheduledTaskService {
+
+ @Resource
+ private IBCDNSManageService bcdnsManageService;
+
+ @Resource
+ private AbstractCrossChainCertificate ptcCrossChainCert;
+
+ @Resource
+ private ISystemConfigRepository systemConfigRepository;
+
+ @Override
+ @Scheduled(fixedDelayString = "${committee.node.schedule.ptc-trust-root-listen.fixed-delay:60000}")
+ public void listenPtcTrustRoot() {
+ try {
+ if (bcdnsManageService.countBCDNSService() == 0) {
+ log.info("have no bcdns service, please add one at least");
+ return;
+ }
+
+ DomainSpaceCertWrapper domainSpaceCertWrapper = bcdnsManageService.getDomainSpaceCert(ptcCrossChainCert.getIssuer());
+ if (ObjectUtil.isNull(domainSpaceCertWrapper)) {
+ throw new CommitteeNodeInternalException(
+ "No domain space cert found for issuer {}", HexUtil.encodeHexStr(ptcCrossChainCert.getIssuer().encode())
+ );
+ }
+ PTCTrustRoot ptcTrustRoot = bcdnsManageService.getBCDNSClient(domainSpaceCertWrapper.getDomainSpace())
+ .queryPTCTrustRoot(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ if (ObjectUtil.isNull(ptcTrustRoot)) {
+ throw new CommitteeNodeInternalException("No ptc trust root found");
+ }
+
+ BigInteger currVer = systemConfigRepository.queryCurrentPtcAnchorVersion();
+ BigInteger verOnBcdns = ptcTrustRoot.getVerifyAnchorMap().keySet().stream().max(BigInteger::compareTo).orElse(BigInteger.ZERO);
+ if (currVer.compareTo(verOnBcdns) >= 0) {
+ log.debug("No new ptc trust root found");
+ return;
+ }
+ log.info("New ptc trust root found, new verify anchors from version {} to {}", currVer, verOnBcdns);
+ systemConfigRepository.setPtcTrustRoot(ptcTrustRoot);
+ } catch (Throwable t) {
+ log.error("Failed to listen ptc trust root", t);
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/main/proto/admingrpc.proto b/acb-committeeptc/monitor-node/src/main/proto/admingrpc.proto
new file mode 100755
index 00000000..a0fc11eb
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/proto/admingrpc.proto
@@ -0,0 +1,74 @@
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "com.alipay.antchain.bridge.ptc.committee.monitor.node.server.grpc";
+option java_outer_classname = "AdminGrpcServerOuter";
+option objc_class_prefix = "AdminGrpcServer";
+
+package acb.ptc.committee.node.admin;
+
+message Response {
+ uint32 code = 1;
+ string errorMsg = 2;
+ oneof response {
+ GetBcdnsServiceInfoResp getBcdnsServiceInfoResp = 3;
+ GetBcdnsCertificateResp getBcdnsCertificateResp = 4;
+ }
+}
+
+message RegisterBcdnsServiceRequest {
+ string domainSpace = 1;
+ string bcdnsType = 2;
+ bytes config = 3;
+ optional string bcdnsRootCert = 4;
+}
+
+message GetBcdnsServiceInfoRequest {
+ string domainSpace = 1;
+}
+
+message GetBcdnsServiceInfoResp {
+ string infoJson = 1;
+}
+
+message DeleteBcdnsServiceRequest {
+ string domainSpace = 1;
+}
+
+message GetBcdnsCertificateRequest {
+ string domainSpace = 1;
+}
+
+message GetBcdnsCertificateResp {
+ string certificate = 1;
+}
+
+message StopBcdnsServiceRequest {
+ string domainSpace = 1;
+}
+
+message RestartBcdnsServiceRequest {
+ string domainSpace = 1;
+}
+
+message AddPtcTrustRootRequest {
+ bytes rawTrustRoot = 1;
+}
+
+// The greeting service definition.
+service AdminService {
+
+ rpc registerBcdnsService(RegisterBcdnsServiceRequest) returns (Response) {}
+
+ rpc getBcdnsServiceInfo(GetBcdnsServiceInfoRequest) returns (Response) {}
+
+ rpc deleteBcdnsService(DeleteBcdnsServiceRequest) returns (Response) {}
+
+ rpc getBcdnsCertificate(GetBcdnsCertificateRequest) returns (Response) {}
+
+ rpc stopBcdnsService(StopBcdnsServiceRequest) returns (Response) {}
+
+ rpc restartBcdnsService(RestartBcdnsServiceRequest) returns (Response) {}
+
+ rpc addPtcTrustRoot(AddPtcTrustRootRequest) returns (Response) {}
+}
diff --git a/acb-committeeptc/monitor-node/src/main/proto/monitorSystemgrpc.proto b/acb-committeeptc/monitor-node/src/main/proto/monitorSystemgrpc.proto
new file mode 100755
index 00000000..be14f86d
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/proto/monitorSystemgrpc.proto
@@ -0,0 +1,71 @@
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "com.alipay.antchain.bridge.ptc.committee.monitor.system.grpc";
+option java_outer_classname = "MonitorGrpcServerOuter";
+option objc_class_prefix = "MonitorGrpcServer";
+
+package acb.ptc.committee.monitor.node;
+
+// 服务a: 监管节点 --> 监管系统
+service MonitorSystemService {
+
+ rpc heartbeat(Empty) returns (MonitorSystemResponse) {}
+
+ rpc verifyCrossChainMessageInMonitorSystem(VerifyCrossChainMessageInMonitorSystemRequest) returns (MonitorSystemResponse) {}
+
+ rpc relayUcpToMonitorSystem(RelayUcpToMonitorSystemRequest) returns (MonitorSystemResponse) {}
+}
+
+message Empty {}
+
+// 监管节点作为客户端向监管系统请求验证ucp的合法性(VerifyCrossChainMessage方法中调用)
+message VerifyCrossChainMessageInMonitorSystemRequest {
+ bytes rawUcp = 1;
+}
+
+// 接收无需监管的跨链消息,并转发给课题四监管系统供其分析(VerifyCrossChainMessage方法中调用)
+message RelayUcpToMonitorSystemRequest {
+ bytes rawUcp = 1;
+}
+
+message MonitorSystemResponse {
+ uint32 code = 1;
+ string errorMsg = 2;
+ oneof response {
+ VerifyCrossChainMessageInMonitorSystemResponse verifyCrossChainMessageInMonitorSystemResp = 3;
+ }
+}
+
+message VerifyCrossChainMessageInMonitorSystemResponse {
+ uint32 result = 1;
+ string msg = 2;
+}
+
+// 服务b: 监管系统 --> 监管节点
+service MonitorOrderService {
+
+ rpc recvMonitorOrder(RecvMonitorOrderRequest) returns (RecvMonitorOrderResponse) {}
+}
+
+// 接收课题四监管系统发送的监管指令
+message RecvMonitorOrderRequest {
+ MonitorOrder monitorOrder = 1;
+}
+
+message RecvMonitorOrderResponse {
+ uint32 code = 1;
+ string errorMsg = 2;
+}
+
+message MonitorOrder {
+ string product = 1;
+ string domain = 2;
+ uint64 monitorOrderType = 3;
+ string senderDomain = 4;
+ string fromAddress = 5;
+ string receiverDomain = 6;
+ string toAddress = 7;
+ string transactionContent = 8;
+ string extra = 9;
+}
diff --git a/acb-committeeptc/monitor-node/src/main/proto/pluginserver.proto b/acb-committeeptc/monitor-node/src/main/proto/pluginserver.proto
new file mode 100755
index 00000000..a7f48eda
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/proto/pluginserver.proto
@@ -0,0 +1,432 @@
+syntax = "proto3";
+
+package com.alipay.antchain.bridge.pluginserver.service;
+
+option java_multiple_files = true;
+option java_package = "com.alipay.antchain.bridge.pluginserver.service";
+option java_outer_classname = "PluginRpcServer";
+
+// just empty
+message Empty {}
+
+service CrossChainService {
+ // Relayer would call this interface to communicate with the `BBCService` object
+ rpc bbcCall(CallBBCRequest) returns (Response) {}
+
+ // handle heartbeat requests from relayers
+ rpc heartbeat(Empty) returns (Response) {}
+
+ // return if these blockchain products support or not
+ rpc ifProductSupport(IfProductSupportRequest) returns (Response) {}
+
+ // return if these blockchain domains alive or not
+ rpc ifDomainAlive(IfDomainAliveRequest) returns (Response) {}
+}
+
+// heartbeat response
+message HeartbeatResponse {
+ repeated string products = 1;
+ repeated string domains = 2;
+}
+
+message IfProductSupportRequest {
+ repeated string products = 1;
+}
+
+message IfProductSupportResponse {
+ // key : which product
+ // value : support or not
+ map results = 1;
+}
+
+message IfDomainAliveRequest {
+ repeated string domains = 1;
+}
+
+message IfDomainAliveResponse {
+ // key : which domain
+ // value : alive or not
+ map results = 1;
+}
+
+// wrapper for all responses
+message Response {
+ uint32 code = 1;
+ string errorMsg = 2;
+ oneof response {
+ CallBBCResponse bbcResp = 3;
+ HeartbeatResponse heartbeatResp = 4;
+ IfProductSupportResponse ifProductSupportResp = 5;
+ IfDomainAliveResponse ifDomainAliveResp = 6;
+ }
+}
+
+// messages for `bbcCall` requests
+message CallBBCRequest {
+ // which kind of blockchain for plugin to load
+ string product = 1;
+
+ // which domain of the blockchain for the `BBCService` to connect with
+ string domain = 2;
+
+ // biz request for `BBCService`
+ // basically, evey interface of `BBCService` has a request message defined here.
+ oneof request {
+ StartUpRequest startUpReq = 3;
+ GetContextRequest getContextReq = 4;
+ ShutdownRequest shutdownReq = 5;
+ SetupAuthMessageContractRequest setupAuthMessageContractReq = 6;
+ SetupSDPMessageContractRequest setupSDPMessageContractReq = 7;
+ SetProtocolRequest setProtocolReq = 8;
+ RelayAuthMessageRequest relayAuthMessageReq = 9;
+ SetAmContractRequest setAmContractReq = 10;
+ ReadCrossChainMessageReceiptRequest readCrossChainMessageReceiptReq = 11;
+ ReadCrossChainMessagesByHeightRequest readCrossChainMessagesByHeightReq = 12;
+ QuerySDPMessageSeqRequest querySDPMessageSeqReq = 13;
+ QueryLatestHeightRequest queryLatestHeightReq = 14;
+ SetLocalDomainRequest setLocalDomainReq = 15;
+ ReadConsensusStateRequest readConsensusStateReq = 16;
+ HasTpBtaRequest hasTpBtaReq = 17;
+ GetTpBtaRequest getTpBtaReq = 18;
+ GetSupportedPTCTypeRequest getSupportedPTCTypeReq = 19;
+ GetPTCTrustRootRequest getPTCTrustRootReq = 20;
+ HasPTCTrustRootRequest hasPTCTrustRootReq = 21;
+ GetPTCVerifyAnchorRequest getPTCVerifyAnchorReq = 22;
+ HasPTCVerifyAnchorRequest hasPTCVerifyAnchorReq = 23;
+ SetupPTCContractRequest setupPTCContractReq = 24;
+ UpdatePTCTrustRootRequest updatePTCTrustRootReq = 25;
+ AddTpBtaRequest addTpBtaReq = 26;
+ SetPtcContractRequest setPtcContractReq = 27;
+ QueryValidatedBlockStateRequest queryValidatedBlockStateRequest = 28;
+ RecvOffChainExceptionRequest recvOffChainExceptionRequest = 29;
+ ReliableRetryRequest reliableRetryRequest = 30;
+ SetupMonitorMessageContractRequest setupMonitorMessageContractReq=31;
+ SetMonitorContractRequest setMonitorContractReq = 32;
+ SetProtocolInMonitorRequest setProtocolInMonitorReq = 33;
+ SetMonitorControlRequest setMonitorControlReq = 34;
+ SetPtcHubInMonitorVerifierRequest setPtcHubInMonitorVerifierReq = 35;
+ RelayMonitorOrderRequest relayMonitorOrderReq =36;
+ }
+}
+
+message StartUpRequest {
+ bytes rawContext = 1;
+}
+
+message GetContextRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message ShutdownRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetupAuthMessageContractRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetupSDPMessageContractRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetupMonitorMessageContractRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetMonitorContractRequest {
+ string contractAddress = 1;
+}
+
+message SetProtocolInMonitorRequest {
+ string protocolAddress = 1;
+}
+
+message SetMonitorControlRequest {
+ uint32 monitorType = 1;
+}
+
+message SetPtcHubInMonitorVerifierRequest {
+ string contractAddress = 1;
+}
+
+message SetProtocolRequest {
+ string protocolAddress = 1;
+ string protocolType = 2;
+}
+
+message SetPtcContractRequest {
+ string ptcContractAddress = 1;
+}
+
+message RelayAuthMessageRequest {
+ bytes rawMessage = 1;
+}
+
+message SetAmContractRequest {
+ string contractAddress = 1;
+}
+
+message ReadCrossChainMessageReceiptRequest {
+ string txhash = 1;
+}
+
+message ReadCrossChainMessagesByHeightRequest {
+ uint64 height = 1;
+}
+
+message QuerySDPMessageSeqRequest {
+ string senderDomain = 1;
+ string fromAddress = 2;
+ string receiverDomain = 3;
+ string toAddress = 4;
+}
+
+message QueryLatestHeightRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetLocalDomainRequest {
+ string domain = 1;
+}
+
+message ReadConsensusStateRequest {
+ string height = 1;
+}
+
+message HasTpBtaRequest {
+ string tpbtaLane = 1;
+ uint32 tpBtaVersion = 2;
+}
+
+message GetTpBtaRequest {
+ string tpbtaLane = 1;
+ uint32 tpBtaVersion = 2;
+}
+
+message GetSupportedPTCTypeRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message GetPTCTrustRootRequest {
+ bytes ptcOwnerOid = 1;
+}
+
+message HasPTCTrustRootRequest {
+ bytes ptcOwnerOid = 1;
+}
+
+message GetPTCVerifyAnchorRequest {
+ bytes ptcOwnerOid = 1;
+ string verifyAnchorVersion = 2;
+}
+
+message HasPTCVerifyAnchorRequest {
+ bytes ptcOwnerOid = 1;
+ string verifyAnchorVersion = 2;
+}
+
+message SetupPTCContractRequest {
+}
+
+message UpdatePTCTrustRootRequest {
+ bytes ptcTrustRoot = 1;
+}
+
+message AddTpBtaRequest {
+ bytes tpBta = 1;
+}
+
+message QueryValidatedBlockStateRequest {
+ string receiverDomain = 1;
+}
+
+message RecvOffChainExceptionRequest {
+ string exceptionMsgAuthor = 1;
+ bytes exceptionMsgPkg = 2;
+}
+
+message ReliableRetryRequest {
+ bytes reliableCrossChainMessageData = 1;
+}
+
+message RelayMonitorOrderRequest {
+ string committeeId = 1;
+ string signAlgo = 2;
+ bytes rawProof = 3;
+ bytes rawMonitorOrder = 4;
+}
+
+// basic messages.
+// same as project `antchain-bridge-commons`
+message CrossChainMessageReceipt {
+ string txhash = 1;
+ bool confirmed = 2;
+ bool successful = 3;
+ string errorMsg = 4;
+ uint64 txTimestamp = 5;
+ bytes rawTx = 6;
+}
+
+enum CrossChainMessageType {
+ AUTH_MSG = 0;
+ DEVELOPER_DESIGN = 1;
+}
+
+message ProvableLedgerData {
+ uint64 height = 1;
+ bytes blockHash = 2;
+ uint64 timestamp = 3;
+ bytes ledgerData = 4;
+ bytes proof = 5;
+ bytes txHash = 6;
+}
+
+message CrossChainMessage {
+ CrossChainMessageType type = 1;
+ bytes message = 2;
+ ProvableLedgerData provableData = 3;
+}
+
+// messages for `bbcCall` responses
+message CallBBCResponse {
+ oneof response {
+ GetContextResponse getContextResp = 1;
+ SetupAuthMessageContractResponse setupAMResp = 2;
+ SetupSDPMessageContractResponse setupSDPResp = 3;
+ ReadCrossChainMessageReceiptResponse readCrossChainMessageReceiptResp = 4;
+ ReadCrossChainMessagesByHeightResponse readCrossChainMessagesByHeightResp = 5;
+ QuerySDPMessageSeqResponse querySDPMsgSeqResp = 6;
+ RelayAuthMessageResponse relayAuthMessageResponse = 7;
+ QueryLatestHeightResponse queryLatestHeightResponse = 8;
+ ReadConsensusStateResponse readConsensusStateResponse = 9;
+ HasTpBtaResponse hasTpBtaResp = 10;
+ GetTpBtaResponse getTpBtaResp = 11;
+ GetSupportedPTCTypeResponse getSupportedPTCTypeResp = 12;
+ GetPTCTrustRootResponse getPTCTrustRootResp = 13;
+ HasPTCTrustRootResponse hasPTCTrustRootResp = 14;
+ GetPTCVerifyAnchorResponse getPTCVerifyAnchorResp = 15;
+ HasPTCVerifyAnchorResponse hasPTCVerifyAnchorResp = 16;
+ SetupPtcContractResponse setupPtcContractResp = 17;
+ QueryValidatedBlockStateResponse queryValidatedBlockStateResponse = 18;
+ RecvOffChainExceptionResponse recvOffChainExceptionResponse = 19;
+ ReliableRetryResponse reliableRetryResponse = 20;
+ SetupMonitorMessageContractResponse setupMonitorResp = 21;
+ RelayMonitorOrderResponse relayMonitorOrderResp = 22;
+ }
+}
+
+message GetContextResponse {
+ bytes rawContext = 1;
+}
+
+enum ContractStatusEnum {
+ INIT = 0;
+ CONTRACT_DEPLOYED = 1;
+ CONTRACT_READY = 2;
+ CONTRACT_FREEZE = 3;
+}
+
+message AuthMessageContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+message SetupAuthMessageContractResponse {
+ AuthMessageContract amContract = 1;
+}
+
+message SDPMessageContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+message SetupSDPMessageContractResponse {
+ SDPMessageContract sdpContract = 1;
+}
+
+message MonitorMessageContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+
+message SetupMonitorMessageContractResponse {
+ MonitorMessageContract monitorContract = 1;
+}
+
+message PtcContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+message SetupPtcContractResponse {
+ PtcContract ptcContract = 1;
+}
+
+message ReadCrossChainMessageReceiptResponse {
+ CrossChainMessageReceipt receipt = 1;
+}
+
+message ReadCrossChainMessagesByHeightResponse {
+ repeated CrossChainMessage messageList = 1;
+}
+
+message QuerySDPMessageSeqResponse {
+ uint64 sequence = 1;
+}
+
+message RelayAuthMessageResponse {
+ CrossChainMessageReceipt receipt = 1;
+}
+
+message QueryLatestHeightResponse {
+ uint64 height = 1;
+}
+
+message ReadConsensusStateResponse {
+ bytes consensusState = 1;
+}
+
+message HasTpBtaResponse {
+ bool result = 1;
+}
+
+message GetTpBtaResponse {
+ bytes tpBta = 1;
+}
+
+message GetSupportedPTCTypeResponse {
+ repeated string ptcTypes = 1;
+}
+
+message GetPTCTrustRootResponse {
+ bytes ptcTrustRoot = 1;
+}
+
+message HasPTCTrustRootResponse {
+ bool result = 1;
+}
+
+message GetPTCVerifyAnchorResponse {
+ bytes ptcVerifyAnchor = 1;
+}
+
+message HasPTCVerifyAnchorResponse {
+ bool result = 1;
+}
+
+message QueryValidatedBlockStateResponse {
+ bytes blockStateData = 1;
+}
+
+message RecvOffChainExceptionResponse {
+ CrossChainMessageReceipt receipt = 1;
+}
+
+message ReliableRetryResponse {
+ CrossChainMessageReceipt receipt = 1;
+}
+
+message RelayMonitorOrderResponse {
+ CrossChainMessageReceipt receipt = 1;
+}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/application.yml b/acb-committeeptc/monitor-node/src/main/resources/application.yml
new file mode 100755
index 00000000..9f2e8935
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/application.yml
@@ -0,0 +1,59 @@
+spring:
+ application:
+ name: committee-ptc
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/committee_node?serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
+ password: YOUR_PWD
+ username: root
+logging:
+ file:
+ path: ./logs
+ level:
+ app: INFO
+# setting for committee
+committee:
+ id: default
+ node:
+ id: monitor-node
+ credential:
+ sign-algo: "Keccak256WithSecp256k1"
+ private-key-file: "file:private_key.pem"
+ cert-file: "file:ptc.crt"
+ plugin:
+ # where to load the hetero-chain plugins
+ repo: ./plugins
+ policy:
+ # limit actions of the plugin classloader
+ classloader:
+ resource:
+ ban-with-prefix:
+ # the plugin classloader will not read the resource file starting with the prefix below
+ APPLICATION: "META-INF/services/io.grpc."
+grpc:
+ server:
+ port: 10081
+ security:
+ # enable tls mode
+ enabled: true
+ # server certificate
+ certificate-chain: file:tls_certs/server.crt
+ # server key
+ private-key: file:tls_certs/server.key
+ # Mutual Certificate Authentication
+ trustCertCollection: file:tls_certs/trust.crt
+ # clientAuth: REQUIRE
+ clients:
+ # monitor system
+ monitor-system:
+ host: localhost
+ port: 50051
+ # ps-server
+ plugin-server:
+ host: localhost
+ port: 9090
+ ps-id: ps01
+ security:
+ certificate-chain: file:tls_certs/server.crt
+ private-key: file:tls_certs/server.key
+ pluginServerCert: file:tls_certs/pluginServer.crt
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/banner.txt b/acb-committeeptc/monitor-node/src/main/resources/banner.txt
new file mode 100755
index 00000000..cfcc6a90
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/banner.txt
@@ -0,0 +1,7 @@
+ __ __ ___ _ _ ___ _____ ___ ___ _ _ ___ ___ ___
+| \/ | / _ \ | \| | |_ _| |_ _| / _ \ | _ \ ___ | \| | / _ \ | \ | __|
+| |\/| | | (_) | | .` | | | | | | (_) | | / |___| | .` | | (_) | | |) | | _|
+|_| |_| \___/ |_|\_| |___| _|_|_ \___/ |_|_\ |_|\_| \___/ |___/ |___|
+
+${AnsiStyle.BOLD} @project.version@
+${AnsiStyle.NORMAL}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/ddl.sql b/acb-committeeptc/monitor-node/src/main/resources/ddl.sql
new file mode 100755
index 00000000..f23a7b6c
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/ddl.sql
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+CREATE DATABASE IF NOT EXISTS committee_node;
+USE committee_node;
+
+DROP TABLE IF EXISTS system_config;
+CREATE TABLE IF NOT EXISTS `system_config`
+(
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `conf_key` VARCHAR(128) DEFAULT NULL,
+ `conf_value` VARCHAR(15000) DEFAULT NULL,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `conf_key` (`conf_key`)
+) ENGINE = InnoDB
+ ROW_FORMAT = DYNAMIC;
+
+DROP TABLE IF EXISTS `bcdns_service`;
+CREATE TABLE IF NOT EXISTS `bcdns_service`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `domain_space` VARCHAR(128) BINARY NOT NULL,
+ `parent_space` VARCHAR(128) BINARY,
+ `owner_oid` VARCHAR(255) NOT NULL,
+ `type` VARCHAR(32) NOT NULL,
+ `state` INT NOT NULL,
+ `properties` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX bcdns_network_id_domain_space
+ ON bcdns_service (domain_space);
+
+DROP TABLE IF EXISTS `domain_space_cert`;
+CREATE TABLE `domain_space_cert`
+(
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `domain_space` VARCHAR(128) BINARY DEFAULT NULL,
+ `parent_space` VARCHAR(128) BINARY DEFAULT NULL,
+ `owner_oid_hex` VARCHAR(255) NOT NULL,
+ `description` VARCHAR(128) DEFAULT NULL,
+ `domain_space_cert` LONGBLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `domain_space` (`domain_space`)
+) ENGINE = InnoDB;
+CREATE INDEX domain_space_cert_owner_oid_hex
+ ON domain_space_cert (owner_oid_hex);
+
+DROP TABLE IF EXISTS `bta`;
+CREATE TABLE IF NOT EXISTS `bta`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `product` VARCHAR(64) DEFAULT NULL,
+ `domain` VARCHAR(128) BINARY NOT NULL,
+ `bta_version` INT(11) DEFAULT NULL,
+ `subject_version` INT(11) DEFAULT NULL,
+ `raw_bta` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX bta_unique_key_domain_subject_version
+ ON bta (domain, subject_version);
+
+DROP TABLE IF EXISTS `tpbta`;
+CREATE TABLE IF NOT EXISTS `tpbta`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `version` INT(11) DEFAULT NULL,
+ `sender_domain` VARCHAR(128) BINARY NOT NULL,
+ `bta_subject_version` INT(11) DEFAULT NULL,
+ `sender_id` VARCHAR(64) DEFAULT NULL,
+ `receiver_domain` VARCHAR(128) DEFAULT NULL,
+ `receiver_id` VARCHAR(64) DEFAULT NULL,
+ `tpbta_version` INT(11) DEFAULT NULL,
+ `ptc_verify_anchor_version` INT(11) DEFAULT NULL,
+ `raw_tpbta` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX tpbta_unique_key_crosschain_lane_and_version
+ ON tpbta (sender_domain, sender_id, receiver_domain, receiver_id, tpbta_version);
+
+CREATE TABLE IF NOT EXISTS `validated_consensus_states`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `cs_version` INT(11) NOT NULL,
+ `domain` VARCHAR(128) BINARY NOT NULL,
+ `height` BIGINT UNSIGNED NOT NULL,
+ `hash` VARCHAR(64) DEFAULT NULL,
+ `parent_hash` VARCHAR(64) DEFAULT NULL,
+ `raw_vcs` LONGBLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE UNIQUE INDEX committee_node_vcs_unique_key
+ ON validated_consensus_states (domain, height);
+CREATE INDEX committee_node_vcs_unique_key_hash
+ ON validated_consensus_states (domain, hash);
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/logback-spring.xml b/acb-committeeptc/monitor-node/src/main/resources/logback-spring.xml
new file mode 100755
index 00000000..372baddf
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/logback-spring.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${logging.path}/${APP_NAME}/error.log
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
+ %msg%n
+
+ UTF-8
+
+
+ ${logging.path}/${APP_NAME}/error.log.%d{yyyy-MM-dd}.%i
+ 7
+ 100MB
+ 2GB
+
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+
+
+ 0
+
+ 512
+
+
+
+
+
+
+ ${logging.path}/${APP_NAME}/application.log
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
+ %msg%n
+
+ UTF-8
+
+
+ ${logging.path}/${APP_NAME}/application.log.%d{yyyy-MM-dd}.%i
+ 7
+ 50MB
+ 2GB
+ true
+
+
+ ERROR
+ DENY
+ ACCEPT
+
+
+
+
+ 0
+
+ 512
+
+
+
+
+
+
+
+
+
+ %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
+
+
+
+ INFO
+
+
+
+
+
+ ${logging.path}/${APP_NAME}/req_trace.log
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
+ %msg%n
+
+ UTF-8
+
+
+ ${logging.path}/${APP_NAME}/req_trace.log.%d{yyyy-MM-dd}.%i
+ 7
+ 50MB
+ 500MB
+ true
+
+
+
+
+ 0
+
+ 256
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/scripts/init_tls_certs.sh b/acb-committeeptc/monitor-node/src/main/resources/scripts/init_tls_certs.sh
new file mode 100755
index 00000000..e621409e
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/scripts/init_tls_certs.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+#
+# Copyright 2023 Ant Group
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+CURR_DIR="$(cd `dirname $0`; pwd)"
+source ${CURR_DIR}/print.sh
+
+print_title
+
+if [ ! -d ${CURR_DIR}/../tls_certs ]; then
+ mkdir -p ${CURR_DIR}/../tls_certs
+fi
+
+openssl genrsa -out ${CURR_DIR}/../tls_certs/server.key 2048 > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ log_error "failed to generate server.key"
+ exit 1
+fi
+openssl pkcs8 -topk8 -inform pem -in ${CURR_DIR}/../tls_certs/server.key -nocrypt -out ${CURR_DIR}/../tls_certs/server_pkcs8.key
+if [ $? -ne 0 ]; then
+ log_error "failed to generate pkcs8 server.key"
+ exit 1
+fi
+mv ${CURR_DIR}/../tls_certs/server_pkcs8.key ${CURR_DIR}/../tls_certs/server.key
+log_info "generate server.key successfully"
+
+openssl req -new -x509 -days 36500 -key ${CURR_DIR}/../tls_certs/server.key -out ${CURR_DIR}/../tls_certs/server.crt -subj "/C=CN/ST=mykey/L=mykey/O=mykey/OU=mykey/CN=MONITOR-NODE"
+if [ $? -ne 0 ]; then
+ log_error "failed to generate server.crt"
+ exit 1
+fi
+log_info "generate server.crt successfully"
+
+if [ ! -f "trust.crt" ]; then
+ cp ${CURR_DIR}/../tls_certs/server.crt ${CURR_DIR}/../tls_certs/trust.crt
+ log_info "generate trust.crt successfully"
+fi
diff --git a/acb-committeeptc/monitor-node/src/main/resources/scripts/monitor-node.service b/acb-committeeptc/monitor-node/src/main/resources/scripts/monitor-node.service
new file mode 100755
index 00000000..5b8b60c5
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/scripts/monitor-node.service
@@ -0,0 +1,28 @@
+#
+# Copyright 2023 Ant Group
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[Unit]
+Description=start shell script
+StartLimitIntervalSec=0
+
+[Service]
+ExecStart=@@START_CMD@@
+Restart=always
+RestartSec=5
+WorkingDirectory=@@WORKING_DIR@@
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/main/resources/scripts/print.sh b/acb-committeeptc/monitor-node/src/main/resources/scripts/print.sh
new file mode 100755
index 00000000..bc347af3
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/scripts/print.sh
@@ -0,0 +1,73 @@
+#
+# Copyright 2023 Ant Group
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+WHITE='\033[1;37m'
+NC='\033[0m'
+LIGHT_GRAY='\033[0;37m'
+
+function print_blue() {
+ printf "${BLUE}%s${NC}\n" "$1"
+}
+
+function print_red() {
+ printf "${RED}%s${NC}\n" "$1"
+}
+
+function print_green() {
+ printf "${GREEN}%s${NC}\n" "$1"
+}
+
+function print_hint() {
+ printf "${WHITE}\033[4m%s${NC}" "$1"
+}
+
+function log_info() {
+ NOW=$(date "+%Y-%m-%d %H:%M:%S.%s" | cut -b 1-23)
+ INFO_PREFIX=$(printf "${GREEN}\033[4m[ INFO ]${NC}")
+
+ INFO=$(printf "_${LIGHT_GRAY}[ %s ]${NC} : %s" "${NOW}" "$1")
+ echo "${INFO_PREFIX}${INFO}"
+}
+
+function log_warn() {
+ NOW=$(date "+%Y-%m-%d %H:%M:%S.%s" | cut -b 1-23)
+ WARN_PREFIX=$(printf "${YELLOW}\033[4m[ WARN ]${NC}")
+
+ INFO=$(printf "_${LIGHT_GRAY}[ %s ]${NC} : %s" "${NOW}" "$1")
+ echo "${WARN_PREFIX}${INFO}"
+}
+
+function log_error() {
+ NOW=$(date "+%Y-%m-%d %H:%M:%S.%s" | cut -b 1-23)
+ ERROR_PREFIX=$(printf "${RED}\033[4m[ ERROR ]${NC}")
+
+ INFO=$(printf "_${LIGHT_GRAY}[ %s ]${NC} : %s" "${NOW}" "$1")
+ echo "${ERROR_PREFIX}${INFO}"
+}
+
+function print_title() {
+ echo ' ___ __ ______ __ _ ____ _ __'
+ echo ' / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___'
+ echo ' / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \'
+ echo ' / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/'
+ echo '/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/'
+ echo ' /____/ '
+ echo
+}
diff --git a/acb-committeeptc/monitor-node/src/main/resources/scripts/start.sh b/acb-committeeptc/monitor-node/src/main/resources/scripts/start.sh
new file mode 100755
index 00000000..e46ee30e
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/scripts/start.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+
+#
+# Copyright 2023 Ant Group
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+Help=$(
+ cat <<-"HELP"
+
+ start.sh - Start the monitor Node
+
+ Usage:
+ start.sh
+
+ Examples:
+ 1. start in system service mode:
+ start.sh -s
+ 2. start in application mode:
+ start.sh
+ 3. start with configuration encrypted:
+ start.sh -P your_jasypt_password
+
+ Options:
+ -s run in system service mode.
+ -P your jasypt password.
+ -h print help information.
+
+HELP
+)
+
+CURR_DIR="$(
+ cd $(dirname $0)
+ pwd
+)"
+
+while getopts "hsP:" opt; do
+ case "$opt" in
+ "h")
+ echo "$Help"
+ exit 0
+ ;;
+ "s")
+ IF_SYS_MODE="on"
+ ;;
+ "P")
+ JASYPT_PASSWD=$OPTARG
+ ;;
+ "?")
+ echo "invalid arguments. "
+ exit 1
+ ;;
+ *)
+ echo "Unknown error while processing options"
+ exit 1
+ ;;
+ esac
+done
+
+source ${CURR_DIR}/print.sh
+
+print_title
+
+JAR_FILE=$(ls ${CURR_DIR}/../lib/ | grep '.jar')
+
+if [[ -n "${JASYPT_PASSWD}" ]]; then
+ JASYPT_FLAG="--jasypt.encryptor.password=${JASYPT_PASSWD}"
+fi
+
+if [ "$IF_SYS_MODE" == "on" ]; then
+ if [[ "$OSTYPE" == "darwin"* ]]; then
+ log_error "${OSTYPE} not support running in system service mode"
+ exit 1
+ fi
+
+ touch /usr/lib/systemd/system/test123 >/dev/null && rm -f /usr/lib/systemd/system/test123
+ if [ $? -ne 0 ]; then
+ log_error "Your account on this OS must have authority to access /usr/lib/systemd/system/"
+ exit 1
+ fi
+
+ log_info "running in system service mode"
+
+ JAVA_BIN=$(which java)
+ if [ -z "$JAVA_BIN" ]; then
+ log_error "install jdk before start"
+ exit 1
+ fi
+ START_CMD="${JAVA_BIN} -jar -Dlogging.file.path=${CURR_DIR}/../log ${CURR_DIR}/../lib/${JAR_FILE} --spring.config.location=file:${CURR_DIR}/../config/application.yml ${JASYPT_FLAG}"
+ WORK_DIR="$(
+ cd ${CURR_DIR}/..
+ pwd
+ )"
+
+ sed -i -e "s#@@START_CMD@@#${START_CMD}#g" ${CURR_DIR}/monitor-node.service
+ sed -i -e "s#@@WORKING_DIR@@#${WORK_DIR}#g" ${CURR_DIR}/monitor-node.service
+
+ cp -f ${CURR_DIR}/monitor-node.service /usr/lib/systemd/system/
+ if [ $? -ne 0 ]; then
+ log_error "failed to cp monitor-node.service to /usr/lib/systemd/system/"
+ exit 1
+ fi
+
+ systemctl daemon-reload && systemctl enable monitor-node.service
+ if [ $? -ne 0 ]; then
+ log_error "failed to enable monitor-node.service"
+ exit 1
+ fi
+
+ systemctl start monitor-node
+ if [ $? -ne 0 ]; then
+ log_error "failed to start monitor-node.service"
+ exit 1
+ fi
+
+else
+ log_info "running in app mode"
+ log_info "start monitor-node now..."
+
+ cd ${CURR_DIR}/..
+ java -jar -Dlogging.file.path=${CURR_DIR}/../log ${CURR_DIR}/../lib/${JAR_FILE} --spring.config.location=file:${CURR_DIR}/../config/application.yml ${JASYPT_FLAG} >/dev/null 2>&1 &
+ if [ $? -ne 0 ]; then
+ log_error "failed to start monitor-node"
+ exit 1
+ fi
+fi
+
+log_info "monitor-node started successfully"
diff --git a/acb-committeeptc/monitor-node/src/main/resources/scripts/stop.sh b/acb-committeeptc/monitor-node/src/main/resources/scripts/stop.sh
new file mode 100755
index 00000000..f50e775f
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/main/resources/scripts/stop.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+#
+# Copyright 2023 Ant Group
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+CURR_DIR="$(
+ cd $(dirname $0)
+ pwd
+)"
+source ${CURR_DIR}/print.sh
+
+print_title
+
+log_info "stop monitor-node now..."
+
+ps -ewf | grep -e "monitor-node-.*\.jar" | grep -v grep | awk '{print $2}' | xargs kill
+if [ $? -ne 0 ]; then
+ log_error "failed to stop monitor-node"
+ exit 1
+fi
+
+log_info "monitor-node stopped successfully"
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/TestBase.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/TestBase.java
new file mode 100755
index 00000000..f1861f1d
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/TestBase.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node;
+
+
+import java.io.ByteArrayInputStream;
+import java.security.PrivateKey;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.crypto.PemUtil;
+import com.alipay.antchain.bridge.bcdns.service.IBlockChainDomainNameService;
+import com.alipay.antchain.bridge.bcdns.types.base.DomainRouter;
+import com.alipay.antchain.bridge.bcdns.types.exception.AntChainBridgeBCDNSException;
+import com.alipay.antchain.bridge.bcdns.types.req.*;
+import com.alipay.antchain.bridge.bcdns.types.resp.*;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.ObjectIdentity;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchor;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IScheduledTaskService;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@ActiveProfiles("test")
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = NodeApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@Sql(scripts = {"classpath:data/ddl.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+@Sql(scripts = "classpath:data/drop_all.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
+public abstract class TestBase {
+
+ public static final String BCDNS_ROOT_CERT = """
+ -----BEGIN BCDNS TRUST ROOT CERTIFICATE-----
+ AADWAQAAAAABAAAAMQEABAAAAHRlc3QCAAEAAAAAAwBrAAAAAABlAAAAAAABAAAA
+ AAEAWAAAADBWMBAGByqGSM49AgEGBSuBBAAKA0IABPSyWJiXGQUhIzdqzRq7hdcy
+ CKuSS40qpcGUNsTXJtky9Ka1hXWqbdAVawAqWsNDIrSp2I5HL9eqpvl1GxSvxN8E
+ AAgAAADSJb9mAAAAAAUACAAAAFJZoGgAAAAABgCGAAAAAACAAAAAAAADAAAAYmlm
+ AQBrAAAAAABlAAAAAAABAAAAAAEAWAAAADBWMBAGByqGSM49AgEGBSuBBAAKA0IA
+ BPSyWJiXGQUhIzdqzRq7hdcyCKuSS40qpcGUNsTXJtky9Ka1hXWqbdAVawAqWsND
+ IrSp2I5HL9eqpvl1GxSvxN8CAAAAAAAHAJ8AAAAAAJkAAAAAAAoAAABLRUNDQUst
+ MjU2AQAgAAAAvSTYE3fohb8st2Hu6eGR0uR+HI+Fr+ig4A/wR/c7ahMCABYAAABL
+ ZWNjYWsyNTZXaXRoU2VjcDI1NmsxAwBBAAAAsGsuR7geJEPmaO9udja1wW+da1ex
+ KNVhpk7oi66g3UNNpYSoJK3wzibTKBj/cRfZCY/FkZdp95j6mMcK2oHsAAA=
+ -----END BCDNS TRUST ROOT CERTIFICATE-----
+ """;
+
+ public static final String DOT_COM_DOMAIN_SPACE_CERT = """
+ -----BEGIN DOMAIN NAME CERTIFICATE-----
+ AADtAQAAAAABAAAAMQEABAAAAC5jb20CAAEAAAABAwBrAAAAAABlAAAAAAABAAAA
+ AAEAWAAAADBWMBAGByqGSM49AgEGBSuBBAAKA0IABPSyWJiXGQUhIzdqzRq7hdcy
+ CKuSS40qpcGUNsTXJtky9Ka1hXWqbdAVawAqWsNDIrSp2I5HL9eqpvl1GxSvxN8E
+ AAgAAADSJb9mAAAAAAUACAAAAFJZoGgAAAAABgCdAAAAAACXAAAAAAADAAAAMS4w
+ AQABAAAAAQIAAAAAAAMABAAAAC5jb20EAGsAAAAAAGUAAAAAAAEAAAAAAQBYAAAA
+ MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE9LJYmJcZBSEjN2rNGruF1zIIq5JLjSql
+ wZQ2xNcm2TL0prWFdapt0BVrACpaw0MitKnYjkcv16qm+XUbFK/E3wUAAAAAAAcA
+ nwAAAAAAmQAAAAAACgAAAEtFQ0NBSy0yNTYBACAAAADTK+miwHYLK8NTN2okHMfo
+ mEShYXWhzkrjivLNXDGt/wIAFgAAAEtlY2NhazI1NldpdGhTZWNwMjU2azEDAEEA
+ AAAWu0d+MaWZfLOUVBnDT2/uC+IxKUyZqxdjsNXy2x7n7zJYSgof+ujJWE7r8qWT
+ 1tBHkbDC/YHXA8QLgVPC2NfMAQ==
+ -----END DOMAIN NAME CERTIFICATE-----
+ """;
+
+ public static final String ANTCHAIN_DOT_COM_CERT = """
+ -----BEGIN DOMAIN NAME CERTIFICATE-----
+ AAD/AQAAAAABAAAAMQEACgAAAHRlc3Rkb21haW4CAAEAAAABAwBrAAAAAABlAAAA
+ AAABAAAAAAEAWAAAADBWMBAGByqGSM49AgEGBSuBBAAKA0IABPSyWJiXGQUhIzdq
+ zRq7hdcyCKuSS40qpcGUNsTXJtky9Ka1hXWqbdAVawAqWsNDIrSp2I5HL9eqpvl1
+ GxSvxN8EAAgAAADSJb9mAAAAAAUACAAAAFJZoGgAAAAABgCpAAAAAACjAAAAAAAD
+ AAAAMS4wAQABAAAAAAIABAAAAC5jb20DAAwAAABhbnRjaGFpbi5jb20EAGsAAAAA
+ AGUAAAAAAAEAAAAAAQBYAAAAMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE9LJYmJcZ
+ BSEjN2rNGruF1zIIq5JLjSqlwZQ2xNcm2TL0prWFdapt0BVrACpaw0MitKnYjkcv
+ 16qm+XUbFK/E3wUAAAAAAAcAnwAAAAAAmQAAAAAACgAAAEtFQ0NBSy0yNTYBACAA
+ AAD2j0+ge6shN1piGmDyb+YY7E3Fs4E7SMeQGxvOiJC5sgIAFgAAAEtlY2NhazI1
+ NldpdGhTZWNwMjU2azEDAEEAAAB8QQjC0e6/Xl4PaTcx+BtX/fg3BQN9b0YdXEXR
+ oYklgXW6KHQ7YLt7farETz2inRjlT0eJka4LvJUSinX53WXVAA==
+ -----END DOMAIN NAME CERTIFICATE-----
+ """;
+
+ public static final AbstractCrossChainCertificate NODE_PTC_CERT = CrossChainCertificateUtil.readCrossChainCertificateFromPem(
+ FileUtil.readBytes("ptc.crt")
+ );
+
+ public static final PrivateKey NODE_PTC_PRIVATE_KEY = SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().readPemPrivateKey(
+ FileUtil.readBytes("private_key.pem")
+ );
+
+ public static final byte[] RAW_NODE_PTC_PUBLIC_KEY = PemUtil.readPem(new ByteArrayInputStream(FileUtil.readBytes("public_key.pem")));
+
+ @MockBean
+ private IScheduledTaskService scheduledTaskService;
+
+ public static class DummyBcdnsServiceImpl implements IBlockChainDomainNameService {
+ @Override
+ public QueryBCDNSTrustRootCertificateResponse queryBCDNSTrustRootCertificate() {
+ return null;
+ }
+
+ @Override
+ public ApplyRelayerCertificateResponse applyRelayerCertificate(AbstractCrossChainCertificate abstractCrossChainCertificate) {
+ return null;
+ }
+
+ @Override
+ public ApplicationResult queryRelayerCertificateApplicationResult(String s) {
+ return null;
+ }
+
+ @Override
+ public ApplyPTCCertificateResponse applyPTCCertificate(AbstractCrossChainCertificate abstractCrossChainCertificate) {
+ return null;
+ }
+
+ @Override
+ public ApplicationResult queryPTCCertificateApplicationResult(String s) {
+ return null;
+ }
+
+ @Override
+ public ApplyDomainNameCertificateResponse applyDomainNameCertificate(AbstractCrossChainCertificate abstractCrossChainCertificate) {
+ return null;
+ }
+
+ @Override
+ public ApplicationResult queryDomainNameCertificateApplicationResult(String s) {
+ return null;
+ }
+
+ @Override
+ public QueryRelayerCertificateResponse queryRelayerCertificate(QueryRelayerCertificateRequest queryRelayerCertificateRequest) {
+ return null;
+ }
+
+ @Override
+ public QueryPTCCertificateResponse queryPTCCertificate(QueryPTCCertificateRequest queryPTCCertificateRequest) {
+ return null;
+ }
+
+ @Override
+ public QueryDomainNameCertificateResponse queryDomainNameCertificate(QueryDomainNameCertificateRequest queryDomainNameCertificateRequest) {
+ return null;
+ }
+
+ @Override
+ public void registerDomainRouter(RegisterDomainRouterRequest registerDomainRouterRequest) throws AntChainBridgeBCDNSException {
+
+ }
+
+ @Override
+ public void registerThirdPartyBlockchainTrustAnchor(RegisterThirdPartyBlockchainTrustAnchorRequest registerThirdPartyBlockchainTrustAnchorRequest) throws AntChainBridgeBCDNSException {
+
+ }
+
+ @Override
+ public DomainRouter queryDomainRouter(QueryDomainRouterRequest queryDomainRouterRequest) {
+ return null;
+ }
+
+ @Override
+ public ThirdPartyBlockchainTrustAnchor queryThirdPartyBlockchainTrustAnchor(QueryThirdPartyBlockchainTrustAnchorRequest queryThirdPartyBlockchainTrustAnchorRequest) {
+ return null;
+ }
+
+ @Override
+ public PTCTrustRoot queryPTCTrustRoot(ObjectIdentity objectIdentity) {
+ return null;
+ }
+
+ @Override
+ public void addPTCTrustRoot(PTCTrustRoot ptcTrustRoot) {
+
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/BCDNSRepositoryTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/BCDNSRepositoryTest.java
new file mode 100755
index 00000000..d35d3e90
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/BCDNSRepositoryTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import jakarta.annotation.Resource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BCDNSRepositoryTest extends TestBase {
+
+ @Resource
+ private IBCDNSRepository bcdnsRepository;
+
+ private final AbstractCrossChainCertificate bcdnsRootCertObj = CrossChainCertificateUtil.readCrossChainCertificateFromPem(BCDNS_ROOT_CERT.getBytes());
+
+ @Test
+ public void testDomainSpaceCert() {
+ var dscObj = CrossChainCertificateUtil.readCrossChainCertificateFromPem(DOT_COM_DOMAIN_SPACE_CERT.getBytes());
+
+ bcdnsRepository.saveDomainSpaceCert(new DomainSpaceCertWrapper(bcdnsRootCertObj));
+ bcdnsRepository.saveDomainSpaceCert(new DomainSpaceCertWrapper(dscObj));
+
+ Assert.assertEquals(
+ bcdnsRootCertObj.getCredentialSubjectInstance().getApplicant(),
+ bcdnsRepository.getDomainSpaceCert(CrossChainCertificateUtil.getCrossChainDomainSpace(bcdnsRootCertObj).getDomain()).getOwnerOid()
+ );
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ bcdnsRootCertObj.encode(),
+ bcdnsRepository.getDomainSpaceCert(bcdnsRootCertObj.getCredentialSubjectInstance().getApplicant()).getDomainSpaceCert().encode()
+ )
+ );
+ Assert.assertTrue(
+ bcdnsRepository.hasDomainSpaceCert(CrossChainCertificateUtil.getCrossChainDomainSpace(bcdnsRootCertObj).getDomain())
+ );
+
+ Assert.assertEquals(
+ 2,
+ bcdnsRepository.getDomainSpaceCertChain(CrossChainCertificateUtil.getCrossChainDomainSpace(dscObj).getDomain()).size()
+ );
+ Assert.assertEquals(
+ CrossChainCertificateUtil.getCrossChainDomainSpace(bcdnsRootCertObj).getDomain(),
+ bcdnsRepository.getDomainSpaceCertChain(CrossChainCertificateUtil.getCrossChainDomainSpace(dscObj).getDomain())
+ .get(CrossChainDomain.ROOT_DOMAIN_SPACE).getDomainSpace()
+ );
+ }
+
+ @Test
+ public void testBcdns() {
+
+ var bcdnsService = new BCDNSServiceDO();
+ bcdnsService.setDomainSpaceCertWrapper(new DomainSpaceCertWrapper(bcdnsRootCertObj));
+ bcdnsService.setDomainSpace(CrossChainCertificateUtil.getCrossChainDomainSpace(bcdnsRootCertObj).getDomain());
+ bcdnsService.setType(BCDNSTypeEnum.EMBEDDED);
+ bcdnsService.setState(BCDNSStateEnum.WORKING);
+ bcdnsService.setOwnerOid(bcdnsRootCertObj.getCredentialSubjectInstance().getApplicant());
+ bcdnsService.setProperties("{}".getBytes());
+
+ bcdnsRepository.saveBCDNSServiceDO(bcdnsService);
+
+ Assert.assertEquals(
+ 1,
+ bcdnsRepository.getAllBCDNSDomainSpace().size()
+ );
+ Assert.assertNotNull(
+ bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace())
+ );
+ Assert.assertEquals(
+ bcdnsService.getOwnerOid(),
+ bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace()).getOwnerOid()
+ );
+ Assert.assertEquals(
+ bcdnsService.getState(),
+ bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace()).getState()
+ );
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ bcdnsService.getProperties(),
+ bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace()).getProperties()
+ )
+ );
+ Assert.assertTrue(
+ bcdnsRepository.hasBCDNSService(bcdnsService.getDomainSpace())
+ );
+ Assert.assertEquals(1, bcdnsRepository.countBCDNSService());
+
+ bcdnsRepository.updateBCDNSServiceState(bcdnsService.getDomainSpace(), BCDNSStateEnum.FROZEN);
+ Assert.assertEquals(
+ BCDNSStateEnum.FROZEN,
+ bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace()).getState()
+ );
+ var newProp = """
+ {"test": "test"}
+ """;
+ bcdnsRepository.updateBCDNSServiceProperties(bcdnsService.getDomainSpace(), newProp.getBytes());
+ Assert.assertEquals(
+ newProp,
+ new String(bcdnsRepository.getBCDNSServiceDO(bcdnsService.getDomainSpace()).getProperties())
+ );
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/EndorseServiceRepositoryTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/EndorseServiceRepositoryTest.java
new file mode 100755
index 00000000..219bcb3a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/EndorseServiceRepositoryTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal;
+
+import java.math.BigInteger;
+import java.security.KeyPairGenerator;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.alipay.antchain.bridge.commons.bcdns.PTCCredentialSubject;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTypeEnum;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.ptc.ValidatedConsensusStateV1;
+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.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeEndorseProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.NodePublicKeyEntry;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.CommitteeEndorseRoot;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.NodeEndorseInfo;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.OptionalEndorsePolicy;
+import jakarta.annotation.Resource;
+import lombok.SneakyThrows;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EndorseServiceRepositoryTest extends TestBase {
+
+ @Resource
+ private IEndorseServiceRepository endorseServiceRepository;
+
+ @SneakyThrows
+ @Test
+ public void testBta() {
+ var oid = new ObjectIdentity(
+ ObjectIdentityType.X509_PUBLIC_KEY_INFO,
+ KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic().getEncoded()
+ );
+ var bta = new BlockchainTrustAnchorV1();
+ bta.setBcOwnerPublicKey("test".getBytes());
+ bta.setBcOwnerSig("test".getBytes());
+ bta.setDomain(new CrossChainDomain("test"));
+ bta.setSubjectIdentity("test".getBytes());
+ bta.setBcOwnerSigAlgo(SignAlgoEnum.SHA256_WITH_ECDSA);
+ bta.setPtcOid(oid);
+ bta.setSubjectProduct("test");
+ bta.setExtension("test".getBytes());
+ bta.setSubjectVersion(0);
+
+ var btaWrapper = new BtaWrapper();
+ btaWrapper.setBta(bta);
+ endorseServiceRepository.setBta(btaWrapper);
+
+ var btaWrapperFromDB = endorseServiceRepository.getBta(btaWrapper.getDomain());
+ Assert.assertEquals(
+ HexUtil.encodeHexStr(btaWrapper.getBta().getSubjectIdentity()),
+ HexUtil.encodeHexStr(btaWrapperFromDB.getBta().getSubjectIdentity())
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ public void testTpBta() {
+ var policy = new OptionalEndorsePolicy();
+ policy.setThreshold(new OptionalEndorsePolicy.Threshold(OptionalEndorsePolicy.OperatorEnum.GREATER_THAN, 1));
+ var nodeEndorseInfo = new NodeEndorseInfo();
+ nodeEndorseInfo.setNodeId("test");
+ nodeEndorseInfo.setRequired(true);
+ var nodePubkeyEntry = new NodePublicKeyEntry("default", SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().generateKeyPair().getPublic());
+ nodeEndorseInfo.setPublicKey(nodePubkeyEntry);
+ var crossChainLane = new CrossChainLane(new CrossChainDomain("test"), new CrossChainDomain("test"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"));
+ var tpbta = new ThirdPartyBlockchainTrustAnchorV1(
+ 1,
+ BigInteger.ONE,
+ new PTCCredentialSubject(
+ "1",
+ "ptc",
+ PTCTypeEnum.COMMITTEE,
+ new X509PubkeyInfoObjectIdentity(
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().generateKeyPair().getPublic().getEncoded()
+ ),
+ new byte[]{}
+ ),
+ crossChainLane,
+ 1,
+ HashAlgoEnum.KECCAK_256,
+ new CommitteeEndorseRoot(
+ "committee",
+ policy,
+ ListUtil.toList(nodeEndorseInfo)
+ ).encode(),
+ CommitteeEndorseProof.builder()
+ .committeeId("committee")
+ .sigs(ListUtil.toList(new CommitteeNodeProof(
+ "test",
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1,
+ "".getBytes()
+ ))).build().encode()
+ );
+ var tpBtaWrapper = new TpBtaWrapper(tpbta);
+
+ endorseServiceRepository.setTpBta(tpBtaWrapper);
+
+ Assert.assertNotNull(endorseServiceRepository.getMatchedTpBta(crossChainLane));
+ Assert.assertTrue(endorseServiceRepository.hasTpBta(crossChainLane, 1));
+ Assert.assertNotNull(endorseServiceRepository.getMatchedTpBta(crossChainLane));
+ }
+
+ @Test
+ public void testValidatedConsensusState() {
+ var cs = new ConsensusState(
+ new CrossChainDomain("test"),
+ BigInteger.valueOf(100L),
+ RandomUtil.randomBytes(32),
+ RandomUtil.randomBytes(32),
+ System.currentTimeMillis(),
+ new byte[]{},
+ new byte[]{},
+ new byte[]{}
+ );
+ var vcs = BeanUtil.copyProperties(cs, ValidatedConsensusStateV1.class);
+
+ endorseServiceRepository.setValidatedConsensusState(new ValidatedConsensusStateWrapper(vcs));
+
+ Assert.assertTrue(endorseServiceRepository.hasValidatedConsensusState("test", BigInteger.valueOf(100L)));
+ Assert.assertEquals(
+ cs.getHashHex(),
+ endorseServiceRepository.getValidatedConsensusState("test", BigInteger.valueOf(100L)).getValidatedConsensusState().getHashHex()
+ );
+ Assert.assertEquals(
+ cs.getHeight(),
+ endorseServiceRepository.getValidatedConsensusState("test", BigInteger.valueOf(100L)).getHeight()
+ );
+ Assert.assertEquals(
+ cs.getParentHashHex(),
+ endorseServiceRepository.getValidatedConsensusState("test", cs.getHashHex()).getParentHash()
+ );
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/SystemConfigRepositoryTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/SystemConfigRepositoryTest.java
new file mode 100755
index 00000000..52ca609f
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/dal/SystemConfigRepositoryTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.dal;
+
+import java.math.BigInteger;
+
+import cn.hutool.core.map.MapUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCVerifyAnchor;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import jakarta.annotation.Resource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SystemConfigRepositoryTest extends TestBase {
+
+ @Resource
+ private ISystemConfigRepository systemConfigRepository;
+
+ @Test
+ public void testPtcTrustRoot() {
+ var ptcCertObj = NODE_PTC_CERT;
+
+ var va = new PTCVerifyAnchor();
+ va.setAnchor(new byte[]{});
+ va.setVersion(BigInteger.ONE);
+
+ var trustRoot = new PTCTrustRoot();
+ trustRoot.setSig(new byte[]{});
+ trustRoot.setNetworkInfo(new byte[]{});
+ trustRoot.setPtcCrossChainCert(ptcCertObj);
+ trustRoot.setIssuerBcdnsDomainSpace(new CrossChainDomain(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ trustRoot.setSigAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1);
+ trustRoot.setVerifyAnchorMap(MapUtil.builder(BigInteger.ONE, va).build());
+
+ systemConfigRepository.setPtcTrustRoot(trustRoot);
+
+ Assert.assertEquals(
+ ptcCertObj.getCredentialSubjectInstance().getApplicant(),
+ systemConfigRepository.getPtcTrustRoot().getPtcCredentialSubject().getApplicant()
+ );
+ Assert.assertEquals(
+ BigInteger.ONE,
+ systemConfigRepository.queryCurrentPtcAnchorVersion()
+ );
+ }
+
+ @Test
+ public void testSystemConfig() {
+ systemConfigRepository.setSystemConfig("test", "test");
+ Assert.assertEquals(
+ "test",
+ systemConfigRepository.getSystemConfig("test")
+ );
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImplTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImplTest.java
new file mode 100755
index 00000000..da250103
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/AdminServiceImplTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.alipay.antchain.bridge.bcdns.factory.BlockChainDomainNameServiceFactory;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTrustRoot;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCVerifyAnchor;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.server.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IBCDNSManageService;
+import com.google.protobuf.ByteString;
+import io.grpc.internal.testing.StreamRecorder;
+import jakarta.annotation.Resource;
+import lombok.SneakyThrows;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+public class AdminServiceImplTest extends TestBase {
+
+ private static final AbstractCrossChainCertificate BCDNS_ROOT_CERT_OBJ = CrossChainCertificateUtil.readCrossChainCertificateFromPem(BCDNS_ROOT_CERT.getBytes());
+
+ @Resource
+ private AdminServiceImpl adminService;
+
+ @Resource
+ private IBCDNSManageService bcdnsManageService;
+
+ @Resource
+ private ISystemConfigRepository systemConfigRepository;
+
+ @Test
+ @SneakyThrows
+ public void testRegisterBcdnsService() {
+
+ MockedStatic mockStaticObj = null;
+ try {
+ mockStaticObj = mockStatic(BlockChainDomainNameServiceFactory.class);
+ when(BlockChainDomainNameServiceFactory.create(notNull(), any())).thenReturn(new DummyBcdnsServiceImpl());
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ adminService.registerBcdnsService(
+ RegisterBcdnsServiceRequest.newBuilder()
+ .setBcdnsType(BCDNSTypeEnum.EMBEDDED.getCode())
+ .setBcdnsRootCert(BCDNS_ROOT_CERT)
+ .setDomainSpace(CrossChainDomain.ROOT_DOMAIN_SPACE)
+ .setConfig(ByteString.empty())
+ .build(),
+ responseObserver
+ );
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ Assert.assertTrue(bcdnsManageService.hasBCDNSServiceData(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ Assert.assertEquals(1, bcdnsManageService.countBCDNSService());
+ Assert.assertNotNull(bcdnsManageService.getBCDNSClient(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ } finally {
+ if (ObjectUtil.isNotNull(mockStaticObj)) {
+ mockStaticObj.close();
+ }
+ }
+ }
+
+ @Test
+ @SneakyThrows
+ public void testGetBcdnsServiceInfo() {
+ MockedStatic mockStaticObj = null;
+ try {
+ mockStaticObj = mockStatic(BlockChainDomainNameServiceFactory.class);
+ when(BlockChainDomainNameServiceFactory.create(notNull(), any())).thenReturn(new DummyBcdnsServiceImpl());
+ bcdnsManageService.registerBCDNSService(
+ CrossChainDomain.ROOT_DOMAIN_SPACE,
+ BCDNSTypeEnum.EMBEDDED,
+ "{}".getBytes(),
+ BCDNS_ROOT_CERT_OBJ
+ );
+ StreamRecorder responseObserver = StreamRecorder.create();
+ adminService.getBcdnsServiceInfo(
+ GetBcdnsServiceInfoRequest.newBuilder()
+ .setDomainSpace(CrossChainDomain.ROOT_DOMAIN_SPACE)
+ .build(),
+ responseObserver
+ );
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ Assert.assertTrue(JSONUtil.isTypeJSON(results.getFirst().getGetBcdnsServiceInfoResp().getInfoJson()));
+ } finally {
+ if (ObjectUtil.isNotNull(mockStaticObj)) {
+ mockStaticObj.close();
+ }
+ }
+ }
+
+ @Test
+ @SneakyThrows
+ public void testStopAndRestart() {
+ MockedStatic mockStaticObj = null;
+ try {
+ mockStaticObj = mockStatic(BlockChainDomainNameServiceFactory.class);
+ when(BlockChainDomainNameServiceFactory.create(notNull(), any())).thenReturn(new DummyBcdnsServiceImpl());
+ bcdnsManageService.registerBCDNSService(
+ CrossChainDomain.ROOT_DOMAIN_SPACE,
+ BCDNSTypeEnum.EMBEDDED,
+ "{}".getBytes(),
+ BCDNS_ROOT_CERT_OBJ
+ );
+ StreamRecorder responseObserver = StreamRecorder.create();
+ adminService.stopBcdnsService(
+ StopBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(CrossChainDomain.ROOT_DOMAIN_SPACE)
+ .build(),
+ responseObserver
+ );
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ Assert.assertEquals(
+ BCDNSStateEnum.FROZEN,
+ bcdnsManageService.getBCDNSServiceData(CrossChainDomain.ROOT_DOMAIN_SPACE).getState()
+ );
+
+ adminService.restartBcdnsService(
+ RestartBcdnsServiceRequest.newBuilder()
+ .setDomainSpace(CrossChainDomain.ROOT_DOMAIN_SPACE)
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertEquals(
+ BCDNSStateEnum.WORKING,
+ bcdnsManageService.getBCDNSServiceData(CrossChainDomain.ROOT_DOMAIN_SPACE).getState()
+ );
+
+ adminService.deleteBcdnsService(
+ DeleteBcdnsServiceRequest.newBuilder().setDomainSpace(CrossChainDomain.ROOT_DOMAIN_SPACE).build(),
+ responseObserver
+ );
+ Assert.assertFalse(
+ bcdnsManageService.hasBCDNSServiceData(CrossChainDomain.ROOT_DOMAIN_SPACE)
+ );
+
+ var ptcCertObj = NODE_PTC_CERT;
+
+ var va = new PTCVerifyAnchor();
+ va.setAnchor(new byte[]{});
+ va.setVersion(BigInteger.ONE);
+
+ var trustRoot = new PTCTrustRoot();
+ trustRoot.setSig(new byte[]{});
+ trustRoot.setNetworkInfo(new byte[]{});
+ trustRoot.setPtcCrossChainCert(ptcCertObj);
+ trustRoot.setIssuerBcdnsDomainSpace(new CrossChainDomain(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ trustRoot.setSigAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1);
+ trustRoot.setVerifyAnchorMap(MapUtil.builder(BigInteger.ONE, va).build());
+
+ adminService.addPtcTrustRoot(
+ AddPtcTrustRootRequest.newBuilder().setRawTrustRoot(ByteString.copyFrom(trustRoot.encode())).build(),
+ responseObserver
+ );
+
+ Assert.assertNotNull(
+ systemConfigRepository.getPtcTrustRoot()
+ );
+
+ } finally {
+ if (ObjectUtil.isNotNull(mockStaticObj)) {
+ mockStaticObj.close();
+ }
+ }
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceTest.java
new file mode 100755
index 00000000..e48e5028
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorNodeServiceTest.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.PTCCredentialSubject;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.ptc.*;
+import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
+import com.alipay.antchain.bridge.commons.core.monitor.MonitorMessageFactory;
+import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+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.grpc.*;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IEndorserService;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.service.IHcdvsPluginService;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeEndorseProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.EndorseBlockStateResp;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.NodePublicKeyEntry;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.CommitteeEndorseRoot;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.NodeEndorseInfo;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.OptionalEndorsePolicy;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.VerifyBtaExtension;
+import com.google.protobuf.ByteString;
+import io.grpc.internal.testing.StreamRecorder;
+import jakarta.annotation.Resource;
+import lombok.SneakyThrows;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MonitorNodeServiceTest extends TestBase {
+
+ private static final ThirdPartyBlockchainTrustAnchorV1 tpbta;
+
+ private static final CrossChainLane crossChainLane;
+
+ private static final ObjectIdentity oid;
+
+ private static final BlockchainTrustAnchorV1 bta;
+
+ private static final AbstractCrossChainCertificate domainCert;
+
+ private static final ConsensusState anchorState;
+
+ private static final ConsensusState currState;
+
+ private static final UniformCrosschainPacket ucp;
+
+ static {
+ oid = new X509PubkeyInfoObjectIdentity(
+ RAW_NODE_PTC_PUBLIC_KEY
+ );
+
+ var policy = new OptionalEndorsePolicy();
+ policy.setThreshold(new OptionalEndorsePolicy.Threshold(OptionalEndorsePolicy.OperatorEnum.GREATER_THAN, -1));
+ var nodeEndorseInfo = new NodeEndorseInfo();
+ nodeEndorseInfo.setNodeId("monitor-node");
+ nodeEndorseInfo.setRequired(true);
+ var nodePubkeyEntry = new NodePublicKeyEntry("default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
+ nodeEndorseInfo.setPublicKey(nodePubkeyEntry);
+ crossChainLane = new CrossChainLane(new CrossChainDomain("test"), new CrossChainDomain("test"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"));
+ tpbta = new ThirdPartyBlockchainTrustAnchorV1(
+ 1,
+ BigInteger.ONE,
+ (PTCCredentialSubject) NODE_PTC_CERT.getCredentialSubjectInstance(),
+ crossChainLane,
+ 1,
+ HashAlgoEnum.KECCAK_256,
+ new CommitteeEndorseRoot(
+ "1",
+ policy,
+ ListUtil.toList(nodeEndorseInfo)
+ ).encode(),
+ CommitteeEndorseProof.builder()
+ .committeeId("1")
+ .sigs(ListUtil.toList(new CommitteeNodeProof(
+ "test",
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1,
+ "".getBytes()
+ ))).build().encode()
+ );
+
+ bta = new BlockchainTrustAnchorV1();
+ bta.setBcOwnerPublicKey(RAW_NODE_PTC_PUBLIC_KEY);
+ bta.setDomain(new CrossChainDomain("ether.com"));
+ bta.setSubjectIdentity("test".getBytes());
+ bta.setBcOwnerSigAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1);
+ bta.setPtcOid(NODE_PTC_CERT.getCredentialSubjectInstance().getApplicant());
+ bta.setSubjectProduct("ethereum2");
+ bta.setInitHeight(BigInteger.valueOf(100L));
+ bta.setInitBlockHash(RandomUtil.randomBytes(32));
+ bta.setExtension(
+ new VerifyBtaExtension(
+ CommitteeEndorseRoot.decode(tpbta.getEndorseRoot()),
+ crossChainLane
+ ).encode()
+ );
+ bta.setSubjectVersion(0);
+ bta.setAmId(RandomUtil.randomBytes(32));
+
+ bta.sign(NODE_PTC_PRIVATE_KEY);
+
+ domainCert = CrossChainCertificateUtil.readCrossChainCertificateFromPem(ANTCHAIN_DOT_COM_CERT.getBytes());
+
+ anchorState = new ConsensusState(
+ crossChainLane.getSenderDomain(),
+ BigInteger.valueOf(100L),
+ bta.getInitBlockHash(),
+ RandomUtil.randomBytes(32),
+ System.currentTimeMillis(),
+ "{}".getBytes(),
+ "{}".getBytes(),
+ "{}".getBytes()
+ );
+
+ currState = new ConsensusState(
+ crossChainLane.getSenderDomain(),
+ BigInteger.valueOf(101L),
+ RandomUtil.randomBytes(32),
+ anchorState.getParentHash(),
+ System.currentTimeMillis(),
+ "{}".getBytes(),
+ "{}".getBytes(),
+ "{}".getBytes()
+ );
+
+ // need to replace "awesome antchain-bridge" with monitor message
+ var monitorMessage = MonitorMessageFactory.createMonitorMessage(
+ 1,
+ 2,
+ "this is a monitorMsg",
+ "awesome antchain-bridge".getBytes()
+ );
+
+ var sdpMessage = SDPMessageFactory.createSDPMessage(
+ 1,
+ new byte[32],
+ crossChainLane.getReceiverDomain().getDomain(),
+ crossChainLane.getReceiverId().getRawID(),
+ -1,
+ monitorMessage.encode()
+ );
+
+ var am = AuthMessageFactory.createAuthMessage(
+ 1,
+ crossChainLane.getSenderId().getRawID(),
+ 0,
+ sdpMessage.encode()
+ );
+
+ ucp = new UniformCrosschainPacket(
+ crossChainLane.getSenderDomain(),
+ CrossChainMessage.createCrossChainMessage(
+ CrossChainMessage.CrossChainMessageType.AUTH_MSG,
+ BigInteger.valueOf(101L),
+ DateUtil.current(),
+ currState.getHash(),
+ am.encode(),
+ "event".getBytes(),
+ "merkle proof".getBytes(),
+ RandomUtil.randomBytes(32)
+ ),
+ NODE_PTC_CERT.getCredentialSubjectInstance().getApplicant()
+ );
+ }
+
+ @Value("${committee.id}")
+ private String committeeId;
+
+ @Value("${committee.node.id}")
+ private String nodeId;
+
+ @Resource
+ private IEndorserService endorserService;
+
+ @MockBean
+ private IEndorseServiceRepository endorseServiceRepository;
+
+ @MockBean
+ private IBCDNSRepository bcdnsRepository;
+
+ @MockBean
+ private ISystemConfigRepository systemConfigRepository;
+
+ @MockBean
+ private IHcdvsPluginService hcdvsPluginService;
+
+ @Resource
+ private AbstractCrossChainCertificate ptcCrossChainCert;
+
+ @Resource
+ private MonitorNodeServiceImpl committeeNodeService;
+
+ @Test
+ @SneakyThrows
+ public void testQueryTpBta() {
+ when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta));
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.queryTpBta(
+ QueryTpBtaRequest.newBuilder()
+ .setSenderDomain(crossChainLane.getSenderDomain().getDomain())
+ .setSenderId(crossChainLane.getSenderIdHex())
+ .setReceiverDomain(crossChainLane.getReceiverDomain().getDomain())
+ .setReceiverId(crossChainLane.getReceiverIdHex())
+ .build(),
+ responseObserver
+ );
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertArrayEquals(
+ tpbta.encode(),
+ results.getFirst().getQueryTpBtaResp().getRawTpBta().toByteArray()
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ public void testHeartbeat() {
+ when(hcdvsPluginService.getAvailableProducts()).thenReturn(ListUtil.toList("mychain"));
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.heartbeat(
+ HeartbeatRequest.newBuilder().build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+
+ Assert.assertEquals(committeeId, results.getFirst().getHeartbeatResp().getCommitteeId());
+ Assert.assertEquals("monitor-node", results.getFirst().getHeartbeatResp().getNodeId());
+ Assert.assertEquals(1, results.getFirst().getHeartbeatResp().getProductsCount());
+ Assert.assertEquals("mychain", results.getFirst().getHeartbeatResp().getProductsList().getFirst());
+ }
+
+ @Test
+ @SneakyThrows
+ public void testVerifyBta() {
+
+ when(bcdnsRepository.getDomainSpaceCert(anyString())).thenReturn(
+ new DomainSpaceCertWrapper(
+ CrossChainCertificateUtil.readCrossChainCertificateFromPem(DOT_COM_DOMAIN_SPACE_CERT.getBytes())
+ )
+ );
+
+ when(systemConfigRepository.queryCurrentPtcAnchorVersion()).thenReturn(BigInteger.ONE);
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.verifyBta(
+ VerifyBtaRequest.newBuilder()
+ .setRawBta(ByteString.copyFrom(bta.encode()))
+ .setRawDomainCert(ByteString.copyFrom(domainCert.encode()))
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var tpbtaInResp = ThirdPartyBlockchainTrustAnchor.decode(results.getFirst().getVerifyBtaResp().getRawTpBta().toByteArray());
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ tpbta.getEndorseRoot(),
+ tpbtaInResp.getEndorseRoot()
+ )
+ );
+ Assert.assertEquals(
+ tpbta.getCrossChainLane().getLaneKey(),
+ tpbtaInResp.getCrossChainLane().getLaneKey()
+ );
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ ptcCrossChainCert.getCredentialSubject(),
+ tpbtaInResp.getSignerPtcCredentialSubject().encode()
+ )
+ );
+
+ var endorseProof = CommitteeEndorseProof.decode(tpbtaInResp.getEndorseProof());
+ Assert.assertEquals(
+ committeeId,
+ endorseProof.getCommitteeId()
+ );
+
+ var endorseRoot = CommitteeEndorseRoot.decode(tpbtaInResp.getEndorseRoot());
+ Assert.assertEquals(
+ 1,
+ endorseRoot.getEndorsers().size()
+ );
+ Assert.assertEquals(
+ nodeId,
+ endorseRoot.getEndorsers().getFirst().getNodeId()
+ );
+ Assert.assertEquals(
+ "default",
+ endorseRoot.getEndorsers().getFirst().getPublicKey().getKeyId()
+ );
+ Assert.assertTrue(
+ endorseRoot.check(endorseProof, tpbtaInResp.getEncodedToSign())
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ 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.getBta(anyString())).thenReturn(new BtaWrapper(bta));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class);
+ vcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ vcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ vcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.commitAnchorState(
+ CommitAnchorStateRequest.newBuilder()
+ .setCrossChainLane(ByteString.copyFrom(crossChainLane.encode()))
+ .setRawAnchorState(ByteString.copyFrom(anchorState.encode()))
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var vcsSigned = ValidatedConsensusState.decode(results.getFirst().getCommitAnchorStateResp().getRawValidatedConsensusState().toByteArray());
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.decode(vcsSigned.getPtcProof()),
+ vcs.getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ public void testCommitConsensusState() {
+ var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class);
+ vcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ vcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ vcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var hcdvs = mock(IHeteroChainDataVerifierService.class);
+ 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.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(vcs));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.commitConsensusState(
+ CommitConsensusStateRequest.newBuilder()
+ .setCrossChainLane(ByteString.copyFrom(crossChainLane.encode()))
+ .setRawConsensusState(ByteString.copyFrom(currState.encode()))
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var vcsSigned = ValidatedConsensusState.decode(results.getFirst().getCommitConsensusStateResp().getRawValidatedConsensusState().toByteArray());
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.decode(vcsSigned.getPtcProof()),
+ currVcs.getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ public void testVerifyCrossChainMessage() {
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var hcdvs = mock(IHeteroChainDataVerifierService.class);
+ when(hcdvs.verifyCrossChainMessage(any(), any())).thenReturn(VerifyResult.builder().success(true).build());
+ 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.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(currVcs));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.verifyCrossChainMessage(
+ VerifyCrossChainMessageRequest.newBuilder()
+ .setCrossChainLane(ByteString.copyFrom(crossChainLane.encode()))
+ .setRawUcp(ByteString.copyFrom(ucp.encode()))
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var nodeProof = CommitteeNodeProof.decode(results.getFirst().getVerifyCrossChainMessageResp().getRawNodeProof().toByteArray());
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.builder()
+ .committeeId(committeeId)
+ .sigs(ListUtil.toList(nodeProof))
+ .build(),
+ ThirdPartyProof.create(
+ tpbta.getTpbtaVersion(),
+ ucp.getSrcMessage().getMessage(),
+ crossChainLane
+ ).getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ @SneakyThrows
+ public void testQueryBlockState() {
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ when(endorseServiceRepository.getLatestValidatedConsensusState(anyString())).thenReturn(new ValidatedConsensusStateWrapper(currVcs));
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.queryBlockState(
+ QueryBlockStateRequest.newBuilder()
+ .setDomain(currVcs.getDomain().toString())
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var blockState = BlockState.decode(results.getFirst().getQueryBlockStateResp().getRawValidatedBlockState().toByteArray());
+ Assert.assertEquals(currVcs.getHeight(), blockState.getHeight());
+ Assert.assertEquals(currVcs.getDomain(), blockState.getDomain());
+ Assert.assertEquals(currVcs.getStateTimestamp(), blockState.getTimestamp());
+ Assert.assertArrayEquals(currVcs.getHash(), blockState.getHash());
+ }
+
+ @Test
+ @SneakyThrows
+ public void testEndorseBlockState() {
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ when(endorseServiceRepository.getExactTpBta(any())).thenReturn(new TpBtaWrapper(tpbta));
+ when(endorseServiceRepository.getValidatedConsensusState(anyString(), any(BigInteger.class))).thenReturn(new ValidatedConsensusStateWrapper(currVcs));
+ when(endorseServiceRepository.hasValidatedConsensusState(anyString(), any())).thenReturn(true);
+
+ StreamRecorder responseObserver = StreamRecorder.create();
+ committeeNodeService.endorseBlockState(
+ EndorseBlockStateRequest.newBuilder()
+ .setCrossChainLane(ByteString.copyFrom(crossChainLane.encode()))
+ .setHeight(currState.getHeight().toString())
+ .setReceiverDomain("receiverdomain")
+ .build(),
+ responseObserver
+ );
+
+ Assert.assertTrue(responseObserver.awaitCompletion(5, TimeUnit.SECONDS));
+ Assert.assertNull(responseObserver.getError());
+
+ var results = responseObserver.getValues();
+ Assert.assertEquals(1, results.size());
+ Assert.assertEquals(0, results.getFirst().getCode());
+
+ var resp = new EndorseBlockStateResp(
+ AuthMessageFactory.createAuthMessage(results.getFirst().getEndorseBlockStateResp().getBlockStateAuthMsg().toByteArray()),
+ CommitteeNodeProof.decode(results.getFirst().getEndorseBlockStateResp().getCommitteeNodeProof().toByteArray())
+ );
+ Assert.assertEquals(
+ "receiverdomain",
+ SDPMessageFactory.createSDPMessage(resp.getBlockStateAuthMsg().getPayload()).getTargetDomain().getDomain()
+ );
+ Assert.assertArrayEquals(
+ currVcs.getHash(),
+ resp.getBlockState().getHash()
+ );
+ Assert.assertEquals(
+ currVcs.getHeight(),
+ resp.getBlockState().getHeight()
+ );
+ Assert.assertEquals(
+ currVcs.getStateTimestamp(),
+ resp.getBlockState().getTimestamp()
+ );
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.builder()
+ .committeeId(committeeId)
+ .sigs(ListUtil.toList(resp.getCommitteeNodeProof()))
+ .build(),
+ ThirdPartyProof.create(
+ tpbta.getTpbtaVersion(),
+ resp.getBlockStateAuthMsg().encode(),
+ crossChainLane
+ ).getEncodedToSign()
+ )
+ );
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceTest.java
new file mode 100755
index 00000000..415a4c1a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/server/MonitorOrderServiceTest.java
@@ -0,0 +1,4 @@
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.server;
+
+public class MonitorOrderServiceTest {
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/BCDNSManageServiceTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/BCDNSManageServiceTest.java
new file mode 100755
index 00000000..af0ad4b5
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/BCDNSManageServiceTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alipay.antchain.bridge.bcdns.factory.BlockChainDomainNameServiceFactory;
+import com.alipay.antchain.bridge.bcdns.service.BCDNSTypeEnum;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.enums.BCDNSStateEnum;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BCDNSServiceDO;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import jakarta.annotation.Resource;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+public class BCDNSManageServiceTest extends TestBase {
+
+ private static MockedStatic mockStaticObj = null;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ try {
+ mockStaticObj = mockStatic(BlockChainDomainNameServiceFactory.class);
+ when(BlockChainDomainNameServiceFactory.create(notNull(), any())).thenReturn(new DummyBcdnsServiceImpl());
+ } catch (Exception e) {
+ if (ObjectUtil.isNotNull(mockStaticObj)) {
+ mockStaticObj.close();
+ }
+ }
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ if (ObjectUtil.isNotNull(mockStaticObj)) {
+ mockStaticObj.close();
+ }
+ }
+
+ private static BCDNSServiceDO bcdnsServiceDO;
+
+ static {
+ var dsc = new DomainSpaceCertWrapper(
+ CrossChainCertificateUtil.readCrossChainCertificateFromPem(BCDNS_ROOT_CERT.getBytes())
+ );
+ bcdnsServiceDO = new BCDNSServiceDO(
+ CrossChainDomain.ROOT_DOMAIN_SPACE,
+ dsc.getOwnerOid(),
+ dsc,
+ BCDNSTypeEnum.EMBEDDED,
+ BCDNSStateEnum.WORKING,
+ "{}".getBytes()
+ );
+ }
+
+ @Resource
+ private IBCDNSManageService bcdnsManageService;
+
+ @MockBean
+ private IBCDNSRepository bcdnsRepository;
+
+ @Test
+ public void countBCDNSService() {
+ when(bcdnsRepository.countBCDNSService()).thenReturn(1L);
+ assertEquals(
+ 1,
+ bcdnsManageService.countBCDNSService()
+ );
+ }
+
+ @Test
+ public void getBCDNSClient() {
+ when(bcdnsRepository.getBCDNSServiceDO(any())).thenReturn(bcdnsServiceDO);
+ assertNotNull(bcdnsManageService.getBCDNSClient(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ }
+
+ @Test
+ public void registerBCDNSService() {
+ bcdnsManageService.registerBCDNSService(
+ bcdnsServiceDO.getDomainSpace(),
+ bcdnsServiceDO.getType(),
+ bcdnsServiceDO.getProperties(),
+ bcdnsServiceDO.getDomainSpaceCertWrapper().getDomainSpaceCert()
+ );
+ Assert.assertNotNull(bcdnsManageService.getBCDNSClient(CrossChainDomain.ROOT_DOMAIN_SPACE));
+ }
+
+ @Test
+ public void startBCDNSService() {
+ assertNotNull(bcdnsManageService.startBCDNSService(bcdnsServiceDO));
+ }
+
+ @Test
+ public void restartBCDNSService() {
+ var frozenBcdnsDO = BeanUtil.copyProperties(bcdnsServiceDO, BCDNSServiceDO.class);
+ frozenBcdnsDO.setState(BCDNSStateEnum.FROZEN);
+ when(bcdnsRepository.getBCDNSServiceDO(any())).thenReturn(frozenBcdnsDO);
+ bcdnsManageService.restartBCDNSService(frozenBcdnsDO.getDomainSpace());
+ }
+
+ @Test
+ public void stopBCDNSService() {
+ when(bcdnsRepository.hasBCDNSService(any())).thenReturn(true);
+ bcdnsManageService.stopBCDNSService(bcdnsServiceDO.getDomainSpace());
+ }
+}
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/EndorserServiceTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/EndorserServiceTest.java
new file mode 100755
index 00000000..9801c491
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/EndorserServiceTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import java.math.BigInteger;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
+import com.alipay.antchain.bridge.commons.bcdns.PTCCredentialSubject;
+import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
+import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
+import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.bta.BlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.ptc.PTCTypeEnum;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyBlockchainTrustAnchorV1;
+import com.alipay.antchain.bridge.commons.core.ptc.ThirdPartyProof;
+import com.alipay.antchain.bridge.commons.core.ptc.ValidatedConsensusStateV1;
+import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
+import com.alipay.antchain.bridge.commons.utils.crypto.HashAlgoEnum;
+import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
+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.monitor.node.TestBase;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.BtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.DomainSpaceCertWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.TpBtaWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.commons.models.ValidatedConsensusStateWrapper;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IBCDNSRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.IEndorseServiceRepository;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.dal.repository.interfaces.ISystemConfigRepository;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeEndorseProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.CommitteeNodeProof;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.EndorseBlockStateResp;
+import com.alipay.antchain.bridge.ptc.committee.types.basic.NodePublicKeyEntry;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.CommitteeEndorseRoot;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.NodeEndorseInfo;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.OptionalEndorsePolicy;
+import com.alipay.antchain.bridge.ptc.committee.types.tpbta.VerifyBtaExtension;
+import jakarta.annotation.Resource;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import static org.mockito.Mockito.*;
+
+public class EndorserServiceTest extends TestBase {
+
+ private static final ThirdPartyBlockchainTrustAnchorV1 tpbta;
+
+ private static final CrossChainLane crossChainLane;
+
+ private static final ObjectIdentity oid;
+
+ private static final BlockchainTrustAnchorV1 bta;
+
+ private static final AbstractCrossChainCertificate domainCert;
+
+ private static final ConsensusState anchorState;
+
+ private static final ConsensusState currState;
+
+ private static final UniformCrosschainPacket ucp;
+
+ static {
+ oid = new X509PubkeyInfoObjectIdentity(
+ RAW_NODE_PTC_PUBLIC_KEY
+ );
+
+ var policy = new OptionalEndorsePolicy();
+ policy.setThreshold(new OptionalEndorsePolicy.Threshold(OptionalEndorsePolicy.OperatorEnum.GREATER_THAN, -1));
+ var nodeEndorseInfo = new NodeEndorseInfo();
+ nodeEndorseInfo.setNodeId("node1");
+ nodeEndorseInfo.setRequired(true);
+ var nodePubkeyEntry = new NodePublicKeyEntry("default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
+ nodeEndorseInfo.setPublicKey(nodePubkeyEntry);
+ crossChainLane = new CrossChainLane(new CrossChainDomain("test"), new CrossChainDomain("test"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"), CrossChainIdentity.fromHexStr("0000000000000000000000000000000000000000000000000000000000000001"));
+ tpbta = new ThirdPartyBlockchainTrustAnchorV1(
+ 1,
+ BigInteger.ONE,
+ (PTCCredentialSubject) NODE_PTC_CERT.getCredentialSubjectInstance(),
+ crossChainLane,
+ 1,
+ HashAlgoEnum.KECCAK_256,
+ new CommitteeEndorseRoot(
+ "1",
+ policy,
+ ListUtil.toList(nodeEndorseInfo)
+ ).encode(),
+ CommitteeEndorseProof.builder()
+ .committeeId("1")
+ .sigs(ListUtil.toList(new CommitteeNodeProof(
+ "test",
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1,
+ "".getBytes()
+ ))).build().encode()
+ );
+
+ bta = new BlockchainTrustAnchorV1();
+ bta.setBcOwnerPublicKey(RAW_NODE_PTC_PUBLIC_KEY);
+ bta.setDomain(new CrossChainDomain("antchain.com"));
+ bta.setSubjectIdentity("test".getBytes());
+ bta.setBcOwnerSigAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1);
+ bta.setPtcOid(NODE_PTC_CERT.getCredentialSubjectInstance().getApplicant());
+ bta.setInitHeight(BigInteger.valueOf(100L));
+ bta.setInitBlockHash(RandomUtil.randomBytes(32));
+ bta.setSubjectProduct("mychain");
+ bta.setExtension(
+ new VerifyBtaExtension(
+ CommitteeEndorseRoot.decode(tpbta.getEndorseRoot()),
+ crossChainLane
+ ).encode()
+ );
+ bta.setSubjectVersion(0);
+ bta.setAmId(RandomUtil.randomBytes(32));
+
+ bta.sign(NODE_PTC_PRIVATE_KEY);
+
+ domainCert = CrossChainCertificateUtil.readCrossChainCertificateFromPem(ANTCHAIN_DOT_COM_CERT.getBytes());
+
+ anchorState = new ConsensusState(
+ crossChainLane.getSenderDomain(),
+ BigInteger.valueOf(100L),
+ bta.getInitBlockHash(),
+ RandomUtil.randomBytes(32),
+ System.currentTimeMillis(),
+ "{}".getBytes(),
+ "{}".getBytes(),
+ "{}".getBytes()
+ );
+
+ currState = new ConsensusState(
+ crossChainLane.getSenderDomain(),
+ BigInteger.valueOf(101L),
+ RandomUtil.randomBytes(32),
+ anchorState.getParentHash(),
+ System.currentTimeMillis(),
+ "{}".getBytes(),
+ "{}".getBytes(),
+ "{}".getBytes()
+ );
+
+ var sdpMessage = SDPMessageFactory.createSDPMessage(
+ 1,
+ new byte[32],
+ crossChainLane.getReceiverDomain().getDomain(),
+ crossChainLane.getReceiverId().getRawID(),
+ -1,
+ "awesome antchain-bridge".getBytes()
+ );
+
+ var am = AuthMessageFactory.createAuthMessage(
+ 1,
+ crossChainLane.getSenderId().getRawID(),
+ 0,
+ sdpMessage.encode()
+ );
+
+ ucp = new UniformCrosschainPacket(
+ crossChainLane.getSenderDomain(),
+ CrossChainMessage.createCrossChainMessage(
+ CrossChainMessage.CrossChainMessageType.AUTH_MSG,
+ BigInteger.valueOf(101L),
+ DateUtil.current(),
+ currState.getHash(),
+ am.encode(),
+ "event".getBytes(),
+ "merkle proof".getBytes(),
+ RandomUtil.randomBytes(32)
+ ),
+ NODE_PTC_CERT.getCredentialSubjectInstance().getApplicant()
+ );
+ }
+
+ @Value("${committee.id}")
+ private String committeeId;
+
+ @Value("${committee.node.id}")
+ private String nodeId;
+
+ @Resource
+ private IEndorserService endorserService;
+
+ @MockBean
+ private IEndorseServiceRepository endorseServiceRepository;
+
+ @MockBean
+ private IBCDNSRepository bcdnsRepository;
+
+ @MockBean
+ private ISystemConfigRepository systemConfigRepository;
+
+ @MockBean
+ private IHcdvsPluginService hcdvsPluginService;
+
+ @Resource
+ private AbstractCrossChainCertificate ptcCrossChainCert;
+
+ @Test
+ public void testQueryTpBta() {
+ when(endorseServiceRepository.getMatchedTpBta(any())).thenReturn(new TpBtaWrapper(tpbta));
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ tpbta.encode(),
+ endorserService.queryMatchedTpBta(crossChainLane).getTpbta().encode()
+ )
+ );
+ }
+
+ @Test
+ public void testVerifyBta() {
+
+ when(bcdnsRepository.getDomainSpaceCert(anyString())).thenReturn(
+ new DomainSpaceCertWrapper(
+ CrossChainCertificateUtil.readCrossChainCertificateFromPem(DOT_COM_DOMAIN_SPACE_CERT.getBytes())
+ )
+ );
+
+ when(systemConfigRepository.queryCurrentPtcAnchorVersion()).thenReturn(BigInteger.ONE);
+
+ var tpbtaWrapper = endorserService.verifyBta(domainCert, bta);
+
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ tpbta.getEndorseRoot(),
+ tpbtaWrapper.getEndorseRoot().encode()
+ )
+ );
+ Assert.assertEquals(
+ tpbta.getCrossChainLane().getLaneKey(),
+ tpbtaWrapper.getCrossChainLane().getLaneKey()
+ );
+ Assert.assertTrue(
+ ArrayUtil.equals(
+ ptcCrossChainCert.getCredentialSubject(),
+ tpbtaWrapper.getTpbta().getSignerPtcCredentialSubject().encode()
+ )
+ );
+ Assert.assertEquals(
+ committeeId,
+ tpbtaWrapper.getEndorseProof().getCommitteeId()
+ );
+ Assert.assertEquals(
+ 1,
+ tpbtaWrapper.getEndorseRoot().getEndorsers().size()
+ );
+ Assert.assertEquals(
+ nodeId,
+ tpbtaWrapper.getEndorseRoot().getEndorsers().getFirst().getNodeId()
+ );
+ Assert.assertEquals(
+ "default",
+ tpbtaWrapper.getEndorseRoot().getEndorsers().getFirst().getPublicKey().getKeyId()
+ );
+ Assert.assertTrue(
+ tpbtaWrapper.getEndorseRoot().check(tpbtaWrapper.getEndorseProof(), tpbtaWrapper.getTpbta().getEncodedToSign())
+ );
+ }
+
+ @Test
+ 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.getBta(anyString())).thenReturn(new BtaWrapper(bta));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class);
+ vcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ vcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ vcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var vcsSigned = endorserService.commitAnchorState(crossChainLane, anchorState);
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.decode(vcsSigned.getPtcProof()),
+ vcs.getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ public void testCommitConsensusState() {
+ var vcs = BeanUtil.copyProperties(anchorState, ValidatedConsensusStateV1.class);
+ vcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ vcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ vcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var hcdvs = mock(IHeteroChainDataVerifierService.class);
+ 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.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(vcs));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ var vcsSigned = endorserService.commitConsensusState(crossChainLane, currState);
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.decode(vcsSigned.getPtcProof()),
+ currVcs.getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ public void testVerifyUcp() {
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ var hcdvs = mock(IHeteroChainDataVerifierService.class);
+ when(hcdvs.verifyCrossChainMessage(any(), any())).thenReturn(VerifyResult.builder().success(true).build());
+ 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.getValidatedConsensusState(anyString(), anyString())).thenReturn(new ValidatedConsensusStateWrapper(currVcs));
+ when(hcdvsPluginService.getHCDVSService(anyString())).thenReturn(hcdvs);
+
+ var nodeProof = endorserService.verifyUcp(crossChainLane, ucp);
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.builder()
+ .committeeId(committeeId)
+ .sigs(ListUtil.toList(nodeProof))
+ .build(),
+ ThirdPartyProof.create(
+ tpbta.getTpbtaVersion(),
+ ucp.getSrcMessage().getMessage(),
+ crossChainLane
+ ).getEncodedToSign()
+ )
+ );
+ }
+
+ @Test
+ public void testEndorseBlockState() {
+ var currVcs = BeanUtil.copyProperties(currState, ValidatedConsensusStateV1.class);
+ currVcs.setPtcOid(ptcCrossChainCert.getCredentialSubjectInstance().getApplicant());
+ currVcs.setTpbtaVersion(tpbta.getTpbtaVersion());
+ currVcs.setPtcType(PTCTypeEnum.COMMITTEE);
+
+ when(endorseServiceRepository.getExactTpBta(any())).thenReturn(new TpBtaWrapper(tpbta));
+ when(endorseServiceRepository.getValidatedConsensusState(anyString(), any(BigInteger.class))).thenReturn(new ValidatedConsensusStateWrapper(currVcs));
+ when(endorseServiceRepository.hasValidatedConsensusState(anyString(), any())).thenReturn(true);
+
+ EndorseBlockStateResp resp = endorserService.endorseBlockState(tpbta.getCrossChainLane(), "receiverdomain", currVcs.getHeight());
+ Assert.assertEquals(
+ "receiverdomain",
+ SDPMessageFactory.createSDPMessage(resp.getBlockStateAuthMsg().getPayload()).getTargetDomain().getDomain()
+ );
+ Assert.assertArrayEquals(
+ currVcs.getHash(),
+ resp.getBlockState().getHash()
+ );
+ Assert.assertEquals(
+ currVcs.getHeight(),
+ resp.getBlockState().getHeight()
+ );
+ Assert.assertEquals(
+ currVcs.getStateTimestamp(),
+ resp.getBlockState().getTimestamp()
+ );
+ Assert.assertTrue(
+ new TpBtaWrapper(tpbta).getEndorseRoot().check(
+ CommitteeEndorseProof.builder()
+ .committeeId(committeeId)
+ .sigs(ListUtil.toList(resp.getCommitteeNodeProof()))
+ .build(),
+ ThirdPartyProof.create(
+ tpbta.getTpbtaVersion(),
+ resp.getBlockStateAuthMsg().encode(),
+ crossChainLane
+ ).getEncodedToSign()
+ )
+ );
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/HCDVSServiceTest.java b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/HCDVSServiceTest.java
new file mode 100755
index 00000000..2e783cb8
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/java/com/alipay/antchain/bridge/ptc/committee/monitor/node/service/HCDVSServiceTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.alipay.antchain.bridge.ptc.committee.monitor.node.service;
+
+import java.util.List;
+
+import com.alipay.antchain.bridge.plugins.manager.core.AntChainBridgePluginState;
+import com.alipay.antchain.bridge.ptc.committee.monitor.node.TestBase;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Test;
+
+@Slf4j
+public class HCDVSServiceTest extends TestBase {
+
+ private static final String PLUGIN_PRODUCT = "testchain";
+
+ private static final String PLUGIN_PATH = "./src/test/resources/plugins/plugin-testchain2-0.1-SNAPSHOT-plugin.jar";
+
+ @Resource
+ private IHcdvsPluginService hcdvsPluginService;
+
+ @Test
+ public void testHCDVSService() {
+ List supportProducts = hcdvsPluginService.getAvailableProducts();
+ Assert.assertFalse(supportProducts.isEmpty());
+
+ Assert.assertTrue(hcdvsPluginService.hasPlugin(PLUGIN_PRODUCT));
+ hcdvsPluginService.stopPlugin(PLUGIN_PRODUCT);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.STOP);
+ hcdvsPluginService.startPluginFromStop(PLUGIN_PRODUCT);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.START);
+ }
+
+ @Test
+ public void testGetAvailableProduct() {
+ List supportProducts = hcdvsPluginService.getAvailableProducts();
+
+ for (String product:supportProducts) {
+ log.info("Committee-node's HcdvsService Contains products: {}", product);
+ }
+ }
+
+ /*
+ @Test
+ public void testGetHCDVSService() {
+ IHeteroChainDataVerifierService hcdvsService = hcdvsPluginService.getHCDVSService(PLUGIN_PRODUCT);
+ hcdvsPluginService.stopPlugin(PLUGIN_PRODUCT); // stopPlugin逻辑里只是把state=STOP,要load指定PLUGIN需要一个unloadPlugin的接口,暂缺
+ hcdvsPluginService.loadPlugin(PLUGIN_PATH);
+ hcdvsPluginService.startPlugin(PLUGIN_PATH);
+ }
+ */
+
+ @Test
+ public void testReloadPluginWithoutPath() {
+ if(hcdvsPluginService.hasPlugin(PLUGIN_PRODUCT)) {
+ hcdvsPluginService.stopPlugin(PLUGIN_PRODUCT);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.STOP);
+ }
+ hcdvsPluginService.reloadPlugin(PLUGIN_PRODUCT);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.START);
+ }
+
+ @Test
+ public void testReloadPluginWithPath() {
+ if(hcdvsPluginService.hasPlugin(PLUGIN_PRODUCT)) {
+ hcdvsPluginService.stopPlugin(PLUGIN_PRODUCT);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.STOP);
+ }
+ hcdvsPluginService.reloadPlugin(PLUGIN_PRODUCT, PLUGIN_PATH);
+ Assert.assertEquals(hcdvsPluginService.getPlugin(PLUGIN_PRODUCT).getCurrState(), AntChainBridgePluginState.START);
+ }
+}
diff --git a/acb-committeeptc/monitor-node/src/test/resources/application-test.yml b/acb-committeeptc/monitor-node/src/test/resources/application-test.yml
new file mode 100755
index 00000000..dc4cd06a
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/application-test.yml
@@ -0,0 +1,43 @@
+spring:
+ application:
+ name: committee-ptc
+ datasource:
+ driver-class-name: org.h2.Driver
+ url: jdbc:h2:mem:committee-node;DB_CLOSE_DELAY=-1;MODE=MySQL;IGNORECASE=TRUE
+ password: 123
+ username: root
+ sql:
+ init:
+ data-locations: classpath:data/ddl.sql
+logging:
+ file:
+ path: ./logs
+ level:
+ app: INFO
+ logback:
+ rollingpolicy:
+ clean-history-on-start: true
+committee:
+ id: 1
+ node:
+ credential:
+ sign-algo: "Keccak256WithSecp256k1"
+ private-key-file: ./private_key.pem
+ cert-file: ./ptc.crt
+ admin:
+ port: ${random.int(10088,20088)}
+ plugin:
+ # where to load the hetero-chain plugins
+ repo: ./src/test/resources/plugins
+ policy:
+ # limit actions of the plugin classloader
+ classloader:
+ resource:
+ ban-with-prefix:
+ # the plugin classloader will not read the resource file starting with the prefix below
+ APPLICATION: "META-INF/services/io.grpc."
+grpc:
+ server:
+ port: 0
+ security:
+ enabled: false
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/test/resources/data/ddl.sql b/acb-committeeptc/monitor-node/src/test/resources/data/ddl.sql
new file mode 100755
index 00000000..3c42b2a4
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/data/ddl.sql
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 Ant Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+CREATE TABLE IF NOT EXISTS `system_config`
+(
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `conf_key` VARCHAR(128) DEFAULT NULL,
+ `conf_value` VARCHAR(15000) DEFAULT NULL,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `conf_key` (`conf_key`)
+);
+
+CREATE TABLE IF NOT EXISTS `bcdns_service`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `domain_space` VARCHAR(128) NOT NULL,
+ `parent_space` VARCHAR(128),
+ `owner_oid` VARCHAR(255) NOT NULL,
+ `type` VARCHAR(32) NOT NULL,
+ `state` INT NOT NULL,
+ `properties` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS `domain_space_cert`
+(
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `domain_space` VARCHAR(128) DEFAULT NULL,
+ `parent_space` VARCHAR(128) DEFAULT NULL,
+ `owner_oid_hex` VARCHAR(255) NOT NULL,
+ `description` VARCHAR(128) DEFAULT NULL,
+ `domain_space_cert` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bta`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `product` VARCHAR(64) DEFAULT NULL,
+ `domain` VARCHAR(128) NOT NULL,
+ `bta_version` INT(11) DEFAULT NULL,
+ `subject_version` INT(11) DEFAULT NULL,
+ `raw_bta` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS `tpbta`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `version` INT(11) DEFAULT NULL,
+ `sender_domain` VARCHAR(128) NOT NULL,
+ `bta_subject_version` INT(11) DEFAULT NULL,
+ `sender_id` VARCHAR(64) DEFAULT NULL,
+ `receiver_domain` VARCHAR(128) DEFAULT NULL,
+ `receiver_id` VARCHAR(64) DEFAULT NULL,
+ `tpbta_version` INT(11) DEFAULT NULL,
+ `ptc_verify_anchor_version` INT(11) DEFAULT NULL,
+ `raw_tpbta` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS `validated_consensus_states`
+(
+ `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ `cs_version` INT(11) NOT NULL,
+ `domain` VARCHAR(128) NOT NULL,
+ `height` BIGINT UNSIGNED NOT NULL,
+ `hash` VARCHAR(64) DEFAULT NULL,
+ `parent_hash` VARCHAR(64) DEFAULT NULL,
+ `raw_vcs` BLOB,
+ `gmt_create` DATETIME DEFAULT CURRENT_TIMESTAMP,
+ `gmt_modified` DATETIME DEFAULT CURRENT_TIMESTAMP
+);
diff --git a/acb-committeeptc/monitor-node/src/test/resources/data/drop_all.sql b/acb-committeeptc/monitor-node/src/test/resources/data/drop_all.sql
new file mode 100755
index 00000000..77183565
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/data/drop_all.sql
@@ -0,0 +1 @@
+DROP ALL OBJECTS
\ No newline at end of file
diff --git a/acb-committeeptc/monitor-node/src/test/resources/private_key.pem b/acb-committeeptc/monitor-node/src/test/resources/private_key.pem
new file mode 100755
index 00000000..49c22afb
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/private_key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHQCAQEEIF2vqNkpBC7VIdSWCsfFw7oMJFxpvBAlXxRIcpaME1LXoAcGBSuBBAAK
+oUQDQgAE9LJYmJcZBSEjN2rNGruF1zIIq5JLjSqlwZQ2xNcm2TL0prWFdapt0BVr
+ACpaw0MitKnYjkcv16qm+XUbFK/E3w==
+-----END EC PRIVATE KEY-----
diff --git a/acb-committeeptc/monitor-node/src/test/resources/ptc.crt b/acb-committeeptc/monitor-node/src/test/resources/ptc.crt
new file mode 100755
index 00000000..09ed47de
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/ptc.crt
@@ -0,0 +1,13 @@
+-----BEGIN PROOF TRANSFORMATION COMPONENT CERTIFICATE-----
+AAD4AQAAAAABAAAAMQEADAAAAGFudGNoYWluLXB0YwIAAQAAAAIDAGsAAAAAAGUA
+AAAAAAEAAAAAAQBYAAAAMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE9LJYmJcZBSEj
+N2rNGruF1zIIq5JLjSqlwZQ2xNcm2TL0prWFdapt0BVrACpaw0MitKnYjkcv16qm
++XUbFK/E3wQACAAAANIlv2YAAAAABQAIAAAAUlmgaAAAAAAGAKAAAAAAAJoAAAAA
+AAMAAAAxLjABAA0AAABjb21taXR0ZWUtcHRjAgABAAAAAQMAawAAAAAAZQAAAAAA
+AQAAAAABAFgAAAAwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAT0sliYlxkFISM3as0a
+u4XXMgirkkuNKqXBlDbE1ybZMvSmtYV1qm3QFWsAKlrDQyK0qdiORy/Xqqb5dRsU
+r8TfBAAAAAAABwCfAAAAAACZAAAAAAAKAAAAS0VDQ0FLLTI1NgEAIAAAAMMQP7r7
+zn4cC2mvoPTZOFzcfakEvZMwQykDEQv9al5fAgAWAAAAS2VjY2FrMjU2V2l0aFNl
+Y3AyNTZrMQMAQQAAAOKPO60UaQlG2KkvfR9QQmM94G2vojyH2RAtWBbewOIqGtgk
+OZuajWhHiipbzTT8Ssb/LS65C/5HeVMAYNkc/gAA
+-----END PROOF TRANSFORMATION COMPONENT CERTIFICATE-----
diff --git a/acb-committeeptc/monitor-node/src/test/resources/public_key.pem b/acb-committeeptc/monitor-node/src/test/resources/public_key.pem
new file mode 100755
index 00000000..c28fd629
--- /dev/null
+++ b/acb-committeeptc/monitor-node/src/test/resources/public_key.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE9LJYmJcZBSEjN2rNGruF1zIIq5JLjSql
+wZQ2xNcm2TL0prWFdapt0BVrACpaw0MitKnYjkcv16qm+XUbFK/E3w==
+-----END PUBLIC KEY-----
diff --git a/acb-committeeptc/pom.xml b/acb-committeeptc/pom.xml
index 0c27c452..e7b9b833 100644
--- a/acb-committeeptc/pom.xml
+++ b/acb-committeeptc/pom.xml
@@ -17,6 +17,8 @@
supervisor
node
node-cli
+ monitor-node
+ monitor-node-cli
pom
diff --git a/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java b/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java
index 371698a6..d31b9227 100644
--- a/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java
+++ b/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/CrossChainServiceImpl.java
@@ -20,6 +20,7 @@
import cn.hutool.core.util.StrUtil;
import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext;
import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.MonitorContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.PTCContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain;
@@ -42,6 +43,7 @@
import net.devh.boot.grpc.server.service.GrpcService;
import java.math.BigInteger;
+import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
@@ -140,9 +142,24 @@ public void bbcCall(CallBBCRequest request, StreamObserver responseObs
case SETUPAUTHMESSAGECONTRACTREQ:
resp = handleSetupAuthMessageContract(bbcService, product, domain);
break;
+ case SETUPMONITORMESSAGECONTRACTREQ:
+ resp = handleSetupMonitorMessageContract(bbcService, product, domain);
+ break;
case SETPROTOCOLREQ:
resp = handleSetProtocol(bbcService, request.getSetProtocolReq(), product, domain);
break;
+ case SETPROTOCOLINMONITORREQ:
+ resp = handleSetProtocolInMonitor(bbcService, request.getSetProtocolInMonitorReq(), product, domain);
+ break;
+ case SETMONITORCONTROLREQ:
+ resp = handleSetMonitorControl(bbcService, request.getSetMonitorControlReq(), product, domain);
+ break;
+ case SETPTCHUBINMONITORVERIFIERREQ:
+ resp = handleSetPtcHubInMonitorVerifier(bbcService, request.getSetPtcHubInMonitorVerifierReq(), product, domain);
+ break;
+ case SETMONITORCONTRACTREQ:
+ resp = handleSetMonitorContract(bbcService, request.getSetMonitorContractReq(), product, domain);
+ break;
case SETAMCONTRACTREQ:
resp = handleSetAmContract(bbcService, request.getSetAmContractReq(), product, domain);
break;
@@ -152,6 +169,9 @@ public void bbcCall(CallBBCRequest request, StreamObserver responseObs
case RELAYAUTHMESSAGEREQ:
resp = handleRelayAuthMessage(bbcService, request.getRelayAuthMessageReq(), product, domain);
break;
+ case RELAYMONITORORDERREQ:
+ resp = handleRelayMonitorOrder(bbcService, request.getRelayMonitorOrderReq(), product, domain);
+ break;
case READCROSSCHAINMESSAGERECEIPTREQ:
resp = handleReadCrossChainMessageReceiptRequest(bbcService, request.getReadCrossChainMessageReceiptReq(), product, domain);
break;
@@ -289,6 +309,26 @@ private Response handleGetContext(IBBCService bbcService, String product, String
}
}
+ private Response handleSetupMonitorMessageContract(IBBCService bbcService, String product, String domain) {
+ try {
+ bbcService.setupMonitorContract();
+ MonitorContract monitor = bbcService.getContext().getMonitorContract();
+ log.info("BBCCall(handleSetupMonitorMessageContract) success [product: {}, domain: {}]: monitorAddr: {}, monitorStatus: {}", product, domain, monitor.getContractAddress(), monitor.getStatus());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder()
+ .setSetupMonitorResp(SetupMonitorMessageContractResponse.newBuilder()
+ .setMonitorContract(
+ MonitorMessageContract.newBuilder()
+ .setContractAddress(monitor.getContractAddress())
+ .setStatusValue(monitor.getStatus().ordinal())
+ )
+ )
+ );
+ } catch (Exception e) {
+ log.error("BBCCall(handleSetupMonitorMessageContract) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_SETUPMONITORMESSAGECONTRACT_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_SETUPMONITORMESSAGECONTRACT_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_SETUPMONITORMESSAGECONTRACT_ERROR, e.toString());
+ }
+ }
+
private Response handleSetupSDPMessageContract(IBBCService bbcService, String product, String domain) {
try {
bbcService.setupSDPMessageContract();
@@ -336,6 +376,46 @@ private Response handleSetProtocol(IBBCService bbcService, SetProtocolRequest re
}
}
+ private Response handleSetProtocolInMonitor(IBBCService bbcService, SetProtocolInMonitorRequest request, String product, String domain) {
+ try {
+ bbcService.setProtocolInMonitor(request.getProtocolAddress());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder());
+ } catch (Exception e) {
+ log.error("BBCCall(handleSetProtocolInMonitor) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_SETPROTOCOLINMONITOR_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_SETPROTOCOLINMONITOR_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_SETPROTOCOLINMONITOR_ERROR, e.toString());
+ }
+ }
+
+ private Response handleSetMonitorControl(IBBCService bbcService, SetMonitorControlRequest request, String product, String domain) {
+ try {
+ bbcService.setMonitorControl(request.getMonitorType());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder());
+ } catch (Exception e) {
+ log.error("BBCCall(handleSetMonitorControl) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_SETMONITORCONTROL_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_SETMONITORCONTROL_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_SETMONITORCONTROL_ERROR, e.toString());
+ }
+ }
+
+ private Response handleSetPtcHubInMonitorVerifier(IBBCService bbcService, SetPtcHubInMonitorVerifierRequest request, String product, String domain) {
+ try {
+ bbcService.setPtcHubInMonitorVerifier(request.getContractAddress());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder());
+ } catch (Exception e) {
+ log.error("BBCCall(handleSetPtcHubInMonitorVerifier) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_SETPTCHUBINMONITORVERIFIER_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_SETPTCHUBINMONITORVERIFIER_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_SETPTCHUBINMONITORVERIFIER_ERROR, e.toString());
+ }
+ }
+
+ private Response handleSetMonitorContract(IBBCService bbcService, SetMonitorContractRequest request, String product, String domain) {
+ try {
+ bbcService.setMonitorContract(request.getContractAddress());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder());
+ } catch (Exception e) {
+ log.error("BBCCall(handleSetMonitorContract) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_SETMONITORCONTRACT_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_SETMONITORCONTRACT_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_SETMONITORCONTRACT_ERROR, e.toString());
+ }
+ }
+
private Response handleSetAmContract(IBBCService bbcService, SetAmContractRequest request, String product, String domain) {
try {
bbcService.setAmContract(request.getContractAddress());
@@ -382,6 +462,28 @@ private Response handleRelayAuthMessage(IBBCService bbcService, RelayAuthMessage
}
}
+ private Response handleRelayMonitorOrder(IBBCService bbcService, RelayMonitorOrderRequest request, String product, String domain) {
+ try {
+ CrossChainMessageReceipt ret = bbcService.relayMonitorOrder(request.getCommitteeId(), request.getSignAlgo(), request.getRawProof().toByteArray(), request.getRawMonitorOrder().toByteArray());
+ return ResponseBuilder.buildBBCSuccessResp(CallBBCResponse.newBuilder()
+ .setRelayMonitorOrderResp(RelayMonitorOrderResponse.newBuilder()
+ .setReceipt(
+ com.alipay.antchain.bridge.pluginserver.service.CrossChainMessageReceipt.newBuilder()
+ .setTxhash(ObjectUtil.defaultIfNull(ret.getTxhash(), ""))
+ .setConfirmed(ret.isConfirmed())
+ .setSuccessful(ret.isSuccessful())
+ .setErrorMsg(ObjectUtil.defaultIfNull(ret.getErrorMsg(), ""))
+ .setTxTimestamp(ret.getTxTimestamp())
+ .setRawTx(ByteString.copyFrom(ObjectUtil.defaultIfNull(ret.getRawTx(), new byte[]{})))
+ )
+ )
+ );
+ } catch (Exception e) {
+ log.error("BBCCall(handleRelayMonitorOrder) fail [product: {}, domain: {}, errorCode: {}, errorMsg: {}]", product, domain, ServerErrorCodeEnum.BBC_RELAYMONITORORDER_ERROR.getErrorCode(), ServerErrorCodeEnum.BBC_RELAYMONITORORDER_ERROR.getShortMsg(), e);
+ return ResponseBuilder.buildFailResp(ServerErrorCodeEnum.BBC_RELAYMONITORORDER_ERROR, e.toString());
+ }
+ }
+
private Response handleReadCrossChainMessageReceiptRequest(IBBCService bbcService, ReadCrossChainMessageReceiptRequest request, String product, String domain) {
try {
CrossChainMessageReceipt receipt = bbcService.readCrossChainMessageReceipt(request.getTxhash());
diff --git a/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java b/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java
index 6e0eddf5..e481f84a 100644
--- a/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java
+++ b/acb-pluginserver/ps-server/src/main/java/com/alipay/antchain/bridge/pluginserver/server/exception/ServerErrorCodeEnum.java
@@ -24,6 +24,18 @@ public enum ServerErrorCodeEnum {
SUCCESS(0, "success"),
+ BBC_SETUPMONITORMESSAGECONTRACT_ERROR(250, "[bbc] set up monitor contract failed"),
+
+ BBC_SETPROTOCOLINMONITOR_ERROR(251, "[bbc] set protocol in monitor contract failed"),
+
+ BBC_SETMONITORCONTROL_ERROR(252, "[bbc] set monitor control failed"),
+
+ BBC_SETPTCHUBINMONITORVERIFIER_ERROR(253, "[bbc] set ptc hub in monitor verifier failed"),
+
+ BBC_SETMONITORCONTRACT_ERROR(254, "[bbc] set monitor contract failed"),
+
+ BBC_RELAYMONITORORDER_ERROR(255, "[bbc] relay monitor order failed"),
+
UNSUPPORT_BBC_REQUEST_ERROR(200, "unsupport bbc request type"),
BBC_GET_SERVICE_ERROR(201, "[bbc] get service failed"),
diff --git a/acb-pluginserver/ps-service/src/main/proto/pluginserver.proto b/acb-pluginserver/ps-service/src/main/proto/pluginserver.proto
index 706f88c5..a7f48eda 100644
--- a/acb-pluginserver/ps-service/src/main/proto/pluginserver.proto
+++ b/acb-pluginserver/ps-service/src/main/proto/pluginserver.proto
@@ -100,6 +100,12 @@ message CallBBCRequest {
QueryValidatedBlockStateRequest queryValidatedBlockStateRequest = 28;
RecvOffChainExceptionRequest recvOffChainExceptionRequest = 29;
ReliableRetryRequest reliableRetryRequest = 30;
+ SetupMonitorMessageContractRequest setupMonitorMessageContractReq=31;
+ SetMonitorContractRequest setMonitorContractReq = 32;
+ SetProtocolInMonitorRequest setProtocolInMonitorReq = 33;
+ SetMonitorControlRequest setMonitorControlReq = 34;
+ SetPtcHubInMonitorVerifierRequest setPtcHubInMonitorVerifierReq = 35;
+ RelayMonitorOrderRequest relayMonitorOrderReq =36;
}
}
@@ -123,6 +129,26 @@ message SetupSDPMessageContractRequest {
// stay empty body for now, maybe fill some stuff in future
}
+message SetupMonitorMessageContractRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetMonitorContractRequest {
+ string contractAddress = 1;
+}
+
+message SetProtocolInMonitorRequest {
+ string protocolAddress = 1;
+}
+
+message SetMonitorControlRequest {
+ uint32 monitorType = 1;
+}
+
+message SetPtcHubInMonitorVerifierRequest {
+ string contractAddress = 1;
+}
+
message SetProtocolRequest {
string protocolAddress = 1;
string protocolType = 2;
@@ -223,6 +249,13 @@ message ReliableRetryRequest {
bytes reliableCrossChainMessageData = 1;
}
+message RelayMonitorOrderRequest {
+ string committeeId = 1;
+ string signAlgo = 2;
+ bytes rawProof = 3;
+ bytes rawMonitorOrder = 4;
+}
+
// basic messages.
// same as project `antchain-bridge-commons`
message CrossChainMessageReceipt {
@@ -277,6 +310,8 @@ message CallBBCResponse {
QueryValidatedBlockStateResponse queryValidatedBlockStateResponse = 18;
RecvOffChainExceptionResponse recvOffChainExceptionResponse = 19;
ReliableRetryResponse reliableRetryResponse = 20;
+ SetupMonitorMessageContractResponse setupMonitorResp = 21;
+ RelayMonitorOrderResponse relayMonitorOrderResp = 22;
}
}
@@ -309,6 +344,16 @@ message SetupSDPMessageContractResponse {
SDPMessageContract sdpContract = 1;
}
+message MonitorMessageContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+
+message SetupMonitorMessageContractResponse {
+ MonitorMessageContract monitorContract = 1;
+}
+
message PtcContract {
string contractAddress = 1;
ContractStatusEnum status = 2;
@@ -380,4 +425,8 @@ message RecvOffChainExceptionResponse {
message ReliableRetryResponse {
CrossChainMessageReceipt receipt = 1;
+}
+
+message RelayMonitorOrderResponse {
+ CrossChainMessageReceipt receipt = 1;
}
\ No newline at end of file
diff --git a/acb-relayer/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java b/acb-relayer/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java
index d8017b6b..b5067fd6 100644
--- a/acb-relayer/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java
+++ b/acb-relayer/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java
@@ -71,6 +71,7 @@ public static BlockchainProperties decode(byte[] rawData) {
"am_client_contract_address",
"sdp_msg_contract_address",
"ptc_contract_address",
+ "monitor_contract_address",
"anchor_runtime_status",
"init_block_height",
"is_domain_registered",
@@ -89,6 +90,9 @@ public static BlockchainProperties decode(byte[] rawData) {
@JSONField(name = "ptc_contract_address")
private String ptcContractAddress;
+ @JSONField(name = "monitor_contract_address")
+ private String monitorContractAddress;
+
@JSONField(name = "anchor_runtime_status")
private BlockchainStateEnum anchorRuntimeStatus;
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IMonitorClientContract.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IMonitorClientContract.java
new file mode 100755
index 00000000..92b48418
--- /dev/null
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IMonitorClientContract.java
@@ -0,0 +1,13 @@
+package com.alipay.antchain.bridge.relayer.core.manager.bbc;
+
+public interface IMonitorClientContract {
+
+ void setProtocolInMonitor(String protocolContract);
+
+ void setMonitorControl(int monitorType);
+
+ void setPtcHubInMonitorVerifier(String contractAddress);
+
+ void deployContract();
+
+}
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java
index 670a234a..823f2628 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java
@@ -18,6 +18,8 @@ public interface ISDPMsgClientContract {
*/
void setAmContract(String amContract);
+ void setMonitorContract(String monitorContract);
+
/**
* Query the sequence number of the cross-chain direction
*
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/MonitorClientContractHeteroBlockchainImpl.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/MonitorClientContractHeteroBlockchainImpl.java
new file mode 100755
index 00000000..ffd7be2f
--- /dev/null
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/MonitorClientContractHeteroBlockchainImpl.java
@@ -0,0 +1,34 @@
+package com.alipay.antchain.bridge.relayer.core.manager.bbc;
+
+import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MonitorClientContractHeteroBlockchainImpl implements IMonitorClientContract {
+ private IBBCServiceClient bbcServiceClient;
+
+ public MonitorClientContractHeteroBlockchainImpl(IBBCServiceClient bbcServiceClient) {
+ this.bbcServiceClient = bbcServiceClient;
+ }
+
+ @Override
+ public void setProtocolInMonitor(String protocolContract) {
+ this.bbcServiceClient.setProtocolInMonitor(protocolContract);
+ }
+
+ @Override
+ public void setMonitorControl(int monitorType) {
+ this.bbcServiceClient.setMonitorControl(monitorType);
+ }
+
+ @Override
+ public void setPtcHubInMonitorVerifier(String contractAddress) {
+ this.bbcServiceClient.setPtcHubInMonitorVerifier(contractAddress);
+ }
+
+ @Override
+ public void deployContract() {
+ this.bbcServiceClient.setupMonitorContract();
+ }
+}
+
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java
index 246a111d..b249c7ce 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java
@@ -23,6 +23,11 @@ public void setAmContract(String amContract) {
this.bbcServiceClient.setLocalDomain(bbcServiceClient.getDomain());
}
+ @Override
+ public void setMonitorContract(String monitorContract) {
+ this.bbcServiceClient.setMonitorContract(monitorContract);
+ }
+
@Override
public long querySDPMsgSeqOnChain(String senderDomain, String from, String receiverDomain, String to) {
try {
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java
index bee47c21..2d9844d2 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java
@@ -89,6 +89,10 @@ public class BlockchainManager implements IBlockchainManager {
private final Map latestVerifyAnchorVersionOnchain = new ConcurrentHashMap<>();
+ private static final int MONITOR_CLOSE = 1;
+
+ private static final int MONITOR_OPEN = 2;
+
@Override
public void addBlockchain(
String product,
@@ -926,6 +930,13 @@ private void deployHeteroBlockchainAMContract(BlockchainMeta blockchainMeta) {
blockchainClient.getSDPMsgClientContract().deployContract();
}
+ if (
+ ObjectUtil.isNull(bbcContext.getMonitorContract())
+ || ContractStatusEnum.INIT == bbcContext.getMonitorContract().getStatus()
+ ) {
+ blockchainClient.getMonitorClientContract().deployContract();
+ }
+
boolean isPtcSupport = true;
if (
ObjectUtil.isNull(bbcContext.getPtcContract())
@@ -954,6 +965,13 @@ private void deployHeteroBlockchainAMContract(BlockchainMeta blockchainMeta) {
blockchainMeta.getProduct(), blockchainMeta.getBlockchainId(), blockchainMeta.getPluginServerId()
);
}
+ if (ObjectUtil.isNull(bbcContext.getMonitorContract()) || StrUtil.isEmpty(bbcContext.getMonitorContract().getContractAddress())) {
+ throw new AntChainBridgeRelayerException(
+ RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR,
+ "monitor contract is empty for blockchain ( product: {}, bcId: {}, pluginServer: {})",
+ blockchainMeta.getProduct(), blockchainMeta.getBlockchainId(), blockchainMeta.getPluginServerId()
+ );
+ }
if (isPtcSupport && (ObjectUtil.isNull(bbcContext.getPtcContract()) || StrUtil.isEmpty(bbcContext.getPtcContract().getContractAddress()))) {
throw new AntChainBridgeRelayerException(
RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR,
@@ -974,10 +992,25 @@ private void deployHeteroBlockchainAMContract(BlockchainMeta blockchainMeta) {
blockchainClient.getSDPMsgClientContract()
.setAmContract(bbcContext.getAuthMessageContract().getContractAddress());
+ blockchainClient.getSDPMsgClientContract()
+ .setMonitorContract(bbcContext.getMonitorContract().getContractAddress());
+
+ blockchainClient.getMonitorClientContract()
+ .setMonitorControl(MONITOR_OPEN);
+
+ blockchainClient.getMonitorClientContract()
+ .setProtocolInMonitor(bbcContext.getSdpContract().getContractAddress());
+
+ if (isPtcSupport) {
+ blockchainClient.getMonitorClientContract()
+ .setPtcHubInMonitorVerifier(bbcContext.getPtcContract().getContractAddress());
+ }
+
bbcContext = blockchainClient.queryBBCContext();
blockchainMeta.getProperties().setAmClientContractAddress(bbcContext.getAuthMessageContract().getContractAddress());
blockchainMeta.getProperties().setSdpMsgContractAddress(bbcContext.getSdpContract().getContractAddress());
+ blockchainMeta.getProperties().setMonitorContractAddress(bbcContext.getMonitorContract().getContractAddress());
if (isPtcSupport) {
blockchainMeta.getProperties().setPtcContractAddress(bbcContext.getPtcContract().getContractAddress());
}
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java
index c8e5119f..6363fd05 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java
@@ -8,6 +8,7 @@
import com.alipay.antchain.bridge.commons.core.rcc.ReliableCrossChainMessage;
import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta;
import com.alipay.antchain.bridge.relayer.core.manager.bbc.IAMClientContract;
+import com.alipay.antchain.bridge.relayer.core.manager.bbc.IMonitorClientContract;
import com.alipay.antchain.bridge.relayer.core.manager.bbc.IPtcContract;
import com.alipay.antchain.bridge.relayer.core.manager.bbc.ISDPMsgClientContract;
import lombok.AllArgsConstructor;
@@ -41,6 +42,8 @@ public abstract class AbstractBlockchainClient {
public abstract ISDPMsgClientContract getSDPMsgClientContract();
+ public abstract IMonitorClientContract getMonitorClientContract();
+
public abstract IPtcContract getPtcContract();
public abstract CrossChainMessageReceipt queryCommittedTxReceipt(String txhash);
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java
index b1b12fa0..ce678b8e 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java
@@ -26,6 +26,8 @@ public class HeteroBlockchainClient extends AbstractBlockchainClient {
private final ISDPMsgClientContract sdpMsgClient;
+ private final IMonitorClientContract monitorClient;
+
private final IPtcContract ptcContract;
public HeteroBlockchainClient(IBBCServiceClient bbcClient, BlockchainMeta blockchainMeta) {
@@ -33,6 +35,7 @@ public HeteroBlockchainClient(IBBCServiceClient bbcClient, BlockchainMeta blockc
this.bbcClient = bbcClient;
this.amClientContract = new AMClientContractHeteroBlockchainImpl(bbcClient);
this.sdpMsgClient = new SDPMsgClientHeteroBlockchainImpl(bbcClient);
+ this.monitorClient = new MonitorClientContractHeteroBlockchainImpl(bbcClient);
this.ptcContract = new PtcContractHeteroBlockchainImpl(bbcClient);
}
@@ -182,6 +185,11 @@ public ISDPMsgClientContract getSDPMsgClientContract() {
return this.sdpMsgClient;
}
+ @Override
+ public IMonitorClientContract getMonitorClientContract() {
+ return this.monitorClient;
+ }
+
@Override
public IPtcContract getPtcContract() {
return this.ptcContract;
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java
index e017caec..69156e99 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java
@@ -7,9 +7,9 @@
/**
- * request complex type的 Java 类。
+ *
Java class for request complex type.
*
- *
以下模式片段指定包含在此类中的预期内容。
+ *
The following schema fragment specifies the expected content contained within this class.
*
*
* <complexType name="request">
@@ -34,7 +34,7 @@ public class Request {
protected String relayerRequest;
/**
- * 获取relayerRequest属性的值。
+ * Gets the value of the relayerRequest property.
*
* @return
* possible object is
@@ -46,7 +46,7 @@ public String getRelayerRequest() {
}
/**
- * 设置relayerRequest属性的值。
+ * Sets the value of the relayerRequest property.
*
* @param value
* allowed object is
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java
index f1e53e0a..7d271b0b 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java
@@ -8,9 +8,9 @@
/**
- * requestResponse complex type的 Java 类。
+ *
Java class for requestResponse complex type.
*
- *
以下模式片段指定包含在此类中的预期内容。
+ *
The following schema fragment specifies the expected content contained within this class.
*
*
* <complexType name="requestResponse">
@@ -36,7 +36,7 @@ public class RequestResponse {
protected String _return;
/**
- * 获取return属性的值。
+ * Gets the value of the return property.
*
* @return
* possible object is
@@ -48,7 +48,7 @@ public String getReturn() {
}
/**
- * 设置return属性的值。
+ * Sets the value of the return property.
*
* @param value
* allowed object is
diff --git a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java
index 83f0d71f..c5da0443 100644
--- a/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java
+++ b/acb-relayer/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java
@@ -210,6 +210,91 @@ public void setupSDPMessageContract() {
}
}
+ @Override
+ public void setupMonitorContract() {
+ Response response = this.blockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(this.getProduct())
+ .setDomain(this.getDomain())
+ .setSetupMonitorMessageContractReq(SetupMonitorMessageContractRequest.getDefaultInstance())
+ .build()
+ );
+ if (response.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setupMonitorMessageContract request failed for plugin server %s: %s",
+ this.domain, this.product, this.psId, response.getErrorMsg())
+ );
+ }
+ }
+
+ @Override
+ public void setMonitorContract(String monitorContract) {
+ Response response = this.blockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(this.getProduct())
+ .setDomain(this.getDomain())
+ .setSetMonitorContractReq(SetMonitorContractRequest.newBuilder().setContractAddress(monitorContract))
+ .build()
+ );
+ if (response.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setMonitorContract request failed for plugin server %s: %s",
+ this.domain, this.product, this.psId, response.getErrorMsg())
+ );
+ }
+ }
+
+ @Override
+ public void setProtocolInMonitor(String protocolContract) {
+ Response response = this.blockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(this.getProduct())
+ .setDomain(this.getDomain())
+ .setSetProtocolInMonitorReq(SetProtocolInMonitorRequest.newBuilder().setProtocolAddress(protocolContract))
+ .build()
+ );
+ if (response.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setProtocolInMonitor request failed for plugin server %s: %s",
+ this.domain, this.product, this.psId, response.getErrorMsg())
+ );
+ }
+ }
+
+ @Override
+ public void setMonitorControl(int monitorType) {
+ Response response = this.blockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(this.getProduct())
+ .setDomain(this.getDomain())
+ .setSetMonitorControlReq(SetMonitorControlRequest.newBuilder().setMonitorType(monitorType))
+ .build()
+ );
+ if (response.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setMonitorControl request failed for plugin server %s: %s",
+ this.domain, this.product, this.psId, response.getErrorMsg())
+ );
+ }
+ }
+
+ @Override
+ public void setPtcHubInMonitorVerifier(String contractAddress) {
+ Response response = this.blockingStub.bbcCall(
+ CallBBCRequest.newBuilder()
+ .setProduct(this.getProduct())
+ .setDomain(this.getDomain())
+ .setSetPtcHubInMonitorVerifierReq(SetPtcHubInMonitorVerifierRequest.newBuilder().setContractAddress(contractAddress))
+ .build()
+ );
+ if (response.getCode() != 0) {
+ throw new RuntimeException(
+ String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setPtcHubInMonitorVerifier request failed for plugin server %s: %s",
+ this.domain, this.product, this.psId, response.getErrorMsg())
+ );
+ }
+ }
+
@Override
public void setProtocol(String protocolAddress, String protocolType) {
Response response = this.blockingStub.bbcCall(
@@ -270,6 +355,11 @@ public CrossChainMessageReceipt relayAuthMessage(byte[] rawMessage) {
return PluginServerUtils.convertFromGRpcCrossChainMessageReceipt(response.getBbcResp().getRelayAuthMessageResponse().getReceipt());
}
+ @Override
+ public CrossChainMessageReceipt relayMonitorOrder(String committeeId, String signAlgo, byte[] rawProof, byte[] rawMonitorOrder) {
+ return new CrossChainMessageReceipt();
+ }
+
@Override
public void setAmContract(String contractAddress) {
Response response = this.blockingStub.bbcCall(
diff --git a/acb-relayer/r-core/src/main/proto/pluginserver.proto b/acb-relayer/r-core/src/main/proto/pluginserver.proto
index 706f88c5..a7f48eda 100644
--- a/acb-relayer/r-core/src/main/proto/pluginserver.proto
+++ b/acb-relayer/r-core/src/main/proto/pluginserver.proto
@@ -100,6 +100,12 @@ message CallBBCRequest {
QueryValidatedBlockStateRequest queryValidatedBlockStateRequest = 28;
RecvOffChainExceptionRequest recvOffChainExceptionRequest = 29;
ReliableRetryRequest reliableRetryRequest = 30;
+ SetupMonitorMessageContractRequest setupMonitorMessageContractReq=31;
+ SetMonitorContractRequest setMonitorContractReq = 32;
+ SetProtocolInMonitorRequest setProtocolInMonitorReq = 33;
+ SetMonitorControlRequest setMonitorControlReq = 34;
+ SetPtcHubInMonitorVerifierRequest setPtcHubInMonitorVerifierReq = 35;
+ RelayMonitorOrderRequest relayMonitorOrderReq =36;
}
}
@@ -123,6 +129,26 @@ message SetupSDPMessageContractRequest {
// stay empty body for now, maybe fill some stuff in future
}
+message SetupMonitorMessageContractRequest {
+ // stay empty body for now, maybe fill some stuff in future
+}
+
+message SetMonitorContractRequest {
+ string contractAddress = 1;
+}
+
+message SetProtocolInMonitorRequest {
+ string protocolAddress = 1;
+}
+
+message SetMonitorControlRequest {
+ uint32 monitorType = 1;
+}
+
+message SetPtcHubInMonitorVerifierRequest {
+ string contractAddress = 1;
+}
+
message SetProtocolRequest {
string protocolAddress = 1;
string protocolType = 2;
@@ -223,6 +249,13 @@ message ReliableRetryRequest {
bytes reliableCrossChainMessageData = 1;
}
+message RelayMonitorOrderRequest {
+ string committeeId = 1;
+ string signAlgo = 2;
+ bytes rawProof = 3;
+ bytes rawMonitorOrder = 4;
+}
+
// basic messages.
// same as project `antchain-bridge-commons`
message CrossChainMessageReceipt {
@@ -277,6 +310,8 @@ message CallBBCResponse {
QueryValidatedBlockStateResponse queryValidatedBlockStateResponse = 18;
RecvOffChainExceptionResponse recvOffChainExceptionResponse = 19;
ReliableRetryResponse reliableRetryResponse = 20;
+ SetupMonitorMessageContractResponse setupMonitorResp = 21;
+ RelayMonitorOrderResponse relayMonitorOrderResp = 22;
}
}
@@ -309,6 +344,16 @@ message SetupSDPMessageContractResponse {
SDPMessageContract sdpContract = 1;
}
+message MonitorMessageContract {
+ string contractAddress = 1;
+ ContractStatusEnum status = 2;
+}
+
+
+message SetupMonitorMessageContractResponse {
+ MonitorMessageContract monitorContract = 1;
+}
+
message PtcContract {
string contractAddress = 1;
ContractStatusEnum status = 2;
@@ -380,4 +425,8 @@ message RecvOffChainExceptionResponse {
message ReliableRetryResponse {
CrossChainMessageReceipt receipt = 1;
+}
+
+message RelayMonitorOrderResponse {
+ CrossChainMessageReceipt receipt = 1;
}
\ No newline at end of file
diff --git a/acb-relayer/r-facade/src/main/java/com/alipay/antchain/bridge/relayer/facade/admin/types/SysContractsInfo.java b/acb-relayer/r-facade/src/main/java/com/alipay/antchain/bridge/relayer/facade/admin/types/SysContractsInfo.java
index 4e0b030a..c7d33a80 100644
--- a/acb-relayer/r-facade/src/main/java/com/alipay/antchain/bridge/relayer/facade/admin/types/SysContractsInfo.java
+++ b/acb-relayer/r-facade/src/main/java/com/alipay/antchain/bridge/relayer/facade/admin/types/SysContractsInfo.java
@@ -17,6 +17,9 @@ public class SysContractsInfo {
@JSONField(name = "ptc_contract")
private String ptcContract;
+ @JSONField(name = "monitor_contract")
+ private String monitorContract;
+
@JSONField(name = "state")
private String state;
}
diff --git a/acb-relayer/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/admin/impl/BlockchainNamespace.java b/acb-relayer/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/admin/impl/BlockchainNamespace.java
index c16320b8..ade3f462 100644
--- a/acb-relayer/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/admin/impl/BlockchainNamespace.java
+++ b/acb-relayer/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/admin/impl/BlockchainNamespace.java
@@ -189,6 +189,12 @@ Object getBlockchainContracts(String... args) {
)
);
+ sysContractsInfo.setMonitorContract(
+ ObjectUtil.defaultIfNull(
+ blockchainMeta.getProperties().getMonitorContractAddress(), "empty"
+ )
+ );
+
OnChainServiceStatusEnum amStatus = blockchainMeta.getProperties().getAmServiceStatus();
sysContractsInfo.setState(
ObjectUtil.isNull(amStatus)? "" : amStatus.name()
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/AbstractBBCContext.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/AbstractBBCContext.java
index c2a9d88e..53ebcd4c 100644
--- a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/AbstractBBCContext.java
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/AbstractBBCContext.java
@@ -19,6 +19,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alipay.antchain.bridge.commons.bbc.syscontract.AuthMessageContract;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.MonitorContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.PTCContract;
import lombok.Getter;
@@ -37,6 +38,9 @@ public abstract class AbstractBBCContext implements IBBCContext {
@JSONField(name = "sdp_contract")
private SDPContract sdpContract;
+ @JSONField(name = "monitor_contract")
+ private MonitorContract monitorContract;
+
@JSONField(name = "is_reliable")
private boolean isReliable;
@@ -47,6 +51,7 @@ public abstract class AbstractBBCContext implements IBBCContext {
public void decodeFromBytes(byte[] raw) {
AbstractBBCContext state = JSON.parseObject(raw, this.getClass());
this.setSdpContract(state.getSdpContract());
+ this.setMonitorContract(state.getMonitorContract());
this.setPtcContract(state.getPtcContract());
this.setAuthMessageContract(state.getAuthMessageContract());
this.setReliable(state.isReliable());
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/syscontract/MonitorContract.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/syscontract/MonitorContract.java
new file mode 100755
index 00000000..6934c5f5
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/bbc/syscontract/MonitorContract.java
@@ -0,0 +1,13 @@
+package com.alipay.antchain.bridge.commons.bbc.syscontract;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MonitorContract {
+
+ private String contractAddress;
+
+ private ContractStatusEnum status;
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorMessage.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorMessage.java
new file mode 100755
index 00000000..4c00c90b
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorMessage.java
@@ -0,0 +1,25 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Getter
+@Setter
+public abstract class AbstractMonitorMessage implements IMonitorMessage {
+
+ public static int MONITOR_CLOSE = 1;
+
+ public static int MONITOR_OPEN = 2;
+
+ public static int MONITOR_ROLLBACK = 3;
+
+ public static int MONITOR_ORDER = 4;
+
+ private int monitorType;
+
+ private String monitorMsg;
+
+ private byte[] payload;
+
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorOrder.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorOrder.java
new file mode 100644
index 00000000..b31d85cf
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/AbstractMonitorOrder.java
@@ -0,0 +1,74 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import cn.hutool.core.util.HexUtil;
+import com.alipay.antchain.bridge.commons.exception.AntChainBridgeCommonsException;
+import com.alipay.antchain.bridge.commons.exception.CommonsErrorCodeEnum;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public abstract class AbstractMonitorOrder implements IMonitorOrder {
+
+ public static final int IDENTITY_LENGTH = 32;
+
+ private String product;
+
+ private String domain;
+
+ private long monitorOrderType;
+
+ private String senderDomain;
+
+ private String fromAddress;
+
+ private String receiverDomain;
+
+ private String toAddress;
+
+ private String transactionContent;
+
+ private String extra;
+
+ public byte[] getRawFromAddress() {
+ byte[] rawID = HexUtil.decodeHex(this.fromAddress);
+ if (rawID.length != IDENTITY_LENGTH) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.CROSS_CHAIN_IDENTITY_DECODE_ERROR,
+ String.format("expected id with length %d but got %d", IDENTITY_LENGTH, rawID.length)
+ );
+ }
+ return rawID;
+ }
+
+ public byte[] getRawToAddress() {
+ byte[] rawID = HexUtil.decodeHex(this.toAddress);
+ if (rawID.length != IDENTITY_LENGTH) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.CROSS_CHAIN_IDENTITY_DECODE_ERROR,
+ String.format("expected id with length %d but got %d", IDENTITY_LENGTH, rawID.length)
+ );
+ }
+ return rawID;
+ }
+
+ public void setFromAddressFromBytes(byte[] fromAddress) {
+ if (fromAddress.length != IDENTITY_LENGTH) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.CROSS_CHAIN_IDENTITY_DECODE_ERROR,
+ String.format("expected id with length %d but got %d", IDENTITY_LENGTH, fromAddress.length)
+ );
+ }
+ this.fromAddress = HexUtil.encodeHexStr(fromAddress);
+ }
+
+ public void setToAddressFromBytes(byte[] toAddress) {
+ if (toAddress.length != IDENTITY_LENGTH) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.CROSS_CHAIN_IDENTITY_DECODE_ERROR,
+ String.format("expected id with length %d but got %d", IDENTITY_LENGTH, toAddress.length)
+ );
+ }
+ this.toAddress = HexUtil.encodeHexStr(toAddress);
+ }
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorMessage.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorMessage.java
new file mode 100755
index 00000000..abff5991
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorMessage.java
@@ -0,0 +1,12 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import com.alipay.antchain.bridge.commons.core.base.IMessage;
+
+public interface IMonitorMessage extends IMessage {
+
+ int getMonitorType();
+
+ String getMonitorMsg();
+
+ byte[] getPayload();
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorOrder.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorOrder.java
new file mode 100644
index 00000000..2483ef0d
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/IMonitorOrder.java
@@ -0,0 +1,25 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import com.alipay.antchain.bridge.commons.core.base.IMessage;
+
+public interface IMonitorOrder extends IMessage {
+
+ String getProduct();
+
+ String getDomain();
+
+ long getMonitorOrderType();
+
+ String getSenderDomain();
+
+ String getFromAddress();
+
+ String getReceiverDomain();
+
+ String getToAddress();
+
+ String getTransactionContent();
+
+ String getExtra();
+
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageFactory.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageFactory.java
new file mode 100755
index 00000000..3451e7cc
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageFactory.java
@@ -0,0 +1,39 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import com.alipay.antchain.bridge.commons.exception.AntChainBridgeCommonsException;
+import com.alipay.antchain.bridge.commons.exception.CommonsErrorCodeEnum;
+
+public class MonitorMessageFactory {
+
+ public static IMonitorMessage createMonitorMessage(byte[] rawMessage) {
+ IMonitorMessage monitorMessage = createAbstractMonitorMessage(MonitorMessageV1.MY_VERSION);
+ monitorMessage.decode(rawMessage);
+ return monitorMessage;
+ }
+
+ public static IMonitorMessage createMonitorMessage(int version, int monitorType, String monitorMsg, byte[] payload) {
+ AbstractMonitorMessage monitorMessage = createAbstractMonitorMessage(version);
+
+ monitorMessage.setMonitorType(monitorType);
+ monitorMessage.setMonitorMsg(monitorMsg);
+ monitorMessage.setPayload(payload);
+
+ return monitorMessage;
+ }
+
+ public static AbstractMonitorMessage createAbstractMonitorMessage(int version) {
+ AbstractMonitorMessage monitorMessage;
+ switch (version) {
+ case MonitorMessageV1.MY_VERSION:
+ monitorMessage = new MonitorMessageV1();
+ break;
+ default:
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.INCORRECT_MONITOR_MESSAGE_ERROR,
+ String.format("wrong version: %d", version)
+ );
+ }
+ return monitorMessage;
+ }
+
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageV1.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageV1.java
new file mode 100755
index 00000000..dfdfde20
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorMessageV1.java
@@ -0,0 +1,109 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import cn.hutool.core.util.ByteUtil;
+import com.alipay.antchain.bridge.commons.exception.AntChainBridgeCommonsException;
+import com.alipay.antchain.bridge.commons.exception.CommonsErrorCodeEnum;
+
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+public class MonitorMessageV1 extends AbstractMonitorMessage {
+
+ public static final int MY_VERSION = 1;
+
+ @Override
+ public void decode(byte[] rawMessage) {
+ int offset = rawMessage.length;
+
+ offset = extractMonitorType(rawMessage, offset);
+ offset = extractMonitorMsg(rawMessage, offset);
+ extractPayload(rawMessage, offset);
+ }
+
+ public int extractMonitorType(byte[] rawMessage, int offset) {
+ offset -= 4;
+ byte[] rawSeq = new byte[4];
+ System.arraycopy(rawMessage, offset, rawSeq, 0, 4);
+ this.setMonitorType(ByteUtil.bytesToInt(rawSeq, ByteOrder.BIG_ENDIAN));
+
+ return offset;
+ }
+
+ public int extractMonitorMsg(byte[] rawMessage, int offset) {
+ offset -= 4;
+ byte[] rawMonitorMsgLen = new byte[4];
+ System.arraycopy(rawMessage, offset, rawMonitorMsgLen, 0, 4);
+
+ byte[] monitorMsg = new byte[ByteUtil.bytesToInt(rawMonitorMsgLen, ByteOrder.BIG_ENDIAN)];
+ offset -= monitorMsg.length;
+ if (offset < 0) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.MONITOR_MESSAGE_DECODE_ERROR,
+ "length of error message in MonitorV1 is incorrect"
+ );
+ }
+ System.arraycopy(rawMessage, offset, monitorMsg, 0, monitorMsg.length);
+ this.setMonitorMsg(new String(monitorMsg));
+
+ return offset;
+ }
+
+ public int extractPayload(byte[] rawMessage, int offset) {
+ offset -= 4;
+ byte[] rawPayloadLen = new byte[4];
+ System.arraycopy(rawMessage, offset, rawPayloadLen, 0, 4);
+
+ byte[] payload = new byte[ByteUtil.bytesToInt(rawPayloadLen, ByteOrder.BIG_ENDIAN)];
+ offset -= payload.length;
+ if (offset < 0) {
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.MONITOR_MESSAGE_DECODE_ERROR,
+ "wrong payload or length of payload in MonitorV1"
+ );
+ }
+ System.arraycopy(rawMessage, offset, payload, 0, payload.length);
+ this.setPayload(payload);
+
+ return offset;
+ }
+
+ @Override
+ public byte[] encode() {
+ int rawMessageLen = 12 + this.getMonitorMsg().getBytes(StandardCharsets.UTF_8).length + this.getPayload().length;
+ byte[] rawMessage = new byte[rawMessageLen];
+
+ int offset = putMonitorType(rawMessage, rawMessageLen);
+ offset = putMonitorMsg(rawMessage, offset);
+ putPayload(rawMessage, offset);
+
+ return rawMessage;
+ }
+
+ private int putMonitorType(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getMonitorType(), ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ return offset;
+ }
+
+ private int putMonitorMsg(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getMonitorMsg().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getMonitorMsg().length();
+ System.arraycopy(this.getMonitorMsg().getBytes(), 0, rawMessage, offset, this.getMonitorMsg().length());
+
+ return offset;
+ }
+
+ private int putPayload(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getPayload().length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getPayload().length;
+ System.arraycopy(this.getPayload(), 0, rawMessage, offset, this.getPayload().length);
+
+ return offset;
+ }
+
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderFactory.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderFactory.java
new file mode 100644
index 00000000..913874d8
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderFactory.java
@@ -0,0 +1,41 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import com.alipay.antchain.bridge.commons.exception.AntChainBridgeCommonsException;
+import com.alipay.antchain.bridge.commons.exception.CommonsErrorCodeEnum;
+
+public class MonitorOrderFactory {
+
+ public static IMonitorOrder createMonitorOrder(int version, String product, String domain, long monitorOrderType,
+ String senderDomain, String fromAddress,
+ String receiverDomain, String toAddress,
+ String transactionContent, String extra) {
+ AbstractMonitorOrder monitorOrder = createAbstractMonitorOrder(version);
+
+ monitorOrder.setProduct(product);
+ monitorOrder.setDomain(domain);
+ monitorOrder.setMonitorOrderType(monitorOrderType);
+ monitorOrder.setSenderDomain(senderDomain);
+ monitorOrder.setFromAddress(fromAddress);
+ monitorOrder.setReceiverDomain(receiverDomain);
+ monitorOrder.setToAddress(toAddress);
+ monitorOrder.setTransactionContent(transactionContent);
+ monitorOrder.setExtra(extra);
+
+ return monitorOrder;
+ }
+
+ private static AbstractMonitorOrder createAbstractMonitorOrder(int version) {
+ AbstractMonitorOrder monitorOrder;
+ switch (version) {
+ case MonitorOrderV1.MY_VERSION:
+ monitorOrder = new MonitorOrderV1();
+ break;
+ default:
+ throw new AntChainBridgeCommonsException(
+ CommonsErrorCodeEnum.INCORRECT_MONITOR_ORDER,
+ String.format("wrong version: %d", version)
+ );
+ }
+ return monitorOrder;
+ }
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderV1.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderV1.java
new file mode 100644
index 00000000..f07fc509
--- /dev/null
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/core/monitor/MonitorOrderV1.java
@@ -0,0 +1,123 @@
+package com.alipay.antchain.bridge.commons.core.monitor;
+
+import cn.hutool.core.util.ByteUtil;
+
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+public class MonitorOrderV1 extends AbstractMonitorOrder {
+
+ public static final int MY_VERSION = 1;
+
+ // no need to do this
+ @Override
+ public void decode(byte[] rawMessage) { }
+
+ @Override
+ public byte[] encode() {
+ // monitorOrderType only needs 4 bytes
+ int rawMessageLen = 28 + this.getProduct().getBytes(StandardCharsets.UTF_8).length +
+ this.getDomain().getBytes(StandardCharsets.UTF_8).length +
+ this.getSenderDomain().getBytes(StandardCharsets.UTF_8).length +
+ this.getRawFromAddress().length +
+ this.getReceiverDomain().getBytes(StandardCharsets.UTF_8).length +
+ this.getRawToAddress().length +
+ this.getTransactionContent().getBytes(StandardCharsets.UTF_8).length +
+ this.getExtra().getBytes(StandardCharsets.UTF_8).length;
+ byte[] rawMessage = new byte[rawMessageLen];
+
+ int offset = putProduct(rawMessage, rawMessageLen);
+ offset = putDomain(rawMessage, offset);
+ offset = putMonitorOrderType(rawMessage, offset);
+ offset = putSenderDomain(rawMessage, offset);
+ offset = putRawFromAddress(rawMessage, offset);
+ offset = putReceiverDomain(rawMessage, offset);
+ offset = putRawToAddress(rawMessage, offset);
+ offset = putTransactionContent(rawMessage, offset);
+ putExtra(rawMessage, offset);
+
+ return rawMessage;
+ }
+
+ private int putProduct(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getProduct().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getProduct().length();
+ System.arraycopy(this.getProduct().getBytes(), 0, rawMessage, offset, this.getProduct().length());
+
+ return offset;
+ }
+
+ private int putDomain(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getDomain().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getDomain().length();
+ System.arraycopy(this.getDomain().getBytes(), 0, rawMessage, offset, this.getDomain().length());
+
+ return offset;
+ }
+
+ private int putMonitorOrderType(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes((int)this.getMonitorOrderType(), ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ return offset;
+ }
+
+ private int putSenderDomain(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getSenderDomain().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getSenderDomain().length();
+ System.arraycopy(this.getSenderDomain().getBytes(), 0, rawMessage, offset, this.getSenderDomain().length());
+
+ return offset;
+ }
+
+ private int putRawFromAddress(byte[] rawMessage, int offset) {
+ offset -= 32;
+ System.arraycopy(this.getRawFromAddress(), 0, rawMessage, offset, 32);
+
+ return offset;
+ }
+
+ private int putReceiverDomain(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getReceiverDomain().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getReceiverDomain().length();
+ System.arraycopy(this.getReceiverDomain().getBytes(), 0, rawMessage, offset, this.getReceiverDomain().length());
+
+ return offset;
+ }
+
+ private int putRawToAddress(byte[] rawMessage, int offset) {
+ offset -= 32;
+ System.arraycopy(this.getRawToAddress(), 0, rawMessage, offset, 32);
+
+ return offset;
+ }
+
+ private int putTransactionContent(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getTransactionContent().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getTransactionContent().length();
+ System.arraycopy(this.getTransactionContent().getBytes(), 0, rawMessage, offset, this.getTransactionContent().length());
+
+ return offset;
+ }
+
+ private int putExtra(byte[] rawMessage, int offset) {
+ offset -= 4;
+ System.arraycopy(ByteUtil.intToBytes(this.getExtra().getBytes(StandardCharsets.UTF_8).length, ByteOrder.BIG_ENDIAN), 0, rawMessage, offset, 4);
+
+ offset -= this.getExtra().length();
+ System.arraycopy(this.getExtra().getBytes(), 0, rawMessage, offset, this.getExtra().length());
+
+ return offset;
+ }
+
+}
diff --git a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/exception/CommonsErrorCodeEnum.java b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/exception/CommonsErrorCodeEnum.java
index 19690cd3..1a0fdc4c 100644
--- a/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/exception/CommonsErrorCodeEnum.java
+++ b/acb-sdk/antchain-bridge-commons/src/main/java/com/alipay/antchain/bridge/commons/exception/CommonsErrorCodeEnum.java
@@ -140,7 +140,22 @@ public enum CommonsErrorCodeEnum {
/**
*
*/
- BCDNS_BID_PUBLIC_KEY_ALGO_NOT_SUPPORT("0706", "BID pubkey algo not support");
+ BCDNS_BID_PUBLIC_KEY_ALGO_NOT_SUPPORT("0706", "BID pubkey algo not support"),
+
+ /**
+ * Something wrong about {@code MonitorMessage}, like version, etc.
+ */
+ INCORRECT_MONITOR_MESSAGE_ERROR("0801", "wrong monitor"),
+
+ /**
+ * Code shows where decode {@code MonitorMessage} failed
+ */
+ MONITOR_MESSAGE_DECODE_ERROR("0802", "monitor decode failed"),
+
+ /**
+ * Something wrong about {@code MonitorOrder}, like version, etc.
+ */
+ INCORRECT_MONITOR_ORDER("0803", "wrong monitor order");
/**
* Error code for errors happened in project {@code antchain-bridge-commons}
diff --git a/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IAntChainBridgeDataWriter.java b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IAntChainBridgeDataWriter.java
index 77a4196b..c4af44ce 100644
--- a/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IAntChainBridgeDataWriter.java
+++ b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IAntChainBridgeDataWriter.java
@@ -40,7 +40,7 @@
* interfaces like {@link IAMWriter}, {@link ISDPWriter}.
*
*/
-public interface IAntChainBridgeDataWriter extends IAMWriter, ISDPWriter {
+public interface IAntChainBridgeDataWriter extends IAMWriter, ISDPWriter, IMonitorWriter {
/**
* Set up the AuthMessage contract.
@@ -65,6 +65,10 @@ public interface IAntChainBridgeDataWriter extends IAMWriter, ISDPWriter {
*/
void setupSDPMessageContract();
+ /**
+ * Set up the monitor contracts, including monitor and monitorVerifier contract
+ */
+ void setupMonitorContract();
/**
* Set up the PTC contracts. For example PTCHub and its verify contracts
*/
diff --git a/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IMonitorWriter.java b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IMonitorWriter.java
new file mode 100755
index 00000000..7d011b87
--- /dev/null
+++ b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/IMonitorWriter.java
@@ -0,0 +1,18 @@
+package com.alipay.antchain.bridge.plugins.spi.bbc.core.write;
+
+import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt;
+
+/**
+ * Through {@code IMonitorWriter}, you can write data
+ * to the storage of the MonitorContract.
+ */
+public interface IMonitorWriter {
+
+ void setProtocolInMonitor(String contractAddress);
+
+ void setMonitorControl(int monitorType);
+
+ void setPtcHubInMonitorVerifier(String contractAddress);
+
+ CrossChainMessageReceipt relayMonitorOrder(String committeeId, String signAlgo, byte[] rawProof, byte[] rawMonitorOrder);
+}
\ No newline at end of file
diff --git a/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/ISDPWriter.java b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/ISDPWriter.java
index 798c54a7..534bba44 100644
--- a/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/ISDPWriter.java
+++ b/acb-sdk/antchain-bridge-spi/src/main/java/com/alipay/antchain/bridge/plugins/spi/bbc/core/write/ISDPWriter.java
@@ -41,4 +41,6 @@ public interface ISDPWriter {
* @param domain the domain value
*/
void setLocalDomain(String domain);
+
+ void setMonitorContract(String contractAddress);
}
diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml b/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml
index 13847e02..1a7c0aa8 100644
--- a/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml
+++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/pom.xml
@@ -236,10 +236,12 @@
+ Monitor
AuthMsg
SDPMsg
PtcHub
CommitteePtcVerifier
+ MonitorVerifier
AppContract
ProxyAdmin
TransparentUpgradeableProxy
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..92991dfa 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
@@ -26,10 +26,7 @@
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext;
-import com.alipay.antchain.bridge.commons.bbc.syscontract.AuthMessageContract;
-import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum;
-import com.alipay.antchain.bridge.commons.bbc.syscontract.PTCContract;
-import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.*;
import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum;
import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil;
@@ -102,6 +99,7 @@ public void startup(AbstractBBCContext abstractBBCContext) {
authMessageContract.setContractAddress(this.config.getAmContractAddressDeployed());
authMessageContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
this.bbcContext.setAuthMessageContract(authMessageContract);
+ getBBCLogger().info("set the pre-deployed am contracts into context: {}: ", this.config.getAmContractAddressDeployed());
}
if (ObjectUtil.isNull(abstractBBCContext.getSdpContract())
@@ -110,6 +108,16 @@ public void startup(AbstractBBCContext abstractBBCContext) {
sdpContract.setContractAddress(this.config.getSdpContractAddressDeployed());
sdpContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
this.bbcContext.setSdpContract(sdpContract);
+ getBBCLogger().info("set the pre-deployed sdp contracts into context: {}: ", this.config.getSdpContractAddressDeployed());
+ }
+
+ if (ObjectUtil.isNull(abstractBBCContext.getMonitorContract())
+ && StrUtil.isNotEmpty(this.config.getMonitorContractAddressDeployed())) {
+ MonitorContract monitorContract = new MonitorContract();
+ monitorContract.setContractAddress(this.config.getMonitorContractAddressDeployed());
+ monitorContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ this.bbcContext.setMonitorContract(monitorContract);
+ getBBCLogger().info("set the pre-deployed monitor contracts into context: {}: ", this.config.getMonitorContractAddressDeployed());
}
if (ObjectUtil.isNull(abstractBBCContext.getPtcContract())
@@ -118,6 +126,7 @@ public void startup(AbstractBBCContext abstractBBCContext) {
ptcContract.setContractAddress(this.config.getPtcHubContractAddressDeployed());
ptcContract.setStatus(ContractStatusEnum.CONTRACT_READY);
this.bbcContext.setPtcContract(ptcContract);
+ getBBCLogger().info("set the pre-deployed ptc contracts into context: {}: ", this.config.getPtcHubContractAddressDeployed());
}
if (ObjectUtil.isEmpty(this.config.getProxyAdmin()) && this.config.isUpgradableContracts()) {
@@ -140,11 +149,13 @@ public AbstractBBCContext getContext() {
throw new RuntimeException("empty bbc context");
}
- getBBCLogger().debug("ETH BBCService context (amAddr: {}, amStatus: {}, sdpAddr: {}, sdpStatus: {})",
+ getBBCLogger().debug("ETH BBCService context (amAddr: {}, amStatus: {}, sdpAddr: {}, sdpStatus: {}, monitorAddr: {}, monitorStatus: {})",
this.bbcContext.getAuthMessageContract() != null ? this.bbcContext.getAuthMessageContract().getContractAddress() : "",
this.bbcContext.getAuthMessageContract() != null ? this.bbcContext.getAuthMessageContract().getStatus() : "",
this.bbcContext.getSdpContract() != null ? this.bbcContext.getSdpContract().getContractAddress() : "",
- this.bbcContext.getSdpContract() != null ? this.bbcContext.getSdpContract().getStatus() : ""
+ this.bbcContext.getSdpContract() != null ? this.bbcContext.getSdpContract().getStatus() : "",
+ this.bbcContext.getMonitorContract() != null ? this.bbcContext.getMonitorContract().getContractAddress() : "",
+ this.bbcContext.getMonitorContract() != null ? this.bbcContext.getMonitorContract().getStatus() : ""
);
this.bbcContext.setConfForBlockchainClient(this.config.toJsonString().getBytes());
@@ -293,6 +304,32 @@ public void setupSDPMessageContract() {
getBBCLogger().info("setup sdp contract successful: {}", sdpContractAddr);
}
+ @Override
+ public void setupMonitorContract() {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (this.config.isUpgradableContracts() && StrUtil.isEmpty(this.config.getProxyAdmin())) {
+ throw new RuntimeException("empty proxy admin");
+ }
+ if (ObjectUtil.isNotNull(this.bbcContext.getMonitorContract())
+ && StrUtil.isNotEmpty(this.bbcContext.getMonitorContract().getContractAddress())) {
+ // If the contract has been pre-deployed and the contract address is configured in the configuration file,
+ // there is no need to redeploy.
+ return;
+ }
+
+ // 2. deploy contract
+ var monitorContractAddr = acbEthClient.deployMonitorContract(acbEthClient.deployMonitorVerifierContract());
+
+ MonitorContract monitorContract = new MonitorContract();
+ monitorContract.setContractAddress(monitorContractAddr);
+ monitorContract.setStatus(ContractStatusEnum.CONTRACT_DEPLOYED);
+ bbcContext.setMonitorContract(monitorContract);
+ getBBCLogger().info("setup monitor contract successful: {}", monitorContractAddr);
+ }
+
@Override
public long querySDPMessageSeq(String senderDomain, String senderID, String receiverDomain, String receiverID) {
// 1. check context
@@ -347,7 +384,38 @@ public void setAmContract(String contractAddress) {
// 4. update sdp contract status
try {
if (!StrUtil.isEmpty(acbEthClient.getAmContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
- && !isByteArrayZero(acbEthClient.getLocalDomainFromSdp(this.bbcContext.getSdpContract().getContractAddress()))) {
+ && !isByteArrayZero(acbEthClient.getLocalDomainFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ && !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getMonitorContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ ) {
+ this.bbcContext.getSdpContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update sdp contract status (address: %s)",
+ this.bbcContext.getSdpContract().getContractAddress()
+ ), e);
+ }
+ }
+
+ @Override
+ public void setMonitorContract(String contractAddress) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getSdpContract())) {
+ throw new RuntimeException("empty sdp contract in bbc context");
+ }
+
+ acbEthClient.setMonitorContractToSdp(this.bbcContext.getSdpContract().getContractAddress(), contractAddress);
+
+ // 4. update sdp contract status
+ try {
+ if (!StrUtil.isEmpty(acbEthClient.getAmContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ && !isByteArrayZero(acbEthClient.getLocalDomainFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ && !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getMonitorContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ ) {
this.bbcContext.getSdpContract().setStatus(ContractStatusEnum.CONTRACT_READY);
}
} catch (Exception e) {
@@ -374,7 +442,9 @@ public void setLocalDomain(String domain) {
// 4. update sdp contract status
try {
if (!StrUtil.isEmpty(acbEthClient.getAmContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
- && !isByteArrayZero(acbEthClient.getLocalDomainFromSdp(this.bbcContext.getSdpContract().getContractAddress()))) {
+ && !isByteArrayZero(acbEthClient.getLocalDomainFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ && !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getMonitorContractFromSdp(this.bbcContext.getSdpContract().getContractAddress()))
+ ) {
this.bbcContext.getSdpContract().setStatus(ContractStatusEnum.CONTRACT_READY);
}
} catch (Exception e) {
@@ -386,6 +456,120 @@ public void setLocalDomain(String domain) {
}
}
+ @Override
+ public void setProtocolInMonitor(String protocolAddress) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getMonitorContract())) {
+ throw new RuntimeException("empty monitor contract in bbc context");
+ }
+
+ acbEthClient.setProtocolToMonitor(this.bbcContext.getMonitorContract().getContractAddress(), protocolAddress);
+
+ // 4. update monitor contract status
+ try {
+ String monitorVerifier = acbEthClient.getMonitorVerifierFromMonitor(this.bbcContext.getMonitorContract().getContractAddress());
+ if (!"0x0000000000000000000000000000000000000000".equalsIgnoreCase(monitorVerifier)
+ && !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getPtcHubFromMonitorVerifier(monitorVerifier))) {
+ if (!"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getProtocolFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()))
+ && acbEthClient.getMonitorControlFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()) != 0) {
+ this.bbcContext.getMonitorContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update monitor contract status (address: %s)",
+ this.bbcContext.getMonitorContract().getContractAddress()
+ ), e);
+ }
+ }
+
+ @Override
+ public void setMonitorControl(int monitorType) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getMonitorContract())) {
+ throw new RuntimeException("empty monitor contract in bbc context");
+ }
+
+ acbEthClient.setMonitorControl(this.bbcContext.getMonitorContract().getContractAddress(), monitorType);
+
+ // 4. update monitor contract status
+ try {
+ String monitorVerifier = acbEthClient.getMonitorVerifierFromMonitor(this.bbcContext.getMonitorContract().getContractAddress());
+ if (!"0x0000000000000000000000000000000000000000".equalsIgnoreCase(monitorVerifier)
+ && !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getPtcHubFromMonitorVerifier(monitorVerifier))) {
+ if (!"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getProtocolFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()))
+ && acbEthClient.getMonitorControlFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()) != 0) {
+ this.bbcContext.getMonitorContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update monitor contract status (address: %s)",
+ this.bbcContext.getMonitorContract().getContractAddress()
+ ), e);
+ }
+ }
+
+ @Override
+ public void setPtcHubInMonitorVerifier(String contractAddress) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getMonitorContract())) {
+ throw new RuntimeException("empty monitor contract in bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getPtcContract())) {
+ throw new RuntimeException("empty PtcHub contract in bbc context");
+ }
+ String monitorVerifier = acbEthClient.getMonitorVerifierFromMonitor(this.bbcContext.getMonitorContract().getContractAddress());
+ if ("0x0000000000000000000000000000000000000000".equalsIgnoreCase(monitorVerifier)) {
+ throw new RuntimeException("not set monitor verifier contract in monitor contract yet");
+ }
+
+ acbEthClient.setPtcHubToMonitorVerifier(monitorVerifier, contractAddress);
+
+ // 4. update monitor contract status
+ try {
+ if (!"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getPtcHubFromMonitorVerifier(monitorVerifier)) &&
+ !"0x0000000000000000000000000000000000000000".equalsIgnoreCase(acbEthClient.getProtocolFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()))
+ && acbEthClient.getMonitorControlFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()) != 0 ) {
+ this.bbcContext.getMonitorContract().setStatus(ContractStatusEnum.CONTRACT_READY);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to update monitor contract status (address: %s)",
+ this.bbcContext.getMonitorContract().getContractAddress()
+ ), e);
+ }
+
+ }
+
+ @Override
+ public CrossChainMessageReceipt relayMonitorOrder(String committeeId, String signAlgo, byte[] rawProof, byte[] rawMonitorOrder) {
+ // 1. check context
+ if (ObjectUtil.isNull(this.bbcContext)) {
+ throw new RuntimeException("empty bbc context");
+ }
+ if (ObjectUtil.isNull(this.bbcContext.getMonitorContract())) {
+ throw new RuntimeException("empty monitor contract in bbc context");
+ }
+
+ getBBCLogger().debug("relay MonitorOrder to {} ", this.bbcContext.getMonitorContract().getContractAddress());
+
+ return acbEthClient.relayMonitorOrderToMonitor(this.bbcContext.getMonitorContract().getContractAddress(), committeeId, signAlgo, rawProof, rawMonitorOrder);
+ }
+
+
@Override
public CrossChainMessageReceipt relayAuthMessage(byte[] rawMessage) {
// 1. check context
@@ -499,6 +683,7 @@ public boolean hasPTCVerifyAnchor(ObjectIdentity ptcOwnerOid, BigInteger version
return acbEthClient.hasPTCVerifyAnchor(this.bbcContext.getPtcContract().getContractAddress(), ptcOwnerOid, version);
}
+ // in this version, ptc contract needs to be deployed after monitor contract, since monitor verifier is necessary for the init
@Override
public void setupPTCContract() {
// 1. check context
@@ -525,7 +710,8 @@ public void setupPTCContract() {
}
// 2. deploy contract
- String ptcHubContractAddr = acbEthClient.deployPtcHubContract(bcdnsRootCert, acbEthClient.deployCommitteeVerifierContract());
+ String ptcHubContractAddr = acbEthClient.deployPtcHubContract(bcdnsRootCert, acbEthClient.deployCommitteeVerifierContract(),
+ acbEthClient.getMonitorVerifierFromMonitor(this.bbcContext.getMonitorContract().getContractAddress()));
PTCContract ptcContract = new PTCContract();
ptcContract.setContractAddress(ptcHubContractAddr);
diff --git a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/conf/EthereumConfig.java b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/conf/EthereumConfig.java
index 41a482d1..5814323d 100644
--- a/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/conf/EthereumConfig.java
+++ b/acb-sdk/pluginset/ethereum2/offchain-plugin/src/main/java/com/alipay/antchain/bridge/plugins/ethereum2/conf/EthereumConfig.java
@@ -87,6 +87,9 @@ public enum CrossChainMessageScanPolicyEnum {
@JSONField
private String sdpContractAddressDeployed;
+ @JSONField
+ private String monitorContractAddressDeployed;
+
@JSONField
private BlockHeightPolicyEnum blockHeightPolicy = BlockHeightPolicyEnum.FINALIZED;
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..1de0c219 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
@@ -256,6 +256,190 @@ public String deploySdpContract() {
return sdpMsg.getContractAddress();
}
+ public String deployMonitorContract(String monitorVerifierContractAddress) {
+ Monitor monitor;
+ try {
+ monitor = Monitor.deploy(
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createDeployGasLimitProvider(Monitor.BINARY)
+ )
+ ).send();
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy monitor", e);
+ }
+
+ String monitorContractAddress = monitor.getContractAddress();
+
+ if (this.config.isUpgradableContracts()) {
+ TransparentUpgradeableProxy proxy;
+ try {
+ proxy = TransparentUpgradeableProxy.deploy(
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createDeployGasLimitProvider(
+ TransparentUpgradeableProxy.BINARY +
+ FunctionEncoder.encodeConstructor(ListUtil.toList(
+ new org.web3j.abi.datatypes.Address(monitor.getContractAddress()),
+ new org.web3j.abi.datatypes.Address(this.config.getProxyAdmin()),
+ new DynamicBytes(HexUtil.decodeHex("e1c7392a"))
+ ))
+ )
+ ),
+ BigInteger.ZERO,
+ monitor.getContractAddress(),
+ this.config.getProxyAdmin(),
+ HexUtil.decodeHex("e1c7392a")
+ ).send();
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy monitor contract", e);
+ }
+ getBbcLogger().info("deploy proxy contract for monitor: {}", proxy.getContractAddress());
+ monitorContractAddress = proxy.getContractAddress();
+ }
+
+ // set monitor verifier to monitor
+ monitor = Monitor.load(
+ monitorContractAddress,
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ monitorContractAddress,
+ new Function(
+ Monitor.FUNC_SETMONITORVERIFIER,
+ ListUtil.toList(new org.web3j.abi.datatypes.Address(monitorVerifierContractAddress)),
+ ListUtil.empty()
+ )
+ )
+ )
+ );
+
+ try {
+ TransactionReceipt receipt = monitor.setMonitorVerifier(monitorVerifierContractAddress).send();
+ if (!receipt.isStatusOK()) {
+ throw new RuntimeException(
+ StrUtil.format(
+ "transaction {} shows failed when set monitor verifier {} to monitor {}: {}",
+ receipt.getTransactionHash(), monitorVerifierContractAddress, monitorContractAddress, receipt.getRevertReason()
+ )
+ );
+ }
+ getBbcLogger().info(
+ "set monitor verifier {} to monitor {} by tx {} ", monitorVerifierContractAddress, monitorContractAddress, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "unexpected failure when setting monitor verifier %s to monitor %s",
+ monitorVerifierContractAddress, monitorContractAddress
+ ), e
+ );
+ }
+
+ return monitorContractAddress;
+ }
+
+ public String deployMonitorVerifierContract() {
+ MonitorVerifier monitorVerifier;
+ try {
+ monitorVerifier = MonitorVerifier.deploy(
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createDeployGasLimitProvider(MonitorVerifier.BINARY)
+ )
+ ).send();
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy MonitorVerifier", e);
+ }
+
+ if (this.config.isUpgradableContracts()) {
+ TransparentUpgradeableProxy proxy;
+ try {
+ proxy = TransparentUpgradeableProxy.deploy(
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createDeployGasLimitProvider(
+ TransparentUpgradeableProxy.BINARY +
+ FunctionEncoder.encodeConstructor(ListUtil.toList(
+ new org.web3j.abi.datatypes.Address(monitorVerifier.getContractAddress()),
+ new org.web3j.abi.datatypes.Address(this.config.getProxyAdmin()),
+ new DynamicBytes(HexUtil.decodeHex("e1c7392a"))
+ ))
+ )
+ ),
+ BigInteger.ZERO,
+ monitorVerifier.getContractAddress(),
+ this.config.getProxyAdmin(),
+ HexUtil.decodeHex("e1c7392a")
+ ).send();
+ } catch (Exception e) {
+ throw new RuntimeException("failed to deploy monitorVerifier contract", e);
+ }
+ getBbcLogger().info("deploy proxy contract for monitorVerifier: {}", proxy.getContractAddress());
+ return proxy.getContractAddress();
+ }
+
+ return monitorVerifier.getContractAddress();
+ }
+
+ @SneakyThrows
+ public String getMonitorVerifierFromMonitor(String monitorContractAddress) {
+ Monitor monitor = Monitor.load(monitorContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
+ return monitor.getMonitorVerifier().send();
+ }
+
+ public void setPtcHubToMonitorVerifier(String monitorVerifierContractAddress, String ptcHubContractAddress) {
+ // 2. load monitor verifier contract
+ MonitorVerifier monitorVerifier = MonitorVerifier.load(
+ monitorVerifierContractAddress,
+ this.web3j,
+ this.rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ monitorVerifierContractAddress,
+ new Function(
+ MonitorVerifier.FUNC_SETPTCHUBADDRESS,
+ ListUtil.toList(new org.web3j.abi.datatypes.Address(ptcHubContractAddress)), // inputs
+ Collections.emptyList()
+ )
+ )
+ )
+ );
+
+ // 3. set ptc hub to monitor verifier
+ try {
+ var receipt = monitorVerifier.setPtcHubAddress(ptcHubContractAddress).send();
+ getBbcLogger().info(
+ "set ptc hub {} to monitor verifier (address: {}) by tx {} ",
+ ptcHubContractAddress, monitorVerifierContractAddress, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to set ptc hub (address: %s) to monitor verifier %s",
+ ptcHubContractAddress, monitorVerifierContractAddress
+ ), e
+ );
+ }
+ }
+
+ @SneakyThrows
+ public String getPtcHubFromMonitorVerifier(String monitorVerifierContractAddress) {
+ MonitorVerifier monitorVerifier = MonitorVerifier.load(monitorVerifierContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
+ return monitorVerifier.getPtcHubAddress().send();
+ }
+
public long querySdpSeq(String sdpContractAddress, String senderDomain, String senderID, String receiverDomain, String receiverID) {
// 2. load sdpMsg
SDPMsg sdpMsg = SDPMsg.load(sdpContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
@@ -401,6 +585,198 @@ public void setLocalDomainToSdp(String sdpContractAddress, String localDomain) {
}
}
+ public void setMonitorContractToSdp(String sdpContractAddress, String monitorContractAddress) {
+ // 2. load sdp contract
+ SDPMsg sdp = SDPMsg.load(
+ sdpContractAddress,
+ this.web3j,
+ this.rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ sdpContractAddress,
+ new Function(
+ SDPMsg.FUNC_SETMONITORCONTRACT,
+ ListUtil.toList(new org.web3j.abi.datatypes.Address(monitorContractAddress)), // inputs
+ Collections.emptyList()
+ )
+ )
+ )
+ );
+
+ // 3. set monitor to sdp
+ try {
+ var receipt = sdp.setMonitorContract(monitorContractAddress).send();
+ getBbcLogger().info(
+ "set monitor contract (address: {}) to SDP {} by tx {}",
+ monitorContractAddress, sdpContractAddress, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to set monitor contract (address: %s) to SDP %s", monitorContractAddress, sdpContractAddress), e
+ );
+ }
+ }
+
+ @SneakyThrows
+ public String getMonitorContractFromSdp(String sdpContractAddress) {
+ var sdp = SDPMsg.load(sdpContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
+ return sdp.getMonitorAddress().send();
+ }
+
+ public void setProtocolToMonitor(String monitorContractAddress, String protocolAddress) {
+ // 2. load am contract
+ Monitor monitor = Monitor.load(
+ monitorContractAddress,
+ this.web3j,
+ this.rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ monitorContractAddress,
+ new Function(
+ Monitor.FUNC_SETPROTOCOL,
+ ListUtil.toList(new org.web3j.abi.datatypes.Address(protocolAddress)), // inputs
+ Collections.emptyList()
+ )
+ )
+ )
+ );
+
+ // 3. set protocol to monitor
+ try {
+ var receipt = monitor.setProtocol(protocolAddress).send();
+ getBbcLogger().info(
+ "set protocol (address: {}) to monitor {} by tx {} ",
+ protocolAddress, monitorContractAddress, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to set protocol (address: %s) to monitor %s",
+ protocolAddress, monitorContractAddress
+ ), e
+ );
+ }
+ }
+
+ @SneakyThrows
+ public String getProtocolFromMonitor(String monitorContractAddress) {
+ var monitor = Monitor.load(monitorContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
+ return monitor.getProtocol().send();
+ }
+
+ public void setMonitorControl(String monitorContractAddress, int monitorType) {
+ // load monitor contract
+ Monitor monitor = Monitor.load(
+ monitorContractAddress,
+ this.web3j,
+ this.rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ monitorContractAddress,
+ new Function(
+ Monitor.FUNC_SETMONITORCONTROL,
+ ListUtil.toList(new org.web3j.abi.datatypes.generated.Uint32(monitorType)), // inputs
+ Collections.emptyList()
+ )
+ )
+ )
+ );
+
+ // set monitorType to monitor
+ try {
+ var receipt = monitor.setMonitorControl(BigInteger.valueOf(monitorType)).send();
+ getBbcLogger().info(
+ "set monitorType (value: {}) to monitor {} by tx {} ",
+ monitorType, monitorContractAddress, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "failed to set monitorType (value: %s) to monitor %s",
+ monitorType, monitorContractAddress
+ ), e
+ );
+ }
+ }
+
+ @SneakyThrows
+ public int getMonitorControlFromMonitor(String monitorContractAddress) {
+ var monitor = Monitor.load(monitorContractAddress, web3j, rawTransactionManager, new DefaultGasProvider());
+ return monitor.getMonitorControl().send().intValue();
+ }
+
+ public CrossChainMessageReceipt relayMonitorOrderToMonitor(String monitorContractAddress, String committeeId, String signAlgo, byte[] rawProof, byte[] rawMonitorOrder) {
+ // 2. create Transaction
+ try {
+ // 2.1 create function
+ Function function = new Function(
+ Monitor.FUNC_RECVMONITORORDER, // function name
+ ListUtil.toList(new Utf8String(committeeId),
+ new Utf8String(signAlgo),
+ new DynamicBytes(rawProof),
+ new DynamicBytes(rawMonitorOrder)), // inputs
+ Collections.emptyList() // outputs
+ );
+ String encodedFunc = FunctionEncoder.encode(function);
+
+ // 2.2 pre-execute before commit tx
+ EthCall call = this.web3j.ethCall(
+ org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction(
+ config.isKmsService() ? this.txKMSSignService.getAddress() : this.credentials.getAddress(),
+ monitorContractAddress,
+ encodedFunc
+ ),
+ DefaultBlockParameterName.LATEST
+ ).send();
+
+ // 2.3 set `confirmed` and `successful` to false if reverted
+ CrossChainMessageReceipt crossChainMessageReceipt = new CrossChainMessageReceipt();
+ if (call.isReverted()) {
+ getBbcLogger().error("call monitor contract [recvMonitorOrder] {} reverted, reason: {}", monitorContractAddress, call.getRevertReason());
+ crossChainMessageReceipt.setSuccessful(false);
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setErrorMsg(call.getRevertReason());
+ return crossChainMessageReceipt;
+ }
+
+ // 2.4 async send tx
+ EthSendTransaction ethSendTransaction = rawTransactionManager.sendTransaction(
+ this.contractGasPriceProvider.getGasPrice(encodedFunc),
+ createEthCallGasLimitProvider(monitorContractAddress, function).getGasLimit(encodedFunc),
+ monitorContractAddress,
+ encodedFunc,
+ BigInteger.ZERO
+ );
+ if (ObjectUtil.isNull(ethSendTransaction)) {
+ throw new RuntimeException("send tx with null result");
+ }
+ if (ethSendTransaction.hasError()) {
+ throw new RuntimeException(StrUtil.format("tx error: {} - {}",
+ ethSendTransaction.getError().getCode(), ethSendTransaction.getError().getMessage()));
+ }
+ if (StrUtil.isEmpty(ethSendTransaction.getTransactionHash())) {
+ throw new RuntimeException("tx hash is empty");
+ }
+
+ // 2.5 return crossChainMessageReceipt
+ crossChainMessageReceipt.setConfirmed(false);
+ crossChainMessageReceipt.setSuccessful(true);
+ crossChainMessageReceipt.setTxhash(ethSendTransaction.getTransactionHash());
+ crossChainMessageReceipt.setErrorMsg("");
+
+ getBbcLogger().info("relay monitor order by tx {}", ethSendTransaction.getTransactionHash());
+
+ return crossChainMessageReceipt;
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format("failed to relay monitorOrder to %s", monitorContractAddress), e
+ );
+ }
+ }
+
public CrossChainMessageReceipt relayMsgToAuthMsg(String amContractAddress, byte[] rawMessage) {
// 2. create Transaction
try {
@@ -674,7 +1050,7 @@ public boolean hasPTCVerifyAnchor(String ptcHubAddress, ObjectIdentity ptcOwnerO
}
}
- public String deployPtcHubContract(AbstractCrossChainCertificate bcdnsRootCert, String committeePtcVerifier) {
+ public String deployPtcHubContract(AbstractCrossChainCertificate bcdnsRootCert, String committeePtcVerifier, String monitorVerifier) {
PtcHub ptcHub;
try {
var rawBcdnsRootCert = bcdnsRootCert.encode();
@@ -731,6 +1107,7 @@ public String deployPtcHubContract(AbstractCrossChainCertificate bcdnsRootCert,
getBbcLogger().info("deploy proxy contract for ptc hub: {}", proxy.getContractAddress());
}
+ // set committeePtcVerifier to ptc hub
ptcHub = PtcHub.load(
ptcHubContractAddr,
web3j,
@@ -770,6 +1147,46 @@ public String deployPtcHubContract(AbstractCrossChainCertificate bcdnsRootCert,
);
}
+ // set monitorVerifier to ptc hub
+ ptcHub = PtcHub.load(
+ ptcHubContractAddr,
+ web3j,
+ rawTransactionManager,
+ new AcbGasProvider(
+ this.contractGasPriceProvider,
+ createEthCallGasLimitProvider(
+ ptcHubContractAddr,
+ new Function(
+ PtcHub.FUNC_SETMONITORVERIFIER,
+ ListUtil.toList(new org.web3j.abi.datatypes.Address(monitorVerifier)),
+ ListUtil.empty()
+ )
+ )
+ )
+ );
+
+ try {
+ TransactionReceipt receipt = ptcHub.setMonitorVerifier(monitorVerifier).send();
+ if (!receipt.isStatusOK()) {
+ throw new RuntimeException(
+ StrUtil.format(
+ "transaction {} shows failed when set monitor verifier {} to ptc hub {}: {}",
+ receipt.getTransactionHash(), monitorVerifier, ptcHubContractAddr, receipt.getRevertReason()
+ )
+ );
+ }
+ getBbcLogger().info(
+ "set monitor verifier {} to ptc hub {} by tx {} ", monitorVerifier, ptcHubContractAddr, receipt.getTransactionHash()
+ );
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "unexpected failure when setting monitor verifier %s to ptc hub %s",
+ monitorVerifier, ptcHubContractAddr
+ ), e
+ );
+ }
+
return ptcHubContractAddr;
}
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..282b432f 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
@@ -43,6 +43,7 @@
import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext;
import com.alipay.antchain.bridge.commons.bbc.syscontract.AuthMessageContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum;
+import com.alipay.antchain.bridge.commons.bbc.syscontract.MonitorContract;
import com.alipay.antchain.bridge.commons.bbc.syscontract.SDPContract;
import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate;
import com.alipay.antchain.bridge.commons.bcdns.PTCCredentialSubject;
@@ -50,6 +51,7 @@
import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory;
import com.alipay.antchain.bridge.commons.core.am.IAuthMessage;
import com.alipay.antchain.bridge.commons.core.base.*;
+import com.alipay.antchain.bridge.commons.core.monitor.*;
import com.alipay.antchain.bridge.commons.core.ptc.*;
import com.alipay.antchain.bridge.commons.core.sdp.ISDPMessage;
import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory;
@@ -60,6 +62,7 @@
import com.alipay.antchain.bridge.commons.utils.crypto.SignAlgoEnum;
import com.alipay.antchain.bridge.plugins.ethereum2.abi.AppContract;
import com.alipay.antchain.bridge.plugins.ethereum2.abi.AuthMsg;
+import com.alipay.antchain.bridge.plugins.ethereum2.abi.Monitor;
import com.alipay.antchain.bridge.plugins.ethereum2.abi.SDPMsg;
import com.alipay.antchain.bridge.plugins.ethereum2.conf.Eth2NetworkEnum;
import com.alipay.antchain.bridge.plugins.ethereum2.conf.EthereumConfig;
@@ -220,7 +223,7 @@ public class EthereumBBCServiceTest {
nodeEndorseInfo.setPublicKey(nodePubkeyEntry);
NodeEndorseInfo nodeEndorseInfo2 = new NodeEndorseInfo();
- nodeEndorseInfo2.setNodeId("node2");
+ nodeEndorseInfo2.setNodeId("monitor-node");
nodeEndorseInfo2.setRequired(false);
nodeEndorseInfo2.setPublicKey(nodePubkeyEntry);
@@ -260,7 +263,7 @@ public class EthereumBBCServiceTest {
.sign(NODE_PTC_PRIVATE_KEY, tpbta.getEncodedToSign())
),
new CommitteeNodeProof(
- "node2",
+ "monitor-node",
SignAlgoEnum.KECCAK256_WITH_SECP256K1,
SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner()
.sign(NODE_PTC_PRIVATE_KEY, tpbta.getEncodedToSign())
@@ -277,14 +280,14 @@ public class EthereumBBCServiceTest {
CommitteeVerifyAnchor verifyAnchor = new CommitteeVerifyAnchor("committee");
verifyAnchor.addNode("node1", "default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
- verifyAnchor.addNode("node2", "default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
+ verifyAnchor.addNode("monitor-node", "default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
verifyAnchor.addNode("node3", "default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
verifyAnchor.addNode("node4", "default", ((X509PubkeyInfoObjectIdentity) oid).getPublicKey());
// prepare the network stuff
CommitteeNetworkInfo committeeNetworkInfo = new CommitteeNetworkInfo("committee");
committeeNetworkInfo.addEndpoint("node1", "grpcs://0.0.0.0:8080", "");
- committeeNetworkInfo.addEndpoint("node2", "grpcs://0.0.0.0:8080", "");
+ committeeNetworkInfo.addEndpoint("monitor-node", "grpcs://0.0.0.0:8080", "");
committeeNetworkInfo.addEndpoint("node3", "grpcs://0.0.0.0:8080", "");
committeeNetworkInfo.addEndpoint("node4", "grpcs://0.0.0.0:8080", "");
@@ -371,16 +374,18 @@ public void testStartupWithDeployedContract() {
EthereumBBCService bbcServiceTmp = new EthereumBBCService();
bbcServiceTmp.startup(mockValidCtx);
- // set up am and sdp
+ // set up am and sdp and monitor
bbcServiceTmp.setupAuthMessageContract();
bbcServiceTmp.setupSDPMessageContract();
+ bbcServiceTmp.setupMonitorContract();
bbcServiceTmp.setupPTCContract();
String amAddr = bbcServiceTmp.getContext().getAuthMessageContract().getContractAddress();
String sdpAddr = bbcServiceTmp.getContext().getSdpContract().getContractAddress();
+ String monitorAddr = bbcServiceTmp.getContext().getMonitorContract().getContractAddress();
String ptcAddr = bbcServiceTmp.getContext().getPtcContract().getContractAddress();
// start up success
- AbstractBBCContext ctx = mockValidCtxWithPreDeployedContracts(amAddr, sdpAddr, ptcAddr);
+ AbstractBBCContext ctx = mockValidCtxWithPreDeployedContracts(amAddr, sdpAddr, monitorAddr, ptcAddr);
EthereumBBCService ethereumBBCService = new EthereumBBCService();
ethereumBBCService.startup(ctx);
@@ -388,6 +393,8 @@ public void testStartupWithDeployedContract() {
Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ethereumBBCService.getBbcContext().getAuthMessageContract().getStatus());
Assert.assertEquals(sdpAddr, ethereumBBCService.getBbcContext().getSdpContract().getContractAddress());
Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ethereumBBCService.getBbcContext().getSdpContract().getStatus());
+ Assert.assertEquals(monitorAddr, ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ethereumBBCService.getBbcContext().getMonitorContract().getStatus());
Assert.assertEquals(ptcAddr, ethereumBBCService.getBbcContext().getPtcContract().getContractAddress());
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ethereumBBCService.getBbcContext().getPtcContract().getStatus());
}
@@ -399,22 +406,26 @@ public void testStartupWithReadyContract() {
EthereumBBCService bbcServiceTmp = new EthereumBBCService();
bbcServiceTmp.startup(mockValidCtx);
- // set up am and sdp
+ // set up am and sdp and monitor
bbcServiceTmp.setupAuthMessageContract();
bbcServiceTmp.setupSDPMessageContract();
+ bbcServiceTmp.setupMonitorContract();
bbcServiceTmp.setupPTCContract();
String amAddr = bbcServiceTmp.getContext().getAuthMessageContract().getContractAddress();
String sdpAddr = bbcServiceTmp.getContext().getSdpContract().getContractAddress();
+ String monitorAddr = bbcServiceTmp.getContext().getMonitorContract().getContractAddress();
String ptcAddr = bbcServiceTmp.getContext().getPtcContract().getContractAddress();
// start up success
EthereumBBCService ethereumBBCService = new EthereumBBCService();
- AbstractBBCContext ctx = mockValidCtxWithPreReadyContracts(amAddr, sdpAddr, ptcAddr);
+ AbstractBBCContext ctx = mockValidCtxWithPreReadyContracts(amAddr, sdpAddr, monitorAddr, ptcAddr);
ethereumBBCService.startup(ctx);
Assert.assertEquals(amAddr, ethereumBBCService.getBbcContext().getAuthMessageContract().getContractAddress());
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ethereumBBCService.getBbcContext().getAuthMessageContract().getStatus());
Assert.assertEquals(sdpAddr, ethereumBBCService.getBbcContext().getSdpContract().getContractAddress());
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ethereumBBCService.getBbcContext().getSdpContract().getStatus());
+ Assert.assertEquals(monitorAddr, ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ethereumBBCService.getBbcContext().getMonitorContract().getStatus());
Assert.assertEquals(ptcAddr, ethereumBBCService.getBbcContext().getPtcContract().getContractAddress());
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ethereumBBCService.getBbcContext().getPtcContract().getStatus());
}
@@ -468,18 +479,65 @@ public void testSetupSDPMessageContract() {
}
@Test
- public void testPtcContractAll() {
+ public void testSetupMonitorContract() {
EthereumBBCService ethereumBBCService = new EthereumBBCService();
// start up
AbstractBBCContext mockValidCtx = mockValidCtx();
ethereumBBCService.startup(mockValidCtx);
- // set up sdp
+ // set up monitor
+ ethereumBBCService.setupMonitorContract();
+
+ // get context
+ AbstractBBCContext ctx = ethereumBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getMonitorContract().getStatus());
+ }
+
+ @Test
+ public void testMonitorAndPtcContractAll() throws Exception {
+ EthereumBBCService ethereumBBCService = new EthereumBBCService();
+ // start up
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ ethereumBBCService.startup(mockValidCtx);
+
+ // set up sdp monitor and ptc
+ ethereumBBCService.setupSDPMessageContract();
+ ethereumBBCService.setupMonitorContract();
ethereumBBCService.setupPTCContract();
var ctx = ethereumBBCService.getContext();
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getPtcContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getMonitorContract().getStatus());
+
+ // init monitor
+ ethereumBBCService.setPtcHubInMonitorVerifier(ethereumBBCService.getBbcContext().getPtcContract().getContractAddress());
+
+ ethereumBBCService.setProtocolInMonitor(ctx.getSdpContract().getContractAddress());
+ String addr = Monitor.load(
+ ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress(),
+ ethereumBBCService.getAcbEthClient().getWeb3j(),
+ ethereumBBCService.getAcbEthClient().getCredentials(),
+ new DefaultGasProvider()
+ ).getProtocol().send();
+ log.info("protocol in monitor: {}", addr);
+
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getMonitorContract().getStatus());
+
+ int monitorType = 2; // 2 denotes MONITOR_OPEN
+ ethereumBBCService.setMonitorControl(monitorType);
+ int monitorControl = Monitor.load(
+ ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress(),
+ ethereumBBCService.getAcbEthClient().getWeb3j(),
+ ethereumBBCService.getAcbEthClient().getCredentials(),
+ new DefaultGasProvider()
+ ).getMonitorControl().send().intValue();
+ log.info("monitor control in monitor: {}", monitorControl);
+
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctx.getMonitorContract().getStatus());
+
+ // init ptc
ethereumBBCService.updatePTCTrustRoot(ptcTrustRoot);
var root = ethereumBBCService.getPTCTrustRoot(oid);
@@ -556,7 +614,8 @@ public void testSetProtocol() throws Exception {
}
@Test
- public void testSetAmContractAndLocalDomain() throws Exception {
+// public void testSetAmContractAndLocalDomain() throws Exception {
+ public void testSetSDPContractAll() throws Exception {
EthereumBBCService ethereumBBCService = new EthereumBBCService();
// start up
AbstractBBCContext mockValidCtx = mockValidCtx();
@@ -568,20 +627,19 @@ public void testSetAmContractAndLocalDomain() throws Exception {
// set up sdp
ethereumBBCService.setupSDPMessageContract();
+ // set up monitor
+ ethereumBBCService.setupMonitorContract();
+
// get context
AbstractBBCContext ctx = ethereumBBCService.getContext();
Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getAuthMessageContract().getStatus());
Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getMonitorContract().getStatus());
// set am to sdp
ethereumBBCService.setAmContract(ctx.getAuthMessageContract().getContractAddress());
- String amAddr = SDPMsg.load(
- ethereumBBCService.getBbcContext().getSdpContract().getContractAddress(),
- ethereumBBCService.getAcbEthClient().getWeb3j(),
- ethereumBBCService.getAcbEthClient().getCredentials(),
- new DefaultGasProvider()
- ).getAmAddress().send();
+ String amAddr = ethereumBBCService.getAcbEthClient().getAmContractFromSdp(ctx.getSdpContract().getContractAddress());
log.info("amAddr: {}", amAddr);
// check contract status
@@ -591,13 +649,31 @@ public void testSetAmContractAndLocalDomain() throws Exception {
// set the domain
ethereumBBCService.setLocalDomain(CHAIN_DOMAIN);
- byte[] rawDomain = SDPMsg.load(
+// byte[] rawDomain = SDPMsg.load(
+// ethereumBBCService.getBbcContext().getSdpContract().getContractAddress(),
+// ethereumBBCService.getAcbEthClient().getWeb3j(),
+// ethereumBBCService.getAcbEthClient().getCredentials(),
+// new DefaultGasProvider()
+// ).getLocalDomain().send();
+ byte[] rawDomain = ethereumBBCService.getAcbEthClient().getLocalDomainFromSdp(ctx.getSdpContract().getContractAddress());
+ log.info("domain: {}", HexUtil.encodeHexStr(rawDomain));
+
+// String monitorAddr0 = ethereumBBCService.getAcbEthClient().getMonitorContractFromSdp(ctx.getSdpContract().getContractAddress());
+// log.info("monitorAddr: {}", monitorAddr0);
+
+ // check contract status
+ ctx = ethereumBBCService.getContext();
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_DEPLOYED, ctx.getSdpContract().getStatus());
+
+ // set monitor to sdp
+ ethereumBBCService.setMonitorContract(ctx.getMonitorContract().getContractAddress());
+ String monitorAddr = SDPMsg.load(
ethereumBBCService.getBbcContext().getSdpContract().getContractAddress(),
ethereumBBCService.getAcbEthClient().getWeb3j(),
ethereumBBCService.getAcbEthClient().getCredentials(),
new DefaultGasProvider()
- ).getLocalDomain().send();
- log.info("domain: {}", HexUtil.encodeHexStr(rawDomain));
+ ).getMonitorAddress().send();
+ log.info("monitorAddr: {}", monitorAddr);
// check contract status
ctx = ethereumBBCService.getContext();
@@ -635,6 +711,124 @@ public void testReadCrossChainMessageReceipt() throws IOException, InterruptedEx
Assert.assertEquals(crossChainMessageReceipt.isSuccessful(), crossChainMessageReceipt1.isSuccessful());
}
+ @Test
+ public void testRelayMonitorOrder() throws Exception {
+ setupBbc();
+
+ // relay monitor order: add app contract to blacklist
+ log.info("[TEST-1]: relay monitor order to ADD app contract to BLACKLIST");
+
+ IMonitorOrder monitorOrder = MonitorOrderFactory.createMonitorOrder(
+ 1,
+ "ethereum",
+ "testDomain1",
+ Long.parseLong("1000"+"0000000000000000000000000000", 2),
+ "testDomain1",
+ StrUtil.replace(appContract.getContractAddress(), "0x", "000000000000000000000000"),
+ "testDomain2",
+ StrUtil.replace(REMOTE_APP_CONTRACT, "0x", "000000000000000000000000"),
+ "this is a test monitor order",
+ "nothing in extra"
+ );
+
+ byte[] rawMonitorOrder = monitorOrder.encode();
+ byte[] rawProof = SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().sign(
+ NODE_PTC_PRIVATE_KEY,
+ rawMonitorOrder
+ );
+
+ CrossChainMessageReceipt crossChainMessageReceipt = ethereumBBCService.relayMonitorOrder(
+ COMMITTEE_ID,
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1.getName(),
+ rawProof,
+ rawMonitorOrder
+ );
+
+// CrossChainMessageReceipt crossChainMessageReceipt = ethereumBBCService.relayMonitorOrder(
+// Long.parseLong("1000"+"0000000000000000000000000000", 2),
+// "testDomain1",
+// StrUtil.replace(appContract.getContractAddress(), "0x", "000000000000000000000000"),
+// "testDomain2",
+// StrUtil.replace(REMOTE_APP_CONTRACT, "0x", "000000000000000000000000"),
+// "this is a test monitor order",
+// "nothing in extra"
+// );
+ Assert.assertTrue(crossChainMessageReceipt.isSuccessful());
+ waitForTxConfirmed(crossChainMessageReceipt.getTxhash(), ethereumBBCService.getAcbEthClient().getWeb3j());
+ CrossChainMessageReceipt crossChainMessageReceipt1 = ethereumBBCService.readCrossChainMessageReceipt(crossChainMessageReceipt.getTxhash());
+ Assert.assertEquals(crossChainMessageReceipt.isSuccessful(), crossChainMessageReceipt1.isSuccessful());
+
+ // test app contract: it should be unable to send message
+ try {
+ appContract.sendUnorderedMessage("remoteDomain", DigestUtil.sha256(REMOTE_APP_CONTRACT), "UnorderedCrossChainMessage".getBytes()).send();
+ Assert.fail("[appContract.sendUnorderedMessage]: expected transaction to revert, but it succeeded.");
+ } catch (Exception e) {
+ log.info("[appContract.sendUnorderedMessage]: reverted as expected: {}", e.getMessage());
+ }
+
+ // relay monitor order: remove app contract from blacklist
+ log.info("[TEST-2]: relay monitor order to REMOVE app contract to BLACKLIST");
+ IMonitorOrder monitorOrder2 = MonitorOrderFactory.createMonitorOrder(
+ 1,
+ "ethereum",
+ "testDomain1",
+ Long.parseLong("1001"+"0000000000000000000000000000", 2),
+ "testDomain1",
+ StrUtil.replace(appContract.getContractAddress(), "0x", "000000000000000000000000"),
+ "testDomain2",
+ StrUtil.replace(REMOTE_APP_CONTRACT, "0x", "000000000000000000000000"),
+ "this is a test monitor order",
+ "nothing in extra"
+ );
+
+ byte[] rawMonitorOrder2 = monitorOrder2.encode();
+ byte[] rawProof2 = SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().sign(
+ NODE_PTC_PRIVATE_KEY,
+ rawMonitorOrder2
+ );
+
+ CrossChainMessageReceipt crossChainMessageReceipt2 = ethereumBBCService.relayMonitorOrder(
+ COMMITTEE_ID,
+ SignAlgoEnum.KECCAK256_WITH_SECP256K1.getName(),
+ rawProof2,
+ rawMonitorOrder2
+ );
+// CrossChainMessageReceipt crossChainMessageReceipt2 = ethereumBBCService.relayMonitorOrder(
+// Long.parseLong("1001"+"0000000000000000000000000000", 2),
+// "testDomain1",
+// StrUtil.replace(appContract.getContractAddress(), "0x", "000000000000000000000000"),
+// "testDomain2",
+// StrUtil.replace(REMOTE_APP_CONTRACT, "0x", "000000000000000000000000"),
+// "this is a test monitor order",
+// "nothing in extra"
+// );
+ Assert.assertTrue(crossChainMessageReceipt2.isSuccessful());
+ waitForTxConfirmed(crossChainMessageReceipt2.getTxhash(), ethereumBBCService.getAcbEthClient().getWeb3j());
+ CrossChainMessageReceipt crossChainMessageReceipt3 = ethereumBBCService.readCrossChainMessageReceipt(crossChainMessageReceipt2.getTxhash());
+ Assert.assertEquals(crossChainMessageReceipt2.isSuccessful(), crossChainMessageReceipt3.isSuccessful());
+
+ // test app contract: it should be able to send message
+ try {
+ var receipt = appContract.sendUnorderedMessage("remoteDomain", DigestUtil.sha256(REMOTE_APP_CONTRACT), "UnorderedCrossChainMessage".getBytes()).send();
+ Assert.assertTrue(receipt.isStatusOK());
+ } catch (Exception e) {
+ log.error("[appContract.sendUnorderedMessage]: unexpected revert: {}", e.getMessage());
+ Assert.fail("[appContract.sendUnorderedMessage]: expected transaction to execute successfully, but it failed");
+ }
+ }
+
+
+ @Test
+ public void testQueryLatestBlockNumber() throws Exception {
+ EthereumBBCService ethereumBBCService = new EthereumBBCService();
+ // start up
+ AbstractBBCContext mockValidCtx = mockValidCtx();
+ ethereumBBCService.startup(mockValidCtx);
+
+ BigInteger l = ethereumBBCService.getAcbEthClient().queryLatestBlockNumber();
+ Assert.assertTrue(l.compareTo(BigInteger.ZERO) > 0);
+ }
+
@Test
public void testReadCrossChainMessagesByHeight_sendUnordered() throws Exception {
setupBbc();
@@ -684,7 +878,7 @@ public void testReadCrossChainMessagesByHeight_sendOrdered() throws Exception {
Assert.assertNotNull(provableData);
Assert.assertEquals(msgOnHeight, provableData.getHeightVal());
Assert.assertEquals(receipt.getTransactionHash(), Numeric.toHexString(provableData.getTxHash()));
- Assert.assertEquals(receipt.getBlockHash(), Numeric.toHexString(provableData.getBlockHash()));
+ Assert.assertEquals(receipt.getBlockHash(), Numeric.toHexString(provableData.getBlockHash()));
var proofObj = EthReceiptProof.decodeFromJson(new String(provableData.getProof()));
Assert.assertNotNull(proofObj);
Assert.assertNotNull(proofObj.getEthTransactionReceipt());
@@ -784,6 +978,9 @@ private void setupBbc() {
// set up sdp
ethereumBBCService.setupSDPMessageContract();
+ // set up monitor
+ ethereumBBCService.setupMonitorContract();
+
ethereumBBCService.setupPTCContract();
// set protocol to am (sdp type: 0)
@@ -797,6 +994,19 @@ private void setupBbc() {
// set local domain to sdp
ethereumBBCService.setLocalDomain(CHAIN_DOMAIN);
+ // set monitor to sdp
+ ethereumBBCService.setMonitorContract(mockValidCtx.getMonitorContract().getContractAddress());
+
+ // set sdp to monitor
+ ethereumBBCService.setProtocolInMonitor(mockValidCtx.getSdpContract().getContractAddress());
+
+ // set monitorControl to monitor
+ ethereumBBCService.setMonitorControl(2);
+
+ // set ptc hub to monitor verifier
+ ethereumBBCService.setPtcHubInMonitorVerifier(mockValidCtx.getPtcContract().getContractAddress());
+
+ // set ptc tp am
ethereumBBCService.setPtcContract(mockValidCtx.getPtcContract().getContractAddress());
ethereumBBCService.updatePTCTrustRoot(ptcTrustRoot);
@@ -807,6 +1017,7 @@ private void setupBbc() {
AbstractBBCContext ctxCheck = ethereumBBCService.getContext();
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctxCheck.getAuthMessageContract().getStatus());
Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctxCheck.getSdpContract().getStatus());
+ Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, ctxCheck.getMonitorContract().getStatus());
TransactionReceipt receipt = appContract.setProtocol(ethereumBBCService.getBbcContext().getSdpContract().getContractAddress()).send();
if (receipt.isStatusOK()) {
@@ -819,6 +1030,17 @@ private void setupBbc() {
ethereumBBCService.getBbcContext().getSdpContract().getContractAddress()));
}
+ TransactionReceipt receipt1 = appContract.setMonitorContract(ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress()).send();
+ if (receipt1.isStatusOK()) {
+ log.info("set monitor contract({}) to app contract({})",
+ ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress(),
+ appContract.getContractAddress());
+ } else {
+ throw new Exception(String.format("failed to set monitor contract(%s) to app contract(%s)",
+ ethereumBBCService.getBbcContext().getMonitorContract().getContractAddress(),
+ appContract.getContractAddress()));
+ }
+
setupBBC = true;
}
@@ -860,13 +1082,14 @@ private AbstractBBCContext mockValidCtx() {
return mockCtx;
}
- private AbstractBBCContext mockValidCtxWithPreDeployedContracts(String amAddr, String sdpAddr, String ptcAddr) {
+ private AbstractBBCContext mockValidCtxWithPreDeployedContracts(String amAddr, String sdpAddr, String monitorAddr, String ptcAddr) {
EthereumConfig mockConf = new EthereumConfig();
mockConf.setUrl(VALID_URL);
mockConf.setBeaconApiUrl(VALID_BEACON_URL);
mockConf.setPrivateKey(BBC_ETH_PRIVATE_KEY_2);
mockConf.setAmContractAddressDeployed(amAddr);
mockConf.setSdpContractAddressDeployed(sdpAddr);
+ mockConf.setMonitorContractAddressDeployed(monitorAddr);
mockConf.setPtcHubContractAddressDeployed(ptcAddr);
mockConf.setMsgScanPolicy(scanPolicy);
mockConf.setGasLimitPolicy(gasLimitPolicy);
@@ -888,13 +1111,14 @@ private AbstractBBCContext mockValidCtxWithPreDeployedContracts(String amAddr, S
return mockCtx;
}
- private AbstractBBCContext mockValidCtxWithPreReadyContracts(String amAddr, String sdpAddr, String ptcAddr) {
+ private AbstractBBCContext mockValidCtxWithPreReadyContracts(String amAddr, String sdpAddr, String monitorAddr, String ptcAddr) {
EthereumConfig mockConf = new EthereumConfig();
mockConf.setUrl(VALID_URL);
mockConf.setBeaconApiUrl(VALID_BEACON_URL);
mockConf.setPrivateKey(BBC_ETH_PRIVATE_KEY_3);
mockConf.setAmContractAddressDeployed(amAddr);
mockConf.setSdpContractAddressDeployed(sdpAddr);
+ mockConf.setMonitorContractAddressDeployed(monitorAddr);
mockConf.setPtcHubContractAddressDeployed(ptcAddr);
mockConf.setMsgScanPolicy(scanPolicy);
mockConf.setGasLimitPolicy(gasLimitPolicy);
@@ -924,6 +1148,11 @@ private AbstractBBCContext mockValidCtxWithPreReadyContracts(String amAddr, Stri
sdpContract.setStatus(ContractStatusEnum.CONTRACT_READY);
mockCtx.setSdpContract(sdpContract);
+ MonitorContract monitorContract = new MonitorContract();
+ monitorContract.setContractAddress(monitorAddr);
+ monitorContract.setStatus(ContractStatusEnum.CONTRACT_READY);
+ mockCtx.setMonitorContract(monitorContract);
+
return mockCtx;
}
@@ -953,13 +1182,20 @@ private void waitForTxConfirmed(String txhash, Web3j web3j) {
}
private byte[] getRawMsgFromRelayer(String receiverAddr) throws IOException {
+ // need to replace "awesome antchain-bridge" with monitor message
+ IMonitorMessage monitorMessage = MonitorMessageFactory.createMonitorMessage(
+ 1,
+ 2,
+ "this is a monitorMsg",
+ "awesome antchain-bridge".getBytes()
+ );
ISDPMessage sdpMessage = SDPMessageFactory.createSDPMessage(
1,
new byte[32],
crossChainLane.getReceiverDomain().getDomain(),
Numeric.hexStringToByteArray(StrUtil.replace(receiverAddr, "0x", "000000000000000000000000")),
-1,
- "awesome antchain-bridge".getBytes()
+ monitorMessage.encode()
);
IAuthMessage am = AuthMessageFactory.createAuthMessage(
1,
@@ -982,7 +1218,14 @@ private byte[] getRawMsgFromRelayer(String receiverAddr) throws IOException {
thirdPartyProof.getEncodedToSign()
)).build();
CommitteeNodeProof node2Proof = CommitteeNodeProof.builder()
- .nodeId("node2")
+ .nodeId("monitor-node")
+ .signAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1)
+ .signature(SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().sign(
+ NODE_PTC_PRIVATE_KEY,
+ thirdPartyProof.getEncodedToSign()
+ )).build();
+ CommitteeNodeProof node3Proof = CommitteeNodeProof.builder()
+ .nodeId("node3")
.signAlgo(SignAlgoEnum.KECCAK256_WITH_SECP256K1)
.signature(SignAlgoEnum.KECCAK256_WITH_SECP256K1.getSigner().sign(
NODE_PTC_PRIVATE_KEY,
@@ -991,7 +1234,7 @@ private byte[] getRawMsgFromRelayer(String receiverAddr) throws IOException {
CommitteeEndorseProof endorseProof = new CommitteeEndorseProof();
endorseProof.setCommitteeId(COMMITTEE_ID);
- endorseProof.setSigs(ListUtil.toList(node1Proof, node2Proof));
+ endorseProof.setSigs(ListUtil.toList(node1Proof, node2Proof, node3Proof));
thirdPartyProof.setRawProof(endorseProof.encode());
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/AppContract.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/AppContract.sol
index 5e60e4d2..bc996119 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/AppContract.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/AppContract.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
import "./interfaces/IContractUsingSDP.sol";
import "./interfaces/ISDPMessage.sol";
+import "./interfaces/IMonitor.sol";
import "./lib/utils/Ownable.sol";
contract AppContract is IContractUsingSDP, Ownable {
@@ -15,6 +16,8 @@ contract AppContract is IContractUsingSDP, Ownable {
mapping(bytes32 => bytes[]) public sendMsg;
+ address public monitorAddress;
+
address public sdpAddress;
bytes32 public latest_msg_id_sent_order;
@@ -31,7 +34,7 @@ contract AppContract is IContractUsingSDP, Ownable {
event sendCrosschainMsg(string receiverDomain, bytes32 receiver, bytes message, bool isOrdered);
modifier onlySdpMsg() {
- require(msg.sender == sdpAddress, "INVALID_PERMISSION");
+ require(msg.sender == sdpAddress, "INVALID_PERMISSION: only sdp message");
_;
}
@@ -39,20 +42,30 @@ contract AppContract is IContractUsingSDP, Ownable {
sdpAddress = protocolAddress;
}
- function recvUnorderedMessage(string memory senderDomain, bytes32 author, bytes memory message) external override onlySdpMsg {
+ modifier onlyMonitorMsg() {
+ require(monitorAddress == msg.sender, "INVALID_PERMISSION: only monitor message");
+ _;
+ }
+
+ function setMonitorContract(address newMonitorAddress) external onlyOwner {
+ monitorAddress = newMonitorAddress;
+ }
+
+ function recvUnorderedMessage(string memory senderDomain, bytes32 author, bytes memory message) external override onlyMonitorMsg {
recvMsg[author].push(message);
last_uo_msg = message;
emit recvCrosschainMsg(senderDomain, author, message, false);
}
- function recvMessage(string memory senderDomain, bytes32 author, bytes memory message) external override onlySdpMsg {
+ function recvMessage(string memory senderDomain, bytes32 author, bytes memory message) external override onlyMonitorMsg {
recvMsg[author].push(message);
last_msg = message;
emit recvCrosschainMsg(senderDomain, author, message, true);
}
+ // notice: monitor only support the sendV1 version message
function sendUnorderedMessage(string memory receiverDomain, bytes32 receiver, bytes memory message) external {
- ISDPMessage(sdpAddress).sendUnorderedMessage(receiverDomain, receiver, message);
+ IMonitor(monitorAddress).sendUnorderedMonitorMessage(receiverDomain, receiver, message);
sendMsg[receiver].push(message);
emit sendCrosschainMsg(receiverDomain, receiver, message, false);
@@ -60,7 +73,7 @@ contract AppContract is IContractUsingSDP, Ownable {
function sendMessage(string memory receiverDomain, bytes32 receiver, bytes memory message) external{
- ISDPMessage(sdpAddress).sendMessage(receiverDomain, receiver, message);
+ IMonitor(monitorAddress).sendMonitorMessage(receiverDomain, receiver, message);
sendMsg[receiver].push(message);
emit sendCrosschainMsg(receiverDomain, receiver, message, true);
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/CommitteePtcVerifier.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/CommitteePtcVerifier.sol
index 2b783a42..24ba4198 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/CommitteePtcVerifier.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/CommitteePtcVerifier.sol
@@ -11,8 +11,8 @@ contract CommitteePtcVerifier is IPtcVerifier {
return CommitteeLib.verifyTpBta(va, tpBta);
}
- function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof) external override returns (bool) {
- return CommitteeLib.verifyTpProof(tpBta, tpProof);
+ function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof, address monitorPtcAddr) external override returns (bool) {
+ return CommitteeLib.verifyTpProof(tpBta, tpProof, monitorPtcAddr);
}
function myPtcType() external pure override returns (PTCTypeEnum) {
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/Monitor.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/Monitor.sol
new file mode 100755
index 00000000..29e6a4c5
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/Monitor.sol
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "./interfaces/IContractUsingSDP.sol";
+import "./interfaces/IContractUsingMonitor.sol";
+import "./interfaces/ISDPMessage.sol";
+import "./interfaces/IMonitor.sol";
+import "./interfaces/IMonitorVerifier.sol";
+import "./lib/monitor/MonitorLib.sol";
+import "./lib/utils/Ownable.sol";
+import "./@openzeppelin/contracts/proxy/utils/Initializable.sol";
+
+// 监管节点部署该合约
+// 接受监管指令; 只在发送方进行事前监管
+contract Monitor is IMonitor, IContractUsingMonitor, Ownable, Initializable {
+ using MonitorLib for MonitorOrder;
+ using MonitorLib for MonitorMessage;
+
+ // SDP合约地址
+ address public sdpAddress;
+
+ // MonitorVerifier合约地址
+ address public monitorVerifierAddress;
+
+ // 控制监管的字段
+ uint32 public monitorControl;
+
+ // 黑名单
+ mapping(bytes32 => bool) public senderBlacklist;
+ mapping(bytes32 => bool) public receiverBlacklist;
+
+ // uint32 public successfulCallInMonitorOPEN = 0;
+ // uint32 public successfulCallInMonitorCLOSE = 0;
+ // uint32 public successfulCallInMonitorROLLBACK = 0;
+
+ modifier onlySubProtocols {
+ require(
+ msg.sender == sdpAddress,
+ "MonitorMsg: sender not valid sub-protocol"
+ );
+ _;
+ }
+
+ constructor() {
+ _disableInitializers();
+ }
+
+ function init() external initializer() {
+ _transferOwnership(_msgSender());
+ }
+
+ function setProtocol(address protocolAddress) override external onlyOwner {
+ require(protocolAddress != address(0), "MonitorMsg: invalid sdp contract");
+ sdpAddress = protocolAddress;
+ }
+
+ function setMonitorVerifier(address newMonitorVerifierAddress) override external {
+ require(newMonitorVerifierAddress != address(0), "MonitorMsg: invalid MonitorVerifier contract");
+ monitorVerifierAddress = newMonitorVerifierAddress;
+ }
+
+ function setMonitorControl(uint32 monitorType) override external onlyOwner {
+ monitorControl = monitorType == 0 ? MonitorLib.MONITOR_CLOSE : MonitorLib.MONITOR_OPEN;
+ }
+
+ function getProtocol() external view returns (address) {
+ return sdpAddress;
+ }
+
+ function getMonitorControl() external view returns (uint32) {
+ return monitorControl;
+ }
+
+ function getMonitorVerifier() external view returns (address) {
+ return monitorVerifierAddress;
+ }
+
+ // function getSuccessfulCallInMonitorOPEN() external view returns (uint32) {
+ // return successfulCallInMonitorOPEN;
+ // }
+
+ // function getSuccessfulCallInMonitorCLOSE() external view returns (uint32) {
+ // return successfulCallInMonitorCLOSE;
+ // }
+
+ // function getSuccessfulCallInMonitorROLLBACK() external view returns (uint32) {
+ // return successfulCallInMonitorROLLBACK;
+ // }
+
+ function sendMonitorMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) override external {
+ _beforeSend(receiverDomain, receiverID, message);
+
+ // 执行事前监管的检查
+ if (monitorControl == MonitorLib.MONITOR_OPEN) {
+ bool monitorResult = false;
+ monitorResult = PreMonitoring(receiverDomain, receiverID, message);
+ if(monitorResult == false) {
+ revert("beforehand Monitor disapproval");
+ }
+ }
+
+ MonitorMessage memory monitorMessage = MonitorMessage(
+ {
+ monitorType: monitorControl,
+ monitorMsg: "",
+ message: message
+ }
+ );
+
+ bytes memory rawMsg = monitorMessage.encode();
+
+ ISDPMessage(sdpAddress).sendMessage(receiverDomain, receiverID, msg.sender, rawMsg);
+
+ _afterSend();
+ }
+
+ function sendUnorderedMonitorMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) external {
+ _beforeSendUnordered(receiverDomain, receiverID, message);
+
+ // 执行事前监管的检查
+ if (monitorControl == MonitorLib.MONITOR_OPEN) {
+ bool monitorResult = false;
+ monitorResult = PreMonitoring(receiverDomain, receiverID, message);
+ if(monitorResult == false) {
+ revert("beforehand Monitor disapproval");
+ }
+ }
+
+ MonitorMessage memory monitorMessage = MonitorMessage(
+ {
+ monitorType: monitorControl,
+ monitorMsg: "",
+ message: message
+ }
+ );
+
+ bytes memory rawMsg = monitorMessage.encode();
+
+ ISDPMessage(sdpAddress).sendUnorderedMessage(receiverDomain, receiverID, msg.sender, rawMsg);
+
+ _afterSendUnordered();
+ }
+
+ // IContractUsingMonitor.sol里面的接口
+ function recvUnorderedMessageFromSDP(string memory senderDomain, bytes32 author, address receiverID, bytes memory message) external override onlySubProtocols {
+ // 去掉监管字段 再向上传给app合约
+ MonitorMessage memory monitorMessage;
+ monitorMessage.decode(message);
+
+ if (monitorMessage.monitorType == MonitorLib.MONITOR_OPEN) {
+ // 验证监管节点的签名
+ bool res = IMonitorVerifier(monitorVerifierAddress).verifyMonitorNodeProofMessage();
+ emit VerifyMonitorNodeProofMessage(senderDomain, author, receiverID, res);
+ // 监管不通过 构造回滚消息(仅修改监管字段 对原文不修改)
+ if (res == false) {
+ monitorMessage.monitorType = MonitorLib.MONITOR_ROLLBACK;
+ monitorMessage.monitorMsg = "MidMonitoring not pass";
+ ISDPMessage(sdpAddress).sendUnorderedMessage(senderDomain, author, receiverID, monitorMessage.encode());
+ emit sendMonitorRollbakMessage(monitorMessage.monitorType, MonitorLib.encodeAddressIntoCrossChainID(receiverID), senderDomain, author, monitorMessage.monitorMsg);
+ } else {
+ IContractUsingSDP(receiverID).recvUnorderedMessage(senderDomain, author, monitorMessage.message);
+ }
+ } else if (monitorMessage.monitorType == MonitorLib.MONITOR_ROLLBACK) {
+ IContractUsingSDP(receiverID).recvUnorderedMessage(senderDomain, author, monitorMessage.message);
+ emit receiveMonitorRollbackMessage(monitorMessage.monitorType, senderDomain, author, MonitorLib.encodeAddressIntoCrossChainID(receiverID), monitorMessage.monitorMsg);
+ } else if (monitorMessage.monitorType == MonitorLib.MONITOR_CLOSE) {
+ IContractUsingSDP(receiverID).recvUnorderedMessage(senderDomain, author, monitorMessage.message);
+ } else {
+ revert("Monitor_Msg: wrong monitor type");
+ }
+ }
+
+ // IContractUsingMonitor.sol里面的接口
+ function recvMessageFromSDP(string memory senderDomain, bytes32 author, address receiverID, bytes memory message) external override onlySubProtocols {
+ // 去掉监管字段 再向上传给app合约
+ MonitorMessage memory monitorMessage;
+ monitorMessage.decode(message);
+
+ if (monitorMessage.monitorType == MonitorLib.MONITOR_OPEN) {
+ // 验证监管节点的签名
+ bool res = IMonitorVerifier(monitorVerifierAddress).verifyMonitorNodeProofMessage();
+ emit VerifyMonitorNodeProofMessage(senderDomain, author, receiverID, res);
+ // 监管不通过 构造回滚消息(仅修改监管字段 对原文不修改)
+ if (res == false) {
+ // revert("Monitor_Msg: MidMonitoring not pass");
+ monitorMessage.monitorType = MonitorLib.MONITOR_ROLLBACK;
+ monitorMessage.monitorMsg = "MidMonitoring not pass";
+ ISDPMessage(sdpAddress).sendMessage(senderDomain, author, receiverID, monitorMessage.encode());
+ emit sendMonitorRollbakMessage(monitorMessage.monitorType, MonitorLib.encodeAddressIntoCrossChainID(receiverID), senderDomain, author, monitorMessage.monitorMsg);
+ } else {
+ IContractUsingSDP(receiverID).recvMessage(senderDomain, author, monitorMessage.message);
+ // successfulCallInMonitorOPEN += 1;
+ }
+ } else if (monitorMessage.monitorType == MonitorLib.MONITOR_ROLLBACK) {
+ IContractUsingSDP(receiverID).recvMessage(senderDomain, author, monitorMessage.message);
+ // successfulCallInMonitorROLLBACK += 1;
+ emit receiveMonitorRollbackMessage(monitorMessage.monitorType, senderDomain, author, MonitorLib.encodeAddressIntoCrossChainID(receiverID), monitorMessage.monitorMsg);
+ } else if (monitorMessage.monitorType == MonitorLib.MONITOR_CLOSE) {
+ IContractUsingSDP(receiverID).recvMessage(senderDomain, author, monitorMessage.message);
+ // successfulCallInMonitorCLOSE += 1;
+ } else {
+ revert("Monitor_Msg: wrong monitor type");
+ }
+ }
+
+ function PreMonitoring(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) internal returns (bool) {
+ bool result = false;
+
+ // 事前监管逻辑
+ bytes32 senderID = MonitorLib.encodeAddressIntoCrossChainID(msg.sender);
+ if(senderBlacklist[senderID]) {
+ emit MonitorDisapproval(senderID,
+ receiverDomain,
+ receiverID,
+ MonitorLib.MAJOR_TYPE_CONTRACT_ADDRESS,
+ MonitorLib.SENDER_IN_BLACKLIST);
+ } else {
+ if (receiverBlacklist[receiverID]) {
+ emit MonitorDisapproval(senderID,
+ receiverDomain,
+ receiverID,
+ MonitorLib.MAJOR_TYPE_CONTRACT_ADDRESS,
+ MonitorLib.RECEIVER_IN_BLACKLIST);
+ } else {
+ emit MonitorApproval(senderID,
+ receiverDomain,
+ receiverID);
+ return true;
+ }
+ }
+
+ return result;
+ }
+
+
+ function recvMonitorOrder(string calldata committeeId, string calldata signAlgo, bytes memory rawProof, bytes memory rawMonitorOrder) external override {
+ // 对监管节点发送的监管指令 验签
+ bool res = IMonitorVerifier(monitorVerifierAddress).verifyMonitorOrder(committeeId, signAlgo, rawProof, rawMonitorOrder);
+ emit VerifyMonitorOrder(committeeId, res);
+ require(res == true, "Monitor_Msg: verify monitor node proof message failed");
+
+ MonitorOrder memory monitorOrder;
+ monitorOrder.decode(rawMonitorOrder);
+
+ uint8[8] memory flags;
+ uint8[8] memory values;
+ for (uint8 i = 0; i < 8; i++) {
+ uint8 chunk = uint8((monitorOrder.monitorOrderType >> (28 - i * 4)) & 0xF); // 取出 4-bit
+ flags[i] = (chunk >> 3) & 0x1; // 最高位为主类型
+ values[i] = chunk & 0x7; // 后3位为子类型
+ }
+
+ // 存储监管指令 目前只支持 加入和移除黑名单 控制监管开关 (合约部署时默认监管开启)
+ if(flags[0] == MonitorLib.MAJOR_TYPE_CONTRACT_ADDRESS) {
+ if(values[0] == MonitorLib.MINOR_TYPE_ADD_TO_BLACKLIST) {
+ senderBlacklist[monitorOrder.sender] = true;
+ receiverBlacklist[monitorOrder.receiver] = true;
+ } else if(values[0] == MonitorLib.MINOR_TYPE_REMOVE_FROM_BLACKLIST) {
+ senderBlacklist[monitorOrder.sender] = false;
+ receiverBlacklist[monitorOrder.receiver] = false;
+ } else {
+ revert("Monitor_Msg: not support monitor order containing this MINOR TYPE in MAJOR_TYPE_CONTRACT_ADDRESS yet");
+ }
+ }
+ if(flags[1] == MonitorLib.MAJOR_TYPE_CONTROL) {
+ if(values[1] == MonitorLib.MINOR_TYPE_MONITOR_CLOSE) {
+ monitorControl = MonitorLib.MONITOR_CLOSE;
+ } else if(values[1] == MonitorLib.MINOR_TYPE_MONITOR_OPEN) {
+ monitorControl = MonitorLib.MONITOR_OPEN;
+ } else {
+ revert("Monitor_Msg: not support monitor order containing this MINOR TYPE in MAJOR_TYPE_CONTROL yet");
+ }
+ }
+ if(flags[0] != MonitorLib.MAJOR_TYPE_CONTRACT_ADDRESS) {
+ if(flags[1] != MonitorLib.MAJOR_TYPE_CONTROL) {
+ revert("Monitor_Msg: not support monitor order containing this MAJOR TYPE yet");
+ }
+ }
+ emit receiveMonitorOrder(monitorOrder.monitorOrderType, monitorOrder.senderDomain, monitorOrder.sender,
+ monitorOrder.receiverDomain, monitorOrder.receiver,
+ monitorOrder.transactionContent, monitorOrder.extra);
+ }
+
+ function _beforeSend(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) internal {}
+
+ function _afterSend() internal {}
+
+ function _beforeSendUnordered(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) internal {}
+
+ function _afterSendUnordered() internal {}
+
+ /**
+ * @dev This empty reserved space is put in place to allow future versions to add new
+ * variables without shifting down storage in the inheritance chain.
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
+ */
+ uint256[50] private __gap;
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/MonitorVerifier.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/MonitorVerifier.sol
new file mode 100755
index 00000000..c416500d
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/MonitorVerifier.sol
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "./interfaces/IMonitorVerifier.sol";
+import "./lib/ptc/CommitteeLib.sol";
+import "./lib/monitor/MonitorLib.sol";
+import "./@openzeppelin/contracts/access/Ownable.sol";
+import "./@openzeppelin/contracts/proxy/utils/Initializable.sol";
+
+contract MonitorVerifier is Ownable, Initializable, IMonitorVerifier {
+
+ using CommitteeLib for NodeEndorseInfo;
+ using CommitteeLib for CommitteeNodeProof;
+ using CommitteeLib for CrossChainLane;
+
+ MonitorNodeProofMessage public monitorNodeProofMessage;
+
+ address public ptcHubAddress;
+
+ // key: committeeId, value: NodeEndorseInfo
+ mapping(string => NodeEndorseInfo) public monitorNodeEndorseInfoMap;
+
+ modifier onlyPtcHub() {
+ require(_msgSender() == ptcHubAddress, "MonitorVerifierMsg: caller is not the ptcHub");
+ _;
+ }
+
+ constructor() {
+ _disableInitializers();
+ }
+
+ function init() external initializer {
+ _transferOwnership(_msgSender());
+ }
+
+ function setPtcHubAddress(address newPtcHubAddress) external override onlyOwner {
+ ptcHubAddress = newPtcHubAddress;
+ }
+
+ function getPtcHubAddress() external view returns (address) {
+ return ptcHubAddress;
+ }
+
+ function updateMonitorNodeEndorseInfo(bytes memory rawEndorseRoot) external override onlyPtcHub {
+ CommitteeEndorseRoot memory cer = CommitteeLib.decodeCommitteeEndorseRootFrom(rawEndorseRoot);
+ for (uint i = 0; i < cer.endorsers.length; i++) {
+ NodeEndorseInfo memory info = cer.endorsers[i];
+ if (CommitteeLib.checkMonitorNode(info.nodeId)) {
+ monitorNodeEndorseInfoMap[cer.committeeId] = info;
+ emit UpdateMonitorNodeEndorseInfo(cer.committeeId, info.nodeId, info.publicKey.rawPublicKey);
+ break;
+ }
+ }
+ }
+
+ // AM-SDP-Monitor合约这样一条跨合约调用链是一个原子性操作, 属于同一笔交易的执行上下文,在交易结束之前不会切换到下一笔交易
+ // 所以此处不需要用modifier修饰符去保证来源的可靠性
+ function receiveMonitorNodeProofMessage(
+ CrossChainLane calldata newCrossChainLane,
+ string calldata newCommitteeId,
+ CommitteeNodeProof calldata newMonitorNodeProof,
+ bytes calldata newEncodedToSign) external override {
+ monitorNodeProofMessage.crossChainLane = newCrossChainLane;
+ monitorNodeProofMessage.committeeId = newCommitteeId;
+ monitorNodeProofMessage.monitorNodeProof = newMonitorNodeProof;
+ monitorNodeProofMessage.encodedToSign = newEncodedToSign;
+ }
+
+ function verifyMonitorNodeProofMessage() external override returns (bool) {
+ require(_hasMonitorNodeEndorseInfo(monitorNodeProofMessage.committeeId), "MonitorVerifierMsg: no monitor node endorse info");
+ return CommitteeLib.verifyTpProofFromMonitorNode(
+ monitorNodeProofMessage.crossChainLane,
+ monitorNodeProofMessage.committeeId,
+ monitorNodeEndorseInfoMap[monitorNodeProofMessage.committeeId],
+ monitorNodeProofMessage.monitorNodeProof,
+ monitorNodeProofMessage.encodedToSign);
+ }
+
+ function verifyMonitorOrder(string calldata committeeId, string calldata signAlgo, bytes calldata rawProof, bytes calldata rawMonitorOrder) external override returns (bool) {
+ require(_hasMonitorNodeEndorseInfo(committeeId), "MonitorVerifierMsg: no monitor node endorse info");
+ return CommitteeLib.verifyMonitorOrder(
+ committeeId,
+ monitorNodeEndorseInfoMap[committeeId],
+ signAlgo,
+ rawProof,
+ rawMonitorOrder);
+ }
+
+ function _hasMonitorNodeEndorseInfo(string memory committeeId) internal view returns (bool) {
+ return bytes(monitorNodeEndorseInfoMap[committeeId].nodeId).length > 0;
+ }
+
+}
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/PtcHub.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/PtcHub.sol
index a0995590..9ef2abd4 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/PtcHub.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/PtcHub.sol
@@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2;
import "./interfaces/IPtcHub.sol";
import "./interfaces/IPtcVerifier.sol";
+import "./interfaces/IMonitorVerifier.sol";
import "./lib/ptc/PtcLib.sol";
import "./@openzeppelin/contracts/access/Ownable.sol";
import "./@openzeppelin/contracts/proxy/utils/Initializable.sol";
@@ -43,6 +44,8 @@ contract PtcHub is IPtcHub, Ownable, Initializable {
PTCTypeEnum[] public ptcTypeSupported;
+ address public monitorVerifierAddr;
+
constructor(bytes memory rawRootBcdnsCert) {
_initBcdns(rawRootBcdnsCert);
_disableInitializers();
@@ -69,6 +72,15 @@ contract PtcHub is IPtcHub, Ownable, Initializable {
emit NewBcdnsCert(k);
}
+ function setMonitorVerifier(address newMonitorVerifierAddr) external override onlyOwner {
+ require(newMonitorVerifierAddr != address(0), "PtcHub: invalid monitorPtc contract");
+ monitorVerifierAddr = newMonitorVerifierAddr;
+ }
+
+ function getMonitorVerifier() external view returns (address) {
+ return monitorVerifierAddr;
+ }
+
function updatePTCTrustRoot(bytes calldata rawPtcTrustRoot)
external
override
@@ -190,6 +202,7 @@ contract PtcHub is IPtcHub, Ownable, Initializable {
}
s.mapByVersion[newTpBta.tpbtaVersion] = rawTpBta;
emit SaveTpBta(k, newTpBta.tpbtaVersion);
+ IMonitorVerifier(monitorVerifierAddr).updateMonitorNodeEndorseInfo(newTpBta.endorseRoot);
}
function getTpBta(bytes calldata tpbtaLane, uint32 tpBtaVersion) external override view returns (bytes memory) {
@@ -231,7 +244,7 @@ contract PtcHub is IPtcHub, Ownable, Initializable {
address verifier = verifierMap[AcbCommons.decodePTCCredentialSubjectFrom(ptr.ptcCrossChainCert.credentialSubject).ptcType];
require(verifier != address(0x0), "no ptc veifier set");
- bool result = IPtcVerifier(verifier).verifyTpProof(tpbta, tpProof);
+ bool result = IPtcVerifier(verifier).verifyTpProof(tpbta, tpProof, monitorVerifierAddr);
emit VerifyProof(tpbta.crossChainLane, result);
require(result, "verify not pass");
}
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/SDPMsg.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/SDPMsg.sol
index 898596c3..74a3f19c 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/SDPMsg.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/SDPMsg.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
import "./interfaces/ISDPMessage.sol";
import "./interfaces/IAuthMessage.sol";
+import "./interfaces/IContractUsingMonitor.sol";
import "./interfaces/IContractUsingSDP.sol";
import "./interfaces/IContractWithAcks.sol";
import "./lib/sdp/SDPLib.sol";
@@ -17,6 +18,8 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
using SDPLib for SDPMessageV3;
using SDPLib for BlockState;
+ address public monitorAddress;
+
address public amAddress;
bytes32 public localDomainHash;
@@ -37,6 +40,11 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
*/
mapping(bytes32 => bool) sendSDPV3Msgs;
+ modifier onlyMonitorMsg() {
+ require(monitorAddress == msg.sender, "SDPMsg: not valid monitor contract");
+ _;
+ }
+
modifier onlyAM() {
require(
amAddress == msg.sender,
@@ -53,6 +61,15 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
_transferOwnership(_msgSender());
}
+ function setMonitorContract(address newMonitorAddress) override external onlyOwner {
+ require(newMonitorAddress != address(0), "SDPMsg: invalid monitor contract");
+ monitorAddress = newMonitorAddress;
+ }
+
+ function getMonitorAddress() external view returns (address) {
+ return monitorAddress;
+ }
+
function setAmContract(address newAmContract) override external onlyOwner {
require(newAmContract != address(0), "SDPMsg: invalid am contract");
amAddress = newAmContract;
@@ -70,7 +87,7 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
return localDomainHash;
}
- function sendMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) override external {
+ function sendMessage(string calldata receiverDomain, bytes32 receiverID, address senderID, bytes calldata message) override external onlyMonitorMsg {
_beforeSend(receiverDomain, receiverID, message);
SDPMessage memory sdpMessage = SDPMessage(
@@ -78,18 +95,18 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
receiveDomain: receiverDomain,
receiver: receiverID,
message: message,
- sequence: _getAndUpdateSendSeq(receiverDomain, msg.sender, receiverID)
+ sequence: _getAndUpdateSendSeq(receiverDomain, senderID, receiverID)
}
);
bytes memory rawMsg = sdpMessage.encode();
- IAuthMessage(amAddress).recvFromProtocol(msg.sender, rawMsg);
+ IAuthMessage(amAddress).recvFromProtocol(senderID, rawMsg);
_afterSend();
}
- function sendUnorderedMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) override external {
+ function sendUnorderedMessage(string calldata receiverDomain, bytes32 receiverID, address senderID, bytes calldata message) override external onlyMonitorMsg {
_beforeSendUnordered(receiverDomain, receiverID, message);
SDPMessage memory sdpMessage = SDPMessage(
@@ -101,7 +118,7 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
}
);
- IAuthMessage(amAddress).recvFromProtocol(msg.sender, sdpMessage.encode());
+ IAuthMessage(amAddress).recvFromProtocol(senderID, sdpMessage.encode());
_afterSendUnordered();
}
@@ -291,7 +308,7 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
errMsg = "receiver has no code";
} else {
try
- IContractUsingSDP(receiver).recvMessage(senderDomain, senderID, sdpMessage.message)
+ IContractUsingMonitor(monitorAddress).recvMessageFromSDP(senderDomain, senderID, receiver, sdpMessage.message)
{
res = true;
} catch Error(
@@ -307,8 +324,8 @@ contract SDPMsg is ISDPMessage, Ownable, Initializable {
}
function _routeUnorderedMessage(string calldata senderDomain, bytes32 senderID, SDPMessage memory sdpMessage) internal {
- IContractUsingSDP(sdpMessage.getReceiverAddress())
- .recvUnorderedMessage(senderDomain, senderID, sdpMessage.message);
+ IContractUsingMonitor(monitorAddress)
+ .recvUnorderedMessageFromSDP(senderDomain, senderID, sdpMessage.getReceiverAddress(), sdpMessage.message);
}
function _processSDPv2(string calldata senderDomain, bytes32 senderID, bytes memory pkg) internal {
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IContractUsingMonitor.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IContractUsingMonitor.sol
new file mode 100755
index 00000000..621d055b
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IContractUsingMonitor.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+// 供dapp合约编写的接口 由监管合约向上传递消息时调用
+interface IContractUsingMonitor {
+
+ function recvUnorderedMessageFromSDP(string memory senderDomain, bytes32 author, address receiverID, bytes memory message) external;
+
+ function recvMessageFromSDP(string memory senderDomain, bytes32 author, address receiverID, bytes memory message) external;
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitor.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitor.sol
new file mode 100755
index 00000000..d3a44144
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitor.sol
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "../lib/monitor/MonitorLib.sol";
+
+interface IMonitor {
+ // 发送方-监管通过 监管不通过
+ event MonitorApproval(
+ bytes32 senderID,
+ string receiverDomain,
+ bytes32 receiverID
+ );
+
+ event MonitorDisapproval(
+ bytes32 senderID,
+ string receiverDomain,
+ bytes32 receiverID,
+ uint8 monitorMainType,
+ uint8 monitorSubType
+ );
+
+ // 收到监管指令
+ event receiveMonitorOrder(
+ uint32 monitorType,
+ string senderDomain,
+ bytes32 sender,
+ string receiverDomain,
+ bytes32 receiver,
+ string transactionContent,
+ string extra
+ );
+
+ // 收到监管回滚消息
+ event receiveMonitorRollbackMessage(
+ uint32 monitorType,
+ string senderDomain,
+ bytes32 sender,
+ bytes32 receiver,
+ string monitorMsg
+ );
+
+ // 监管不通过 构造监管回滚消息
+ event sendMonitorRollbakMessage(
+ uint32 monitorType,
+ bytes32 sender,
+ string receiverDomain,
+ bytes32 receiver,
+ string monitorMsg
+ );
+
+ // 验证监管节点签名
+ event VerifyMonitorNodeProofMessage(
+ string senderDomain,
+ bytes32 author,
+ address receiverID,
+ bool result
+ );
+
+ event VerifyMonitorOrder(
+ string committeeId,
+ bool result
+ );
+
+ // send接口:供dapp合约调用
+ function sendMonitorMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) external;
+
+ function sendUnorderedMonitorMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) external;
+
+ function recvMonitorOrder(string calldata committeeId, string calldata signAlgo, bytes memory proof, bytes memory rawMonitorOrder) external;
+
+ // 其他
+ function setProtocol(address protocolAddress) external;
+
+ function setMonitorVerifier(address newMonitorVerifierAddress) external;
+
+ function setMonitorControl(uint32 monitorType) external;
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitorVerifier.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitorVerifier.sol
new file mode 100755
index 00000000..7106fb5d
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IMonitorVerifier.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+pragma experimental ABIEncoderV2;
+
+import "../lib/ptc/CommitteeLib.sol";
+import "../lib/commons/AcbCommons.sol";
+
+interface IMonitorVerifier {
+
+ struct MonitorNodeProofMessage {
+ CrossChainLane crossChainLane;
+ string committeeId;
+ CommitteeNodeProof monitorNodeProof;
+ bytes encodedToSign;
+ }
+
+ event UpdateMonitorNodeEndorseInfo(string committeeId, string nodeId, bytes rawPublicKey);
+
+ function setPtcHubAddress(address newPtcHubAddress) external;
+
+ function updateMonitorNodeEndorseInfo(bytes memory rawEndorseRoot) external;
+
+ function receiveMonitorNodeProofMessage(
+ CrossChainLane calldata newCrossChainLane,
+ string calldata newCommitteeId,
+ CommitteeNodeProof calldata newMonitorNodeProof,
+ bytes calldata encodedToSign) external;
+
+ function verifyMonitorNodeProofMessage() external returns (bool);
+
+ function verifyMonitorOrder(string calldata committeeId, string calldata signAlgo, bytes calldata rawProof, bytes calldata rawMonitorOrder) external returns (bool);
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcHub.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcHub.sol
index ea01ca0b..6cedecc4 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcHub.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcHub.sol
@@ -21,6 +21,8 @@ interface IPtcHub {
event VerifyProof(CrossChainLane tpbtaLane, bool result);
+ function setMonitorVerifier(address newMonitorVerifierAddr) external;
+
function updatePTCTrustRoot(bytes calldata rawPtcTrustRoot) external;
function getPTCTrustRoot(bytes calldata ptcOwnerOid) external view returns (bytes memory);
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcVerifier.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcVerifier.sol
index 7c0a8b78..558cdc53 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcVerifier.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/IPtcVerifier.sol
@@ -8,7 +8,7 @@ interface IPtcVerifier {
function verifyTpBta(PTCVerifyAnchor calldata va, TpBta calldata tpBta) external returns (bool);
- function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof) external returns (bool);
+ function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof, address monitorPtcAddr) external returns (bool);
function myPtcType() external returns (PTCTypeEnum);
}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISDPMessage.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISDPMessage.sol
index 915d9826..335c79b7 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISDPMessage.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISDPMessage.sol
@@ -129,7 +129,7 @@ interface ISDPMessage is ISubProtocol {
* @param receiverID the address of the receiver.
* @param message the raw message from DApp contracts
*/
- function sendMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) external;
+ function sendMessage(string calldata receiverDomain, bytes32 receiverID, address senderID, bytes calldata message) external;
/**
* @dev Smart contracts call this method to send cross-chain messages out of order in SDPv2.
@@ -154,7 +154,7 @@ interface ISDPMessage is ISubProtocol {
* @param receiverID the address of the receiver.
* @param message the raw message from DApp contracts
*/
- function sendUnorderedMessage(string calldata receiverDomain, bytes32 receiverID, bytes calldata message) external;
+ function sendUnorderedMessage(string calldata receiverDomain, bytes32 receiverID, address senderID, bytes calldata message) external;
/**
* @dev Query the current sdp message sequence for the channel identited by `senderDomain`,
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISubProtocol.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISubProtocol.sol
index 4d25f4a1..53492ad9 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISubProtocol.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/interfaces/ISubProtocol.sol
@@ -18,4 +18,6 @@ interface ISubProtocol {
* @param newAmContract the address of the AuthMessage contract.
*/
function setAmContract(address newAmContract) external;
+
+ function setMonitorContract(address newMonitorAddress) external;
}
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/monitor/MonitorLib.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/monitor/MonitorLib.sol
new file mode 100755
index 00000000..a05714d4
--- /dev/null
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/monitor/MonitorLib.sol
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "../utils/TypesToBytes.sol";
+import "../utils/BytesToTypes.sol";
+import "../utils/SizeOf.sol";
+import "../utils/TLVUtils.sol";
+import "../../@openzeppelin/contracts/utils/Strings.sol";
+
+struct MonitorOrder {
+ string product;
+ string domain;
+ uint32 monitorOrderType;
+ string senderDomain;
+ bytes32 sender;
+ string receiverDomain;
+ bytes32 receiver;
+ string transactionContent;
+ string extra;
+}
+
+struct MonitorMessage {
+ uint32 monitorType;
+ string monitorMsg;
+ bytes message;
+}
+
+library MonitorLib {
+ // 监管字段(monitorType值)
+ uint32 constant public MONITOR_CLOSE = 1;
+ uint32 constant public MONITOR_OPEN = 2;
+ uint32 constant public MONITOR_ROLLBACK = 3;
+
+ // 监管指令类型与子类型(32bit 每4bit为间隔 4bit中前1bit表示主类型 后3bit为子类型)
+ // 0000 0000 0000 0000 0000 0000 0000 0000: 从左到右共 8 个主类型
+ // 第一个主类型-地址相关
+ uint8 constant public MAJOR_TYPE_CONTRACT_ADDRESS = 1;
+ // 子类型-黑名单
+ uint8 constant public MINOR_TYPE_ADD_TO_BLACKLIST = 0;
+ uint8 constant public MINOR_TYPE_REMOVE_FROM_BLACKLIST = 1;
+
+ // 第二个主类型-控制相关
+ uint8 constant public MAJOR_TYPE_CONTROL = 1;
+ // 子类型-是否开启监管
+ uint8 constant public MINOR_TYPE_MONITOR_CLOSE = 0;
+ uint8 constant public MINOR_TYPE_MONITOR_OPEN = 1;
+
+ // 事前监管失败类型
+ uint8 constant public SENDER_IN_BLACKLIST = 0;
+ uint8 constant public RECEIVER_IN_BLACKLIST = 1;
+
+ function encodeAddressIntoCrossChainID(address _address) internal pure returns (bytes32) {
+ bytes32 id = TypesToBytes.addressToBytes32(_address);
+ return id;
+ }
+
+ function encodeCrossChainIDIntoAddress(bytes32 id) pure internal returns (address) {
+ bytes memory rawId = new bytes(32);
+ TypesToBytes.bytes32ToBytes(32, id, rawId);
+ return BytesToTypes.bytesToAddress(32, rawId);
+ }
+
+ function decode(MonitorOrder memory monitorOrder, bytes memory rawMessage) internal pure {
+ uint256 offset = rawMessage.length;
+
+ bytes memory raw_product = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.product = string(raw_product);
+ offset -= 4 + raw_product.length;
+
+ bytes memory raw_domain = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.domain = string(raw_domain);
+ offset -= 4 + raw_domain.length;
+
+ monitorOrder.monitorOrderType = BytesToTypes.bytesToUint32(offset, rawMessage);
+ offset -= 4;
+
+ bytes memory raw_send_domain = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.senderDomain = string(raw_send_domain);
+ offset -= 4 + raw_send_domain.length;
+
+ monitorOrder.sender = BytesToTypes.bytesToBytes32(offset, rawMessage);
+ offset -= SizeOf.sizeOfBytes32();
+
+ bytes memory raw_recv_domain = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.receiverDomain = string(raw_recv_domain);
+ offset -= 4 + raw_recv_domain.length;
+
+ monitorOrder.receiver = BytesToTypes.bytesToBytes32(offset, rawMessage);
+ offset -= SizeOf.sizeOfBytes32();
+
+ bytes memory raw_tran_cont = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.transactionContent = string(raw_tran_cont);
+ offset -= 4 + raw_tran_cont.length;
+
+ bytes memory raw_extra = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorOrder.extra = string(raw_extra);
+ offset -= 4 + raw_extra.length;
+ }
+
+ function encode(MonitorMessage memory monitorMessage) pure internal returns (bytes memory) {
+ require(
+ monitorMessage.message.length <= 0xFFFFFFFF,
+ "encodeSDPMessage: body length overlimit"
+ );
+ // 4 + (4 + monitorMsg) + (4 + payload)
+ uint total_size = 12 + bytes(monitorMessage.monitorMsg).length + monitorMessage.message.length;
+ bytes memory pkg = new bytes(total_size);
+ uint offset = total_size;
+
+ TypesToBytes.uint32ToBytes(offset, monitorMessage.monitorType, pkg);
+ offset -= SizeOf.sizeOfUint(32);
+
+ bytes memory raw_monitorMsg = bytes(monitorMessage.monitorMsg);
+ TypesToBytes.varBytesToBytes(offset, raw_monitorMsg, pkg);
+ offset -= 4 + raw_monitorMsg.length;
+
+ TypesToBytes.varBytesToBytes(offset, monitorMessage.message, pkg);
+ offset -= 4 + monitorMessage.message.length;
+
+ return pkg;
+ }
+
+ function decode(MonitorMessage memory monitorMessage, bytes memory rawMessage) internal pure {
+ uint256 offset = rawMessage.length;
+
+ monitorMessage.monitorType = BytesToTypes.bytesToUint32(offset, rawMessage);
+ offset -= SizeOf.sizeOfInt(32);
+
+ bytes memory raw_monitorMsg = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ monitorMessage.monitorMsg = string(raw_monitorMsg);
+ offset -= 4 + raw_monitorMsg.length;
+
+ monitorMessage.message = BytesToTypes.bytesToVarBytes(offset, rawMessage);
+ offset -= 4 + monitorMessage.message.length;
+ }
+}
\ No newline at end of file
diff --git a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/ptc/CommitteeLib.sol b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/ptc/CommitteeLib.sol
index 4212e9f9..34ed885c 100644
--- a/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/ptc/CommitteeLib.sol
+++ b/acb-sdk/pluginset/ethereum2/onchain-plugin/solidity/sys/lib/ptc/CommitteeLib.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./PtcLib.sol";
+import "../../interfaces/IMonitorVerifier.sol";
import "../../@openzeppelin/contracts/utils/Strings.sol";
// tags for CommitteeEndorseRoot
@@ -94,6 +95,8 @@ library CommitteeLib {
event DoVeriyTpBta(CrossChainLane laneKey, string committeeId, string nodeId, bool result);
event DoVeriyTpProof(CrossChainLane laneKey, string committeeId, string nodeId, bool result);
+ event DoVerifyTpProofFromMonitorNode(CrossChainLane laneKey, string committeeId, string nodeId, bool result);
+ event DoVerifyMonitorOrder(string committeeId, string nodeId, bool res);
using TLVUtils for TLVPacket;
using TLVUtils for TLVItem;
@@ -162,7 +165,7 @@ library CommitteeLib {
return 3 * correct > 2 * cva.anchors.length;
}
- function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof) internal returns (bool) {
+ function verifyTpProof(TpBta memory tpBta, ThirdPartyProof memory tpProof , address monitorPtcAddr) internal returns (bool) {
CommitteeEndorseRoot memory cer = decodeCommitteeEndorseRootFrom(tpBta.endorseRoot);
CommitteeEndorseProof memory ceProof = decodeCommitteeEndorseProofFrom(tpProof.rawProof);
@@ -173,9 +176,17 @@ library CommitteeLib {
for (uint i = 0; i < cer.endorsers.length; i++)
{
NodeEndorseInfo memory info = cer.endorsers[i];
+ bool isMonitorNode = false;
bool res = false;
for (uint j = 0; j < ceProof.sigs.length; j++) {
if (info.nodeId.equal(ceProof.sigs[j].nodeId)) {
+ // if it is monitor node proof, trans it to MonitorPTC
+ if (checkMonitorNode(info.nodeId)) {
+ isMonitorNode = true;
+ IMonitorVerifier(monitorPtcAddr).receiveMonitorNodeProofMessage(tpBta.crossChainLane, cer.committeeId, ceProof.sigs[j], encodedToSign);
+ res = true;
+ break;
+ }
res = AcbCommons.verifySig(
ceProof.sigs[j].signAlgo,
info.publicKey.getRawPublicKey(),
@@ -188,8 +199,9 @@ library CommitteeLib {
}
}
}
-
- emit DoVeriyTpProof(tpBta.crossChainLane, cer.committeeId, info.nodeId, res);
+ if (!isMonitorNode) {
+ emit DoVeriyTpProof(tpBta.crossChainLane, cer.committeeId, info.nodeId, res);
+ }
if (!res && info.required) {
return false;
}
@@ -198,6 +210,59 @@ library CommitteeLib {
return cer.policy.threshold.check(optinalCorrect);
}
+ function checkMonitorNode(string memory nodeId) internal pure returns (bool) {
+ bytes memory prefix = bytes("monitor");
+ bytes memory nodeIdBytes = bytes(nodeId);
+
+ if (nodeIdBytes.length < prefix.length) {
+ return false;
+ }
+
+ for (uint i = 0; i < prefix.length; i++) {
+ if (nodeIdBytes[i] != prefix[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function verifyTpProofFromMonitorNode(
+ CrossChainLane memory crossChainLane,
+ string memory committeeId,
+ NodeEndorseInfo memory monitorNodeEndorseInfo,
+ CommitteeNodeProof memory monitorNodeProof,
+ bytes memory encodedToSign
+ ) internal returns (bool) {
+ bool res = false;
+ res = AcbCommons.verifySig(
+ monitorNodeProof.signAlgo,
+ monitorNodeEndorseInfo.publicKey.getRawPublicKey(),
+ encodedToSign,
+ monitorNodeProof.signature
+ );
+ emit DoVerifyTpProofFromMonitorNode(crossChainLane, committeeId, monitorNodeEndorseInfo.nodeId, res);
+ return res;
+ }
+
+ function verifyMonitorOrder(
+ string memory committeeId,
+ NodeEndorseInfo memory monitorNodeEndorseInfo,
+ string memory signAlgo,
+ bytes memory rawProof,
+ bytes memory rawMonitorOrder
+ ) internal returns (bool) {
+ bool res = false;
+ res = AcbCommons.verifySig(
+ signAlgo,
+ monitorNodeEndorseInfo.publicKey.getRawPublicKey(),
+ rawMonitorOrder,
+ rawProof
+ );
+ emit DoVerifyMonitorOrder(committeeId, monitorNodeEndorseInfo.nodeId, res);
+ return res;
+ }
+
function getRawPublicKey(
NodePublicKeyEntry memory entry
) internal pure returns (bytes memory) {
diff --git "a/docs/images/\345\220\253\347\233\221\347\256\241\347\232\204\350\267\250\351\223\276\346\265\201\347\250\213\345\233\276.png" "b/docs/images/\345\220\253\347\233\221\347\256\241\347\232\204\350\267\250\351\223\276\346\265\201\347\250\213\345\233\276.png"
new file mode 100644
index 0000000000000000000000000000000000000000..66f099487bc261352e6ec4ad7d8905a9f2cd6fba
GIT binary patch
literal 385054
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