diff --git a/pom.xml b/pom.xml index 2ac7df4..23fd809 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,6 @@ r-bootstrap r-dal r-cli - r-service r-core r-engine r-server @@ -41,6 +40,11 @@ 4.13.2 2.2.224 0.7.3 + 0.6.1 + 3.19.1 + 1.42.2 + 1.76 + 4.4 @@ -76,6 +80,11 @@ spring-boot-starter-data-jdbc ${spring-boot.version} + + org.springframework.boot + spring-boot-starter-web-services + ${spring-boot.version} + com.mysql mysql-connector-j @@ -127,6 +136,21 @@ + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + com.alipay.antchain.bridge @@ -143,11 +167,6 @@ r-core ${project.version} - - com.alipay.antchain.bridge - r-service - ${project.version} - com.alipay.antchain.bridge r-engine @@ -168,6 +187,21 @@ lombok ${lombok.version} + + org.bouncycastle + bcprov-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + diff --git a/r-bootstrap/.gitignore b/r-bootstrap/.gitignore index 5ff6309..cb07487 100644 --- a/r-bootstrap/.gitignore +++ b/r-bootstrap/.gitignore @@ -35,4 +35,8 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +src/main/resources/cc_certs/** + +src/main/resources/node_keys/** \ No newline at end of file diff --git a/r-bootstrap/pom.xml b/r-bootstrap/pom.xml index ce4f7be..ba7a5da 100644 --- a/r-bootstrap/pom.xml +++ b/r-bootstrap/pom.xml @@ -44,10 +44,6 @@ com.alipay.antchain.bridge r-server - - com.alipay.antchain.bridge - r-service - org.springframework.boot spring-boot-starter-test @@ -74,6 +70,87 @@ junit test + + com.github.kstyrc + embedded-redis + 0.6 + test + + + + + src/main/resources + + **/*.yml + **/*.xml + **/banner.txt + node_keys/** + cc_certs/** + + + true + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + ../target/boot + + + + + repackage + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + jks + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/AntChainBridgeRelayerApplication.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/AntChainBridgeRelayerApplication.java index 43182ff..ea61e6a 100644 --- a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/AntChainBridgeRelayerApplication.java +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/AntChainBridgeRelayerApplication.java @@ -16,6 +16,9 @@ package com.alipay.antchain.bridge.relayer.bootstrap; +import java.security.Security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -26,6 +29,7 @@ public class AntChainBridgeRelayerApplication { public static void main(String[] args) { + Security.addProvider(new BouncyCastleProvider()); new SpringApplicationBuilder(AntChainBridgeRelayerApplication.class) .web(WebApplicationType.NONE) .run(args); diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/EngineConfig.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/EngineConfig.java new file mode 100644 index 0000000..e6e4c5e --- /dev/null +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/EngineConfig.java @@ -0,0 +1,62 @@ +/* + * 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.relayer.bootstrap.config; + +import java.util.Map; + +import cn.hutool.core.map.MapUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.DistributedTaskTypeEnum; +import com.alipay.antchain.bridge.relayer.engine.core.ScheduleContext; +import com.alipay.antchain.bridge.relayer.engine.executor.*; +import lombok.Getter; +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 +@Getter +public class EngineConfig { + + @Value("${relayer.engine.node_id_mode:IP}") + private String nodeIdMode; + + @Bean + public ScheduleContext scheduleContext() { + return new ScheduleContext(nodeIdMode); + } + + @Bean + @Autowired + public Map scheduleTaskExecutorMap( + AnchorScheduleTaskExecutor anchorScheduleTaskExecutor, + CommitterScheduleTaskExecutor committerScheduleTaskExecutor, + ProcessScheduleTaskExecutor processScheduleTaskExecutor, + TxConfirmScheduleTaskExecutor txConfirmScheduleTaskExecutor, + ArchiveScheduleTaskExecutor archiveScheduleTaskExecutor, + AsyncDeployScheduleTaskExecutor asyncDeployScheduleTaskExecutor + ) { + Map res = MapUtil.newHashMap(); + res.put(DistributedTaskTypeEnum.ANCHOR_TASK, anchorScheduleTaskExecutor); + res.put(DistributedTaskTypeEnum.COMMIT_TASK, committerScheduleTaskExecutor); + res.put(DistributedTaskTypeEnum.PROCESS_TASK, processScheduleTaskExecutor); + res.put(DistributedTaskTypeEnum.AM_CONFIRM_TASK, txConfirmScheduleTaskExecutor); + res.put(DistributedTaskTypeEnum.ARCHIVE_TASK, archiveScheduleTaskExecutor); + res.put(DistributedTaskTypeEnum.DEPLOY_SERVICE_TASK, asyncDeployScheduleTaskExecutor); + return res; + } +} diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/MemCacheConfig.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/MemCacheConfig.java new file mode 100644 index 0000000..cc26f85 --- /dev/null +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/MemCacheConfig.java @@ -0,0 +1,58 @@ +/* + * 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.relayer.bootstrap.config; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MemCacheConfig { + + @Value("${relayer.cache.domain_cert.ttl:10000}") + private long domainCertCacheTTL; + + @Value("${relayer.cache.domain_cert.ttl:3000}") + private long blockchainMetaCacheTTL; + + @Value("${relayer.cache.system_conf.ttl:30000}") + private long systemConfigCacheTTL; + + @Bean + public Cache domainCertWrapperCache() { + return CacheUtil.newLRUCache(30, domainCertCacheTTL); + } + + @Bean + public Cache blockchainMetaCache() { + return CacheUtil.newLRUCache(30, blockchainMetaCacheTTL); + } + + @Bean(name = "blockchainIdToDomainCache") + public Cache blockchainIdToDomainCache() { + return CacheUtil.newLRUCache(30); + } + + @Bean(name = "systemConfigCache") + public Cache systemConfigCache() { + return CacheUtil.newLRUCache(10, systemConfigCacheTTL); + } +} diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/RelayerCoreConfig.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/RelayerCoreConfig.java new file mode 100644 index 0000000..ec18bd2 --- /dev/null +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/RelayerCoreConfig.java @@ -0,0 +1,167 @@ +/* + * 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.relayer.bootstrap.config; + +import java.io.ByteArrayInputStream; +import java.security.PrivateKey; +import java.util.concurrent.*; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.crypto.PemUtil; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.GRpcBBCPluginManager; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.IBBCPluginManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.network.ws.WsSslFactory; +import com.alipay.antchain.bridge.relayer.dal.repository.IPluginServerRepository; +import com.alipay.antchain.bridge.relayer.server.network.WSRelayerServer; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.support.TransactionTemplate; + +@Configuration +public class RelayerCoreConfig { + + @Value("${relayer.plugin_server_manager.grpc.auth.tls.client.key.path:config/relayer.key}") + private String clientKeyPath; + + @Value("${relayer.plugin_server_manager.grpc.auth.tls.client.ca.path:config/relayer.crt}") + private String clientCaPath; + + @Value("${relayer.plugin_server_manager.grpc.thread.num:32}") + private int clientThreadNum; + + @Value("${relayer.plugin_server_manager.grpc.heartbeat.thread.num:4}") + private int clientHeartbeatThreadNum; + + @Value("${relayer.plugin_server_manager.grpc.heartbeat.delayed_time:5000}") + private long heartbeatDelayedTime; + + @Value("${relayer.plugin_server_manager.grpc.heartbeat.error_limit:5}") + private int errorLimitForHeartbeat; + + @Value("${relayer.network.node.crosschain_cert_path:null}") + private String relayerCrossChainCertPath; + + @Value("${relayer.network.node.private_key_path}") + private String relayerPrivateKeyPath; + + @Value("${relayer.network.node.server.mode:https}") + private String localNodeServerMode; + + @Value("${relayer.network.node.server.port:8082}") + private int localNodeServerPort; + + @Value("#{systemConfigRepository.defaultNetworkId}") + private String defaultNetworkId; + + @Value("${relayer.network.node.server.as_discovery:false}") + private boolean isDiscoveryService; + + public AbstractCrossChainCertificate getLocalRelayerCrossChainCertificate() { + AbstractCrossChainCertificate relayerCertificate = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes(relayerCrossChainCertPath) + ); + Assert.equals( + CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, + relayerCertificate.getType() + ); + return relayerCertificate; + } + + public RelayerCredentialSubject getLocalRelayerCredentialSubject() { + return RelayerCredentialSubject.decode(getLocalRelayerCrossChainCertificate().getCredentialSubject()); + } + + public PrivateKey getLocalPrivateKey() { + return PemUtil.readPemPrivateKey(new ByteArrayInputStream(FileUtil.readBytes(relayerPrivateKeyPath))); + } + + public String getLocalRelayerNodeId() { + return RelayerNodeInfo.calculateNodeId(getLocalRelayerCrossChainCertificate()); + } + + @Bean + @Autowired + public IBBCPluginManager bbcPluginManager( + IPluginServerRepository pluginServerRepository, + TransactionTemplate transactionTemplate + ) { + return new GRpcBBCPluginManager( + clientKeyPath, + clientCaPath, + pluginServerRepository, + transactionTemplate, + new ThreadPoolExecutor( + clientThreadNum, + clientThreadNum, + 3000, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(clientThreadNum * 20), + new ThreadFactoryBuilder().setNameFormat("plugin_manager-grpc-%d").build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ), + new ScheduledThreadPoolExecutor( + clientHeartbeatThreadNum, + new ThreadFactoryBuilder().setNameFormat("plugin_manager-heartbeat-%d").build() + ), + heartbeatDelayedTime, + errorLimitForHeartbeat + ); + } + + @Bean + @Autowired + public WSRelayerServer wsRelayerServer( + @Qualifier("wsRelayerServerExecutorService") ExecutorService wsRelayerServerExecutorService, + WsSslFactory wsSslFactory, + IRelayerNetworkManager relayerNetworkManager, + IRelayerCredentialManager relayerCredentialManager, + ReceiverService receiverService + ) { + try { + return new WSRelayerServer( + localNodeServerMode, + localNodeServerPort, + defaultNetworkId, + wsRelayerServerExecutorService, + wsSslFactory, + relayerNetworkManager, + relayerCredentialManager, + receiverService, + isDiscoveryService + ); + } catch (Exception e) { + throw new BeanInitializationException( + "failed to initialize bean wsRelayerServer", + e + ); + } + } +} diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/ThreadsConfig.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/ThreadsConfig.java new file mode 100644 index 0000000..0b9dfd0 --- /dev/null +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/ThreadsConfig.java @@ -0,0 +1,258 @@ +/* + * 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.relayer.bootstrap.config; + +import java.util.concurrent.*; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ThreadsConfig { + + @Value("${relayer.network.node.server.threads.core_size:32}") + private int wsRelayerServerCoreSize; + + @Value("${relayer.network.node.server.threads.total_size:64}") + private int wsRelayerServerTotalSize; + + @Value("${relayer.network.node.client.threads.core_size:32}") + private int wsRelayerClientCoreSize; + + @Value("${relayer.network.node.client.threads.total_size:64}") + private int wsRelayerClientTotalSize; + + @Value("${relayer.service.process.threads.core_size:32}") + private int processServiceCoreSize; + + @Value("${relayer.service.process.threads.total_size:64}") + private int processServiceTotalSize; + + @Value("${relayer.service.committer.threads.core_size:32}") + private int committerServiceCoreSize; + + @Value("${relayer.service.committer.threads.total_size:64}") + private int committerServiceTotalSize; + + @Value("${relayer.service.anchor.sync_task.threads.core_size:16}") + private int blockSyncTaskCoreSize; + + @Value("${relayer.service.anchor.sync_task.threads.total_size:256}") + private int blockSyncTaskTotalSize; + + @Value("${relayer.service.confirm.threads.core_size:16}") + private int confirmServiceCoreSize; + + @Value("${relayer.service.confirm.threads.total_size:64}") + private int confirmServiceTotalSize; + + @Value("${relayer.engine.duty.anchor.threads.core_size:16}") + private int anchorScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.anchor.threads.total_size:32}") + private int anchorScheduleTaskExecutorTotalSize; + + @Value("${relayer.engine.duty.committer.threads.core_size:16}") + private int committerScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.committer.threads.total_size:32}") + private int committerScheduleTaskExecutorTotalSize; + + @Value("${relayer.engine.duty.process.threads.core_size:16}") + private int processScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.process.threads.total_size:32}") + private int processScheduleTaskExecutorTotalSize; + + @Value("${relayer.engine.duty.confirm.threads.core_size:8}") + private int confirmScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.confirm.threads.total_size:16}") + private int confirmScheduleTaskExecutorTotalSize; + + @Value("${relayer.engine.duty.archive.threads.core_size:16}") + private int archiveScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.archive.threads.total_size:16}") + private int archiveScheduleTaskExecutorTotalSize; + + @Value("${relayer.engine.duty.deploy.threads.core_size:4}") + private int deployScheduleTaskExecutorCoreSize; + + @Value("${relayer.engine.duty.deploy.threads.total_size:8}") + private int deployScheduleTaskExecutorTotalSize; + + + @Bean(name = "wsRelayerServerExecutorService") + public ExecutorService wsRelayerServerExecutorService() { + return new ThreadPoolExecutor( + wsRelayerServerCoreSize, + wsRelayerServerTotalSize, + 0L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(10000), + new ThreadFactoryBuilder().setNameFormat("ws-relayer-server-worker-%d").build(), + new ThreadPoolExecutor.AbortPolicy() + ); + } + + @Bean(name = "wsRelayerClientThreadsPool") + public ExecutorService wsRelayerClientThreadsPool() { + return new ThreadPoolExecutor( + wsRelayerClientCoreSize, + wsRelayerClientTotalSize, + 0L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(10000), + new ThreadFactoryBuilder().setNameFormat("ws-relayer-client-worker-%d").build(), + new ThreadPoolExecutor.AbortPolicy() + ); + } + + @Bean(name = "processServiceThreadsPool") + public ExecutorService processServiceThreadsPool() { + return new ThreadPoolExecutor( + processServiceCoreSize, + processServiceTotalSize, + 1000, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(10000), + new ThreadFactoryBuilder().setNameFormat("Process-worker-%d").build(), + new ThreadPoolExecutor.AbortPolicy() + ); + } + + @Bean(name = "committerServiceThreadsPool") + public ExecutorService committerServiceThreadsPool() { + return new ThreadPoolExecutor( + committerServiceCoreSize, + committerServiceTotalSize, + 5000L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(10000), + new ThreadFactoryBuilder().setNameFormat("Committer-worker-%d").build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + + @Bean(name = "blockSyncTaskThreadsPool") + public ExecutorService blockSyncTaskThreadsPool() { + return new ThreadPoolExecutor( + blockSyncTaskCoreSize, + blockSyncTaskTotalSize, + 5000L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(1280), + new ThreadFactoryBuilder().setNameFormat("BlockSyncTask-worker-%d").build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + + @Bean(name = "confirmServiceThreadsPool") + public ExecutorService confirmServiceThreadsPool() { + return new ThreadPoolExecutor( + confirmServiceCoreSize, + confirmServiceTotalSize, + 5000L, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(1000), + new ThreadFactoryBuilder().setNameFormat("AMConfirm-worker-%d").build(), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + + @Bean(name = "distributedTaskEngineScheduleThreadsPool") + public ScheduledExecutorService distributedTaskEngineScheduleThreadsPool() { + return new ScheduledThreadPoolExecutor( + 4, + new ThreadFactoryBuilder().setNameFormat("ScheduleEngine-Executor-%d").build() + ); + } + + @Bean(name = "anchorScheduleTaskExecutorThreadsPool") + public ExecutorService anchorScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + anchorScheduleTaskExecutorCoreSize, + anchorScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("anchor_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } + + @Bean(name = "committerScheduleTaskExecutorThreadsPool") + public ExecutorService committerScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + committerScheduleTaskExecutorCoreSize, + committerScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("committer_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } + + @Bean(name = "processScheduleTaskExecutorThreadsPool") + public ExecutorService processScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + processScheduleTaskExecutorCoreSize, + processScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("process_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } + + @Bean(name = "confirmScheduleTaskExecutorThreadsPool") + public ExecutorService confirmScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + confirmScheduleTaskExecutorCoreSize, + confirmScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("confirm_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } + + @Bean(name = "archiveScheduleTaskExecutorThreadsPool") + public ExecutorService archiveScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + archiveScheduleTaskExecutorCoreSize, + archiveScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("archive_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } + + @Bean(name = "deployScheduleTaskExecutorThreadsPool") + public ExecutorService deployScheduleTaskExecutorThreadsPool() { + return new ThreadPoolExecutor( + deployScheduleTaskExecutorCoreSize, + deployScheduleTaskExecutorTotalSize, + 0, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(100), + new ThreadFactoryBuilder().setNameFormat("deploy_executor-worker-%d").build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + } +} diff --git a/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/WebServiceConfig.java b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/WebServiceConfig.java new file mode 100644 index 0000000..3cecedf --- /dev/null +++ b/r-bootstrap/src/main/java/com/alipay/antchain/bridge/relayer/bootstrap/config/WebServiceConfig.java @@ -0,0 +1,55 @@ +/* + * 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.relayer.bootstrap.config; + +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.config.annotation.WsConfigurerAdapter; +import org.springframework.ws.transport.http.MessageDispatcherServlet; +import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; +import org.springframework.xml.xsd.SimpleXsdSchema; +import org.springframework.xml.xsd.XsdSchema; + +//@EnableWs +//@Configuration +public class WebServiceConfig extends WsConfigurerAdapter { + + @Bean + public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) { + MessageDispatcherServlet servlet = new MessageDispatcherServlet(); + servlet.setApplicationContext(applicationContext); + servlet.setTransformWsdlLocations(true); + return new ServletRegistrationBean<>(servlet, "/*"); + } + + @Bean(name = "WSEndpointServer") + public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema relayerNodeRequestSchema) { + DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition(); + wsdl11Definition.setPortTypeName("WSRelayerServerAPImpl"); + wsdl11Definition.setLocationUri("/WSEndpointServer"); + wsdl11Definition.setTargetNamespace("http://ws.offchainapi.oracle.mychain.alipay.com/"); + wsdl11Definition.setSchema(relayerNodeRequestSchema); + return wsdl11Definition; + } + + @Bean + public XsdSchema relayerNodeRequestSchema() { + return new SimpleXsdSchema(new ClassPathResource("relayer_node_peer.xsd")); + } +} diff --git a/r-bootstrap/src/main/resources/banner.txt b/r-bootstrap/src/main/resources/banner.txt index 141025c..68e0ea0 100644 --- a/r-bootstrap/src/main/resources/banner.txt +++ b/r-bootstrap/src/main/resources/banner.txt @@ -1,8 +1,9 @@ ${AnsiColor.BLUE}${AnsiStyle.BOLD} - ___ __ ______ __ _ ____ _ __ - / | ____ / /_ / ____// /_ ____ _ (_)____ / __ ) _____ (_)____/ /____ _ ___ - / /| | / __ \ / __// / / __ \ / __ `// // __ \ / __ |/ ___// // __ // __ `// _ \ - / ___ | / / / // /_ / /___ / / / // /_/ // // / / / / /_/ // / / // /_/ // /_/ // __/ -/_/ |_|/_/ /_/ \__/ \____//_/ /_/ \__,_//_//_/ /_/ /_____//_/ /_/ \__,_/ \__, / \___/ - /____/ - relayer v0.1 + ___ ______ ____ ____ ______ __ ___ __ __ ______ ____ + / | / ____// __ ) / __ \ / ____// / / |\ \/ // ____// __ \ + / /| | / / / __ | / /_/ // __/ / / / /| | \ // __/ / /_/ / + / ___ |/ /___ / /_/ / / _, _// /___ / /___ / ___ | / // /___ / _, _/ +/_/ |_|\____//_____/ /_/ |_|/_____//_____//_/ |_|/_//_____//_/ |_| + + relayer v0.1 +${AnsiColor.BLACK} \ No newline at end of file diff --git a/r-bootstrap/src/main/resources/db/ddl.sql b/r-bootstrap/src/main/resources/db/ddl.sql index fee450f..1a6c763 100644 --- a/r-bootstrap/src/main/resources/db/ddl.sql +++ b/r-bootstrap/src/main/resources/db/ddl.sql @@ -89,8 +89,9 @@ CREATE TABLE `domain_cert` `domain` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `blockchain_product` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `instance` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `cert_pk_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `issuer_pk_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `subject_oid` blob DEFAULT NULL, + `issuer_oid` blob DEFAULT NULL, + `domain_space` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `domain_cert` longblob, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, @@ -106,6 +107,7 @@ CREATE TABLE `domain_space_cert` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_space` varchar(128) DEFAULT NULL, + `parent_space` varchar(128) DEFAULT NULL, `description` varchar(128) DEFAULT NULL, `domain_space_cert` longblob, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, @@ -247,14 +249,16 @@ CREATE TABLE `relayer_network` drop table if exists relayer_node; CREATE TABLE `relayer_node` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `node_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `node_public_key` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `domains` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `endpoints` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, - `properties` longblob, - `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, - `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, + `id` int(11) NOT NULL AUTO_INCREMENT, + `node_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `node_crosschain_cert` binary DEFAULT NULL, + `node_sig_algo` varchar(255) DEFAULT NULL, + `domains` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `endpoints` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `blockchain_content` binary DEFAULT NULL, + `properties` longblob, + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_relayer_node` (`node_id`) ) ENGINE = InnoDB diff --git a/r-bootstrap/src/main/resources/desc_tar.xml b/r-bootstrap/src/main/resources/desc_tar.xml new file mode 100644 index 0000000..450fcce --- /dev/null +++ b/r-bootstrap/src/main/resources/desc_tar.xml @@ -0,0 +1,40 @@ + + ${version} + + tar.gz + + true + + + ${project.basedir}/../target/boot + ${file.separator}lib + + ${artifactId}-${version}.jar + + + + ${project.basedir}/src/main/resources/scripts + ${file.separator}bin + + *.sh + plugin-server.service + + + + ${project.basedir}/src/main/resources + ${file.separator}config + + application.yml + + + + ${project.basedir}/../ + ${file.separator} + + README.md + + + + \ No newline at end of file diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/TestBase.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/TestBase.java index 0e75549..55a4346 100644 --- a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/TestBase.java +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/TestBase.java @@ -4,6 +4,13 @@ */ package com.alipay.antchain.bridge.relayer.bootstrap; +import cn.hutool.core.io.FileUtil; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject; +import com.alipay.antchain.bridge.relayer.bootstrap.basic.BlockchainModelsTest; +import com.alipay.antchain.bridge.relayer.bootstrap.utils.MyRedisServer; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.runner.RunWith; @@ -11,6 +18,9 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit4.SpringRunner; +import redis.embedded.RedisExecProvider; +import redis.embedded.util.Architecture; +import redis.embedded.util.OS; @ActiveProfiles("test") @RunWith(SpringRunner.class) @@ -19,53 +29,54 @@ @Sql(scripts = "classpath:data/drop_all.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) public abstract class TestBase { - @BeforeClass - public static void before() throws Exception { + public static final BlockchainMeta.BlockchainProperties blockchainProperties1 + = BlockchainMeta.BlockchainProperties.decode(BlockchainModelsTest.BLOCKCHAIN_META_EXAMPLE_OBJ.getBytes()); - } + public static final BlockchainMeta.BlockchainProperties blockchainProperties2 + = BlockchainMeta.BlockchainProperties.decode(BlockchainModelsTest.BLOCKCHAIN_META_EXAMPLE_OBJ.getBytes()); - @AfterClass - public static void after() throws Exception { + public static final BlockchainMeta.BlockchainProperties blockchainProperties3 + = BlockchainMeta.BlockchainProperties.decode(BlockchainModelsTest.BLOCKCHAIN_META_EXAMPLE_OBJ.getBytes()); - } + public static final BlockchainMeta testchain1Meta = new BlockchainMeta("testchain", "testchain_1.id", "", "", blockchainProperties1); + + public static final BlockchainMeta testchain2Meta = new BlockchainMeta("testchain", "testchain_2.id", "", "", blockchainProperties2); + + public static final BlockchainMeta testchain3Meta = new BlockchainMeta("testchain", "testchain_3.id", "", "", blockchainProperties3); + + public static AbstractCrossChainCertificate antchainDotCommCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/antchain.com.crt") + ); + + public static AbstractCrossChainCertificate catchainDotCommCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/catchain.com.crt") + ); - public void test() throws Exception { -// blockchainService.getBaseMapper().insertBlockchain("test", "test", "test", "test", new byte[]{1, 2, 3}); -// -// System.out.println(blockchainService.list().get(0).getGmtModified()); -// -// Thread.sleep(2_000); -// -//// blockchainService.lambdaUpdate() -//// .set(BlockchainEntity::getAlias, "") -//// .set(BlockchainEntity::getDesc, "") -//// .eq(BlockchainEntity::getProduct, "test") -//// .eq(BlockchainEntity::getBlockchainId, "test") -//// .update(); -// -// blockchainService.getBaseMapper().update( -// BlockchainEntity.builder() -// .desc("test123") -// .build(), -// new LambdaQueryWrapper() -// .eq(BlockchainEntity::getBlockchainId, "test") -// ); -// -// System.out.println(blockchainService.list().get(0).getGmtModified()); -// -// JSON.parseObject(new byte[]{}, BlockchainMeta.class); -// -// System.out.println(blockchainService.count()); - -// pluginServerObjectsMapper.insertPluginServer( -// PluginServerObjectsEntity.builder() -// .state(PluginServerStateEnum.NOT_FOUND) -// .psId("test") -// .address("test") -// .build() -// ); -// -// List entity = pluginServerObjectsMapper.selectList(null); + public static AbstractCrossChainCertificate dogchainDotCommCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/dogchain.com.crt") + ); + + public DomainNameCredentialSubject antchainSubject = DomainNameCredentialSubject.decode(antchainDotCommCert.getCredentialSubject()); + + public DomainNameCredentialSubject catchainSubject = DomainNameCredentialSubject.decode(catchainDotCommCert.getCredentialSubject()); + + public DomainNameCredentialSubject dogchainSubject = DomainNameCredentialSubject.decode(dogchainDotCommCert.getCredentialSubject()); + + public static MyRedisServer redisServer; + + @BeforeClass + public static void beforeTest() throws Exception { + redisServer = new MyRedisServer( + RedisExecProvider.defaultProvider() + .override(OS.MAC_OS_X, Architecture.x86_64, "/usr/local/bin/redis-server") + .override(OS.MAC_OS_X, Architecture.x86, "/usr/local/bin/redis-server"), + 6379 + ); + redisServer.start(); } + @AfterClass + public static void after() throws Exception { + redisServer.stop(); + } } diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/BlockchainModelsTest.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/BlockchainModelsTest.java new file mode 100644 index 0000000..3f1258c --- /dev/null +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/BlockchainModelsTest.java @@ -0,0 +1,346 @@ +/* + * 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.relayer.bootstrap.basic; + +import java.io.ByteArrayInputStream; +import java.security.PrivateKey; +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.collection.ListUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.crypto.PemUtil; +import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject; +import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil; +import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain; +import com.alipay.antchain.bridge.relayer.commons.constant.OnChainServiceStatusEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainContent; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainInfo; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class BlockchainModelsTest { + + private static final String BLOCKCHAIN_META_EXAMPLE = "{\n" + + " \"init_block_height\" : \"13947633\",\n" + + " \"anchor_runtime_status\" : \"RUNNING\",\n" + + " \"sdp_msg_contract_address\" : \"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"is_domain_registered\" : \"true\",\n" + + " \"heterogeneous_bbc_context\" : \"{\\\"am_contract\\\":{\\\"contractAddress\\\":\\\"0x72e82e6aa48fca141ceb5914382be199fa514f96\\\",\\\"status\\\":\\\"CONTRACT_READY\\\"},\\\"raw_conf\\\":\\\"eyJ0ZXN0IjoidGVzdCJ9\\\",\\\"sdp_contract\\\":{\\\"contractAddress\\\":\\\"0x098310f3921eb1f7488ee169298e92759caa4e14\\\",\\\"status\\\":\\\"CONTRACT_READY\\\"}}\",\n" + + " \"am_service_status\" : \"DEPLOY_FINISHED\",\n" + + " \"am_client_contract_address\" : \"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"plugin_server_id\" : \"p-QYj86x8Zd\"\n" + + "}"; + + public static final String BLOCKCHAIN_META_EXAMPLE_OBJ = "{\n" + + " \"init_block_height\":\"13947633\",\n" + + " \"anchor_runtime_status\":\"RUNNING\",\n" + + " \"sdp_msg_contract_address\":\"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"is_domain_registered\":\"true\",\n" + + " \"heterogeneous_bbc_context\":{\n" + + " \"am_contract\":{\n" + + " \"contractAddress\":\"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " },\n" + + " \"raw_conf\":\"eyJ0ZXN0IjoidGVzdCJ9\",\n" + + " \"sdp_contract\":{\n" + + " \"contractAddress\":\"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " }\n" + + " },\n" + + " \"am_service_status\":\"DEPLOY_FINISHED\",\n" + + " \"am_client_contract_address\":\"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"plugin_server_id\":\"p-QYj86x8Zd\"\n" + + "}"; + + private static final String NEW_BBC_CONTEXT = "{\n" + + " \"am_contract\":{\n" + + " \"contractAddress\":\"1234\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " },\n" + + " \"raw_conf\":\"eyJ0ZXN0IjoidGVzdCJ9\",\n" + + " \"sdp_contract\":{\n" + + " \"contractAddress\":\"1234\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " }\n" + + " }"; + + private static final String BLOCKCHAIN_META_EXAMPLE_OBJ_LESS_INFO = "{\n" + + " \"heterogeneous_bbc_context\":{\n" + + " \"am_contract\":{\n" + + " \"contractAddress\":\"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " },\n" + + " \"raw_conf\":\"eyJ0ZXN0IjoidGVzdCJ9\",\n" + + " \"sdp_contract\":{\n" + + " \"contractAddress\":\"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " }\n" + + " },\n" + + " \"plugin_server_id\":\"p-QYj86x8Zd\"\n" + + "}"; + + private static final String BLOCKCHAIN_META_EXAMPLE_OBJ_EXTRA_NOT_IN_MAP = "{\n" + + " \"test\":\"test\",\n" + + " \"init_block_height\":\"13947633\",\n" + + " \"anchor_runtime_status\":\"RUNNING\",\n" + + " \"sdp_msg_contract_address\":\"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"is_domain_registered\":\"true\",\n" + + " \"heterogeneous_bbc_context\":{\n" + + " \"am_contract\":{\n" + + " \"contractAddress\":\"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " },\n" + + " \"raw_conf\":\"eyJ0ZXN0IjoidGVzdCJ9\",\n" + + " \"sdp_contract\":{\n" + + " \"contractAddress\":\"0x098310f3921eb1f7488ee169298e92759caa4e14\",\n" + + " \"status\":\"CONTRACT_READY\"\n" + + " }\n" + + " },\n" + + " \"am_service_status\":\"DEPLOY_FINISHED\",\n" + + " \"am_client_contract_address\":\"0x72e82e6aa48fca141ceb5914382be199fa514f96\",\n" + + " \"plugin_server_id\":\"p-QYj86x8Zd\"\n" + + "}"; + + private static AbstractCrossChainCertificate antchainDotComDomainCert; + + private static AbstractCrossChainCertificate catchainDotComDomainCert; + + private static AbstractCrossChainCertificate dogchainDotComDomainCert; + + private static AbstractCrossChainCertificate birdchainDotComDomainCert; + + private static AbstractCrossChainCertificate dotComDomainSpaceCert; + + private static List domainCertList = new ArrayList<>(); + + private static Map trustRootMap = new HashMap<>(); + + private static AbstractCrossChainCertificate relayerCert; + + private static AbstractCrossChainCertificate trustRootCert; + + private static PrivateKey privateKey; + + @BeforeClass + public static void setup() throws Exception { + antchainDotComDomainCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/antchain.com.crt") + ); + catchainDotComDomainCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/catchain.com.crt") + ); + dogchainDotComDomainCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/dogchain.com.crt") + ); + birdchainDotComDomainCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/birdchain.com.crt") + ); + + domainCertList.add(antchainDotComDomainCert); + domainCertList.add(catchainDotComDomainCert); + domainCertList.add(dogchainDotComDomainCert); + domainCertList.add(birdchainDotComDomainCert); + + relayerCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/relayer.crt") + ); + dotComDomainSpaceCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/x.com.crt") + ); + trustRootCert = CrossChainCertificateFactory.createCrossChainCertificateFromPem( + FileUtil.readBytes("cc_certs/trust_root.crt") + ); + privateKey = PemUtil.readPemPrivateKey( + new ByteArrayInputStream(FileUtil.readBytes("cc_certs/private_key.pem")) + ); + + trustRootMap.put(CrossChainDomain.ROOT_DOMAIN_SPACE, trustRootCert); + trustRootMap.put( + CrossChainCertificateUtil.getCrossChainDomainSpace(dotComDomainSpaceCert).getDomain(), + dotComDomainSpaceCert + ); + } + + @Test + public void testBlockchainPropertiesDeserialization() throws Exception { + BlockchainMeta.BlockchainProperties properties = BlockchainMeta.BlockchainProperties.decode(BLOCKCHAIN_META_EXAMPLE.getBytes()); + Assert.assertNotNull(properties); + Assert.assertEquals(BlockchainStateEnum.RUNNING, properties.getAnchorRuntimeStatus()); + Assert.assertEquals(OnChainServiceStatusEnum.DEPLOY_FINISHED, properties.getAmServiceStatus()); + Assert.assertNotNull(properties.getBbcContext()); + Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, properties.getBbcContext().getSdpContract().getStatus()); + } + + @Test + public void testBlockchainPropertiesDeserializationObj() throws Exception { + BlockchainMeta.BlockchainProperties properties = BlockchainMeta.BlockchainProperties.decode(BLOCKCHAIN_META_EXAMPLE_OBJ.getBytes()); + Assert.assertNotNull(properties); + Assert.assertEquals(BlockchainStateEnum.RUNNING, properties.getAnchorRuntimeStatus()); + Assert.assertEquals(OnChainServiceStatusEnum.DEPLOY_FINISHED, properties.getAmServiceStatus()); + Assert.assertNotNull(properties.getBbcContext()); + Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, properties.getBbcContext().getSdpContract().getStatus()); + } + + @Test + public void testBlockchainPropertiesDeserializationObjLessInfo() throws Exception { + BlockchainMeta.BlockchainProperties properties = BlockchainMeta.BlockchainProperties.decode(BLOCKCHAIN_META_EXAMPLE_OBJ_LESS_INFO.getBytes()); + Assert.assertNotNull(properties); + Assert.assertNull(properties.getAnchorRuntimeStatus()); + Assert.assertNotNull(properties.getBbcContext()); + Assert.assertEquals(ContractStatusEnum.CONTRACT_READY, properties.getBbcContext().getSdpContract().getStatus()); + } + + @Test + public void testBlockchainPropertiesSerialization() throws Exception { + BlockchainMeta.BlockchainProperties properties = BlockchainMeta.BlockchainProperties.decode(BLOCKCHAIN_META_EXAMPLE_OBJ.getBytes()); + Assert.assertNotNull(properties); + System.out.println(new String(properties.encode())); + } + + @Test + public void testBlockchainPropertiesDeserializationWithExtra() throws Exception { + BlockchainMeta.BlockchainProperties properties = BlockchainMeta.BlockchainProperties.decode(BLOCKCHAIN_META_EXAMPLE_OBJ_EXTRA_NOT_IN_MAP.getBytes()); + Assert.assertNotNull(properties); + Assert.assertNotNull(properties.getExtraProperties()); + Assert.assertEquals("test", properties.getExtraProperties().get("test")); + } + + @Test + public void testBlockchainMetaUpdateProperty() throws Exception { + BlockchainMeta blockchainMeta = new BlockchainMeta( + "chain", "chainid", "", "", BLOCKCHAIN_META_EXAMPLE_OBJ_EXTRA_NOT_IN_MAP.getBytes() + ); + blockchainMeta.updateProperty("heterogeneous_bbc_context", NEW_BBC_CONTEXT); + blockchainMeta.updateProperty("anchor_runtime_status", BlockchainStateEnum.STOPPED.getCode()); + blockchainMeta.updateProperty("test", "newtest"); + blockchainMeta.updateProperty("testabc", "testabc"); + + Assert.assertEquals( + "1234", + blockchainMeta.getProperties().getBbcContext().getSdpContract().getContractAddress() + ); + Assert.assertEquals( + "1234", + blockchainMeta.getProperties().getBbcContext().getAuthMessageContract().getContractAddress() + ); + Assert.assertEquals( + "newtest", + blockchainMeta.getProperties().getExtraProperties().get("test") + ); + Assert.assertEquals( + "testabc", + blockchainMeta.getProperties().getExtraProperties().get("testabc") + ); + } + + @Test + public void testRelayerBlockchainInfo() { + + DomainCertWrapper domainCertWrapper = new DomainCertWrapper( + antchainDotComDomainCert, + DomainNameCredentialSubject.decode(antchainDotComDomainCert.getCredentialSubject()), + "mychain", + "antchain.com.id", + "antchain.com", + ".com" + ); + + RelayerBlockchainInfo relayerBlockchainInfo = new RelayerBlockchainInfo(); + relayerBlockchainInfo.setDomainCert(domainCertWrapper); + relayerBlockchainInfo.setDomainSpaceChain(ListUtil.toList(".com")); + relayerBlockchainInfo.setAmContractClientAddresses("0xda216434d379c95db9e80edb2566abaaac467429ce63d92cfb2c5af338f65f52"); + + String rawInfo = relayerBlockchainInfo.encode(); + System.out.println(rawInfo); + + RelayerBlockchainInfo relayerBlockchainInfo1 = RelayerBlockchainInfo.decode(rawInfo); + Assert.assertNotNull(relayerBlockchainInfo1); + Assert.assertEquals( + relayerBlockchainInfo.getDomainCert().getDomain(), + relayerBlockchainInfo1.getDomainCert().getDomain() + ); + } + + @Test + public void testRelayerBlockchainContent() { + + RelayerBlockchainContent relayerBlockchainContent = new RelayerBlockchainContent( + domainCertList.stream() + .map( + cert -> new RelayerBlockchainInfo( + new DomainCertWrapper( + cert, + DomainNameCredentialSubject.decode(cert.getCredentialSubject()), + "mychain", + CrossChainCertificateUtil.getCrossChainDomain(cert).getDomain() + ".id", + CrossChainCertificateUtil.getCrossChainDomain(cert).getDomain(), + CrossChainCertificateUtil.getCrossChainDomainSpace(dotComDomainSpaceCert).getDomain() + ), + ListUtil.toList(".com"), + HexUtil.encodeHexStr(RandomUtil.randomBytes(32)) + ) + ).collect(Collectors.toMap( + info -> info.getDomainCert().getDomain(), + info -> info + )), + trustRootMap + ); + + Assert.assertEquals( + CrossChainCertificateUtil.getCrossChainDomain(antchainDotComDomainCert).getDomain(), + relayerBlockchainContent.getRelayerBlockchainInfo( + CrossChainCertificateUtil.getCrossChainDomain(antchainDotComDomainCert).getDomain() + ).getDomainCert().getDomain() + ); + String rawContent = relayerBlockchainContent.encodeToJson(); + Assert.assertNotNull(rawContent); + System.out.println(rawContent); + + RelayerBlockchainContent relayerBlockchainContent1 = RelayerBlockchainContent.decodeFromJson(rawContent); + Assert.assertNotNull(relayerBlockchainContent1); + Assert.assertEquals( + relayerBlockchainContent.getRelayerBlockchainInfo( + CrossChainCertificateUtil.getCrossChainDomain(catchainDotComDomainCert).getDomain() + ).getDomainCert().getDomain(), + relayerBlockchainContent1.getRelayerBlockchainInfo( + CrossChainCertificateUtil.getCrossChainDomain(catchainDotComDomainCert).getDomain() + ).getDomainCert().getDomain() + ); + + AbstractCrossChainCertificate domainSpaceCert = relayerBlockchainContent1.getDomainSpaceCert( + CrossChainCertificateUtil.getCrossChainDomainSpace(dotComDomainSpaceCert).getDomain() + ); + Assert.assertNotNull(domainSpaceCert); + Assert.assertEquals( + dotComDomainSpaceCert.getId(), + domainSpaceCert.getId() + ); + } +} diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/WsClientTest.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/WsClientTest.java new file mode 100644 index 0000000..53648ef --- /dev/null +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/basic/WsClientTest.java @@ -0,0 +1,36 @@ +/* + * 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.relayer.bootstrap.basic; + +import java.net.URL; + +import com.alipay.antchain.bridge.relayer.core.types.network.ws.client.WSRelayerServerAPImplServiceWithHost; +import org.junit.Ignore; +import org.junit.Test; + +@Ignore +public class WsClientTest { + + @Test + public void testWSRelayerServerAPImplServiceWithHost() throws Exception { + + WSRelayerServerAPImplServiceWithHost wsRelayerServerAPImplServiceWithHost = new WSRelayerServerAPImplServiceWithHost( + new URL("http://" + "localhost:8082" + "/WSEndpointServer?wsdl") + ); + wsRelayerServerAPImplServiceWithHost.getWSRelayerServerAPImplPort().request("test"); + } +} diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/BlockchainRepositoryTest.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/BlockchainRepositoryTest.java index 9e38302..d8d3c96 100644 --- a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/BlockchainRepositoryTest.java +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/BlockchainRepositoryTest.java @@ -16,13 +16,276 @@ package com.alipay.antchain.bridge.relayer.bootstrap.repo; +import java.util.List; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; import com.alipay.antchain.bridge.relayer.bootstrap.TestBase; +import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.model.AnchorProcessHeights; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; +import com.alipay.antchain.bridge.relayer.core.service.anchor.tasks.BlockTaskTypeEnum; +import com.alipay.antchain.bridge.relayer.core.service.anchor.tasks.NotifyTaskTypeEnum; +import com.alipay.antchain.bridge.relayer.dal.entities.AnchorProcessEntity; +import com.alipay.antchain.bridge.relayer.dal.entities.DomainCertEntity; +import com.alipay.antchain.bridge.relayer.dal.mapper.AnchorProcessMapper; +import com.alipay.antchain.bridge.relayer.dal.mapper.DomainCertMapper; +import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; import org.junit.Test; +import org.junit.runners.MethodSorters; +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class BlockchainRepositoryTest extends TestBase { + @BeforeClass + public static void setup() throws Exception { + } + + @Resource + private IBlockchainRepository blockchainRepository; + + @Resource + private DomainCertMapper domainCertMapper; + + @Resource + private AnchorProcessMapper anchorProcessMapper; + + private void saveSomeBlockchains() { + + blockchainRepository.saveBlockchainMeta(testchain1Meta); + + DomainCertEntity entity = new DomainCertEntity(); + entity.setBlockchainId(testchain1Meta.getBlockchainId()); + entity.setProduct(testchain1Meta.getProduct()); + entity.setDomain(antchainSubject.getDomainName().getDomain()); + entity.setDomainSpace(antchainSubject.getParentDomainSpace().getDomain()); + entity.setIssuerOid(antchainDotCommCert.getIssuer().encode()); + entity.setSubjectOid(antchainSubject.getApplicant().encode()); + entity.setDomainCert(antchainDotCommCert.encode()); + domainCertMapper.insert(entity); + + blockchainRepository.saveBlockchainMeta(testchain2Meta); + + entity = new DomainCertEntity(); + entity.setBlockchainId(testchain2Meta.getBlockchainId()); + entity.setProduct(testchain2Meta.getProduct()); + entity.setDomain(catchainSubject.getDomainName().getDomain()); + entity.setDomainSpace(catchainSubject.getParentDomainSpace().getDomain()); + entity.setIssuerOid(catchainDotCommCert.getIssuer().encode()); + entity.setSubjectOid(catchainSubject.getApplicant().encode()); + entity.setDomainCert(catchainDotCommCert.encode()); + domainCertMapper.insert(entity); + + blockchainRepository.saveBlockchainMeta(testchain3Meta); + + entity = new DomainCertEntity(); + entity.setBlockchainId(testchain3Meta.getBlockchainId()); + entity.setProduct(testchain3Meta.getProduct()); + entity.setDomain(dogchainSubject.getDomainName().getDomain()); + entity.setDomainSpace(dogchainSubject.getParentDomainSpace().getDomain()); + entity.setIssuerOid(dogchainDotCommCert.getIssuer().encode()); + entity.setSubjectOid(dogchainSubject.getApplicant().encode()); + entity.setDomainCert(dogchainDotCommCert.encode()); + domainCertMapper.insert(entity); + + anchorProcessMapper.insert( + new AnchorProcessEntity( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId() + "getAnchorProcessHeight", + BlockTaskTypeEnum.POLLING.getCode(), + 100L + ) + ); + } + + @Test + public void testSaveBlockchainMeta() { + BlockchainMeta blockchainMeta = new BlockchainMeta("testchain", "testchain.id", "", "", blockchainProperties1); + blockchainRepository.saveBlockchainMeta(blockchainMeta); + Assert.assertThrows(AntChainBridgeRelayerException.class, () -> blockchainRepository.saveBlockchainMeta(blockchainMeta)); + } + + @Test + public void testGetAllBlockchainMetaByState() { + saveSomeBlockchains(); + List result = blockchainRepository.getBlockchainMetaByState(BlockchainStateEnum.RUNNING); + Assert.assertTrue(result.size() >= 1); + + testchain1Meta.getProperties().setAnchorRuntimeStatus(BlockchainStateEnum.STOPPED); + Assert.assertTrue(blockchainRepository.updateBlockchainMeta(testchain1Meta)); + + result = blockchainRepository.getBlockchainMetaByState(BlockchainStateEnum.STOPPED); + + Assert.assertTrue(result.size() >= 1); + } + + @Test + public void testGetBlockchainMetaByDomain() { + saveSomeBlockchains(); + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMetaByDomain( + antchainSubject.getDomainName().getDomain() + ); + Assert.assertNotNull(blockchainMeta); + Assert.assertEquals(testchain1Meta.getBlockchainId(), blockchainMeta.getBlockchainId()); + } + + @Test + public void testGetAnchorProcessHeight() { + saveSomeBlockchains(); + + Assert.assertEquals( + 100L, + blockchainRepository.getAnchorProcessHeight( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId() + "getAnchorProcessHeight", + BlockTaskTypeEnum.POLLING.getCode() + ).longValue() + ); + } + + @Test + public void testGetAnchorProcessHeights() { + saveSomeBlockchains(); + + anchorProcessMapper.insert( + new AnchorProcessEntity( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId(), + BlockTaskTypeEnum.POLLING.getCode(), + 200L + ) + ); + anchorProcessMapper.insert( + new AnchorProcessEntity( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId(), + BlockTaskTypeEnum.SYNC.getCode(), + 150L + ) + ); + anchorProcessMapper.insert( + new AnchorProcessEntity( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId(), + BlockTaskTypeEnum.NOTIFY.toNotifyWorkerHeightType(NotifyTaskTypeEnum.CROSSCHAIN_MSG_WORKER.getCode()), + 100L + ) + ); + + AnchorProcessHeights anchorProcessHeights = blockchainRepository.getAnchorProcessHeights( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId() + ); + Assert.assertNotNull(anchorProcessHeights); + Assert.assertEquals( + 200L, + anchorProcessHeights.getProcessHeights().get(BlockTaskTypeEnum.POLLING.getCode()).longValue() + ); + Assert.assertEquals( + 150L, + anchorProcessHeights.getProcessHeights().get(BlockTaskTypeEnum.SYNC.getCode()).longValue() + ); + Assert.assertEquals( + 100L, + anchorProcessHeights.getProcessHeights().get( + BlockTaskTypeEnum.NOTIFY.toNotifyWorkerHeightType( + NotifyTaskTypeEnum.CROSSCHAIN_MSG_WORKER.getCode() + ) + ).longValue() + ); + } + + @Test + public void testSetAnchorProcessHeight() { + + blockchainRepository.setAnchorProcessHeight( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId() + "setAnchorProcessHeight", + BlockTaskTypeEnum.POLLING.getCode(), + 999L + ); + + Assert.assertEquals( + 999L, + blockchainRepository.getAnchorProcessHeight( + testchain1Meta.getProduct(), + testchain1Meta.getBlockchainId() + "setAnchorProcessHeight", + BlockTaskTypeEnum.POLLING.getCode() + ).longValue() + ); + } + + @Test + public void test0_GetAllBlockchainMeta() { + saveSomeBlockchains(); + + Assert.assertEquals(3, blockchainRepository.getAllBlockchainMeta().size()); + } + + @Test + public void testHasBlockchain() { + saveSomeBlockchains(); + Assert.assertTrue(blockchainRepository.hasBlockchain(antchainSubject.getDomainName().getDomain())); + + Assert.assertTrue(blockchainRepository.hasBlockchain(testchain1Meta.getProduct(), testchain1Meta.getBlockchainId())); + } + + @Test + public void testGetBlockchainMetaByPluginServerId() { + saveSomeBlockchains(); + + Assert.assertTrue( + 1 <= blockchainRepository.getBlockchainMetaByPluginServerId("p-QYj86x8Zd").size() + ); + } + + @Test + public void testGetBlockchainMeta() { + saveSomeBlockchains(); + + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMeta(testchain1Meta.getProduct(), testchain1Meta.getBlockchainId()); + Assert.assertNotNull(blockchainMeta); + Assert.assertEquals(testchain1Meta.getMetaKey(), blockchainMeta.getMetaKey()); + Assert.assertEquals(testchain1Meta.getProperties().getAmClientContractAddress(), blockchainMeta.getProperties().getAmClientContractAddress()); + } + + @Test + public void testGetBlockchainDomain() { + saveSomeBlockchains(); + + Assert.assertEquals( + antchainSubject.getDomainName().getDomain(), + blockchainRepository.getBlockchainDomain(testchain1Meta.getProduct(), testchain1Meta.getBlockchainId()) + ); + } + + @Test + public void testGetBlockchainDomainsByState() { + saveSomeBlockchains(); + + List domains = blockchainRepository.getBlockchainDomainsByState(BlockchainStateEnum.RUNNING); + Assert.assertTrue( + domains.containsAll( + ListUtil.of( + catchainSubject.getDomainName().getDomain(), + dogchainSubject.getDomainName().getDomain() + ) + ) + ); + } + @Test - public void test() { + public void testGetDomainCert() { + saveSomeBlockchains(); + DomainCertWrapper domainCertWrapper = blockchainRepository.getDomainCert(catchainSubject.getDomainName().getDomain()); + Assert.assertNotNull(domainCertWrapper); + Assert.assertEquals(catchainSubject.getDomainName().getDomain(), domainCertWrapper.getDomain()); + Assert.assertEquals(catchainSubject.getParentDomainSpace().getDomain(), domainCertWrapper.getDomainSpace()); } } diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/CrossChainRepositoryTest.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/CrossChainRepositoryTest.java index 80aecff..2e884c5 100644 --- a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/CrossChainRepositoryTest.java +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/repo/CrossChainRepositoryTest.java @@ -16,6 +16,7 @@ package com.alipay.antchain.bridge.relayer.bootstrap.repo; +import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; @@ -37,6 +38,7 @@ import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; import com.alipay.antchain.bridge.relayer.commons.constant.UpperProtocolTypeBeyondAMEnum; import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgCommitResult; import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgWrapper; import com.alipay.antchain.bridge.relayer.dal.entities.*; import com.alipay.antchain.bridge.relayer.dal.mapper.AuthMsgArchiveMapper; @@ -46,10 +48,12 @@ import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +@Slf4j public class CrossChainRepositoryTest extends TestBase { @BeforeClass @@ -128,6 +132,26 @@ public void testArchiveAuthMessages() { ); } + @Test + public void testPutAuthMessages() { + List authMsgWrappers = new ArrayList<>(); + for (int i = 990; i < 1000; i++) { + authMsgWrappers.add( + new AuthMsgWrapper( + "test", + "test", + "test", + ByteUtil.intToBytes(i), + "am", + AuthMsgProcessStateEnum.PROVED, + authMessage + ) + ); + } + + Assert.assertEquals(10, crossChainMessageRepository.putAuthMessages(authMsgWrappers)); + } + @Test public void testPutSDPMessage() throws Exception { sdpMsgPoolMapper.selectList(null); @@ -135,8 +159,6 @@ public void testPutSDPMessage() throws Exception { saveSomeSDP(); System.out.println(sdpMsgPoolMapper.update(new SDPMsgPoolEntity(), new LambdaUpdateWrapper().eq(BaseEntity::getId, 100))); - - } @Test @@ -166,7 +188,7 @@ public void testUpdateSDPMessage() { Assert.assertTrue( crossChainMessageRepository.updateSDPMessage( new SDPMsgWrapper( - 10, + 10L, new AuthMsgWrapper( 10, "test", @@ -203,6 +225,147 @@ public void testUpdateSDPMessage() { Assert.assertTrue(sdpMsgWrapper.isTxSuccess()); } + @Test + public void testUpdateSDPMessageResults() { + saveSomeSDP(); + + List results = new ArrayList<>(); + + SDPMsgCommitResult sdpMsgCommitResult = new SDPMsgCommitResult(); + sdpMsgCommitResult.setCommitSuccess(true); + sdpMsgCommitResult.setConfirmed(true); + sdpMsgCommitResult.setTxHash(DigestUtil.sha256Hex(Integer.toString(10))); + sdpMsgCommitResult.setFailReason("test!"); + sdpMsgCommitResult.setBlockTimestamp(System.currentTimeMillis()); + sdpMsgCommitResult.setReceiveProduct("eth"); + sdpMsgCommitResult.setReceiveBlockchainId("ethid"); + + results.add(sdpMsgCommitResult); + + crossChainMessageRepository.updateSDPMessageResults(results); + + SDPMsgWrapper sdpMsgWrapper = crossChainMessageRepository.getSDPMessage(DigestUtil.sha256Hex(Integer.toString(10))); + Assert.assertEquals( + SDPMsgProcessStateEnum.TX_SUCCESS, + sdpMsgWrapper.getProcessState() + ); + } + + @Test + public void testPeekTxPendingSDPMessageIds() { + saveSomeSDP(); + + Assert.assertTrue( + crossChainMessageRepository.updateSDPMessage( + new SDPMsgWrapper( + 10L, + new AuthMsgWrapper( + 10 + 1, + "test", + "test", + "test", + ByteUtil.intToBytes(10), + "am", + AuthMsgProcessStateEnum.PENDING, + authMessage + ), + "eth", + "ethid", + "am", + SDPMsgProcessStateEnum.TX_SUCCESS, + DigestUtil.sha256Hex(Integer.toString(10)), + true, + "", + sdpMessage + ) + ) + ); + + Assert.assertTrue( + crossChainMessageRepository.updateSDPMessage( + new SDPMsgWrapper( + 9L, + new AuthMsgWrapper( + 9 + 1, + "test", + "test", + "test", + ByteUtil.intToBytes(9), + "am", + AuthMsgProcessStateEnum.PENDING, + authMessage + ), + "eth", + "ethid", + "am", + SDPMsgProcessStateEnum.TX_FAILED, + DigestUtil.sha256Hex(Integer.toString(9)), + false, + "test!", + sdpMessage + ) + ) + ); + + List res = crossChainMessageRepository.peekTxFinishedSDPMessageIds( + "eth", + "ethid", + 10 + ); + + Assert.assertEquals(2, res.size()); + Assert.assertTrue(res.stream().anyMatch(x -> x.getId() == 10)); + Assert.assertTrue(res.stream().anyMatch(x -> x.getId() == 9)); + } + + @Test + public void testCountSDPMessagesByState() { + saveSomeSDP(); + Assert.assertTrue( + crossChainMessageRepository.updateSDPMessage( + new SDPMsgWrapper( + 9L, + new AuthMsgWrapper( + 9 + 1, + "test", + "test", + "test", + ByteUtil.intToBytes(9), + "am", + AuthMsgProcessStateEnum.PENDING, + authMessage + ), + "eth", + "ethid", + "am", + SDPMsgProcessStateEnum.TX_FAILED, + DigestUtil.sha256Hex(Integer.toString(9)), + false, + "test!", + sdpMessage + ) + ) + ); + Assert.assertEquals( + 10, + crossChainMessageRepository.countSDPMessagesByState( + "eth", + "ethid", + SDPMsgProcessStateEnum.PENDING + ) + ); + } + + @Test + public void testGetAuthMessage() { + saveElevenAM(getAMCurrentId()); + + AuthMsgWrapper authMsgWrapper = crossChainMessageRepository.getAuthMessage(1, true); + Assert.assertNotNull(authMsgWrapper); + Assert.assertEquals(authMsgWrapper.getDomain(), "test"); + Assert.assertNotNull(authMsgWrapper.getAuthMessage()); + } + private long getAMCurrentId() { AuthMsgPoolEntity entity = new AuthMsgPoolEntity(); entity.setId(0L); @@ -213,9 +376,6 @@ private long getAMCurrentId() { } private void saveElevenAM(long startId) { - if (ifAlreadyWriteAM) { - return; - } for (int i = 0; i < 11; i++) { Assert.assertEquals( @@ -233,8 +393,6 @@ private void saveElevenAM(long startId) { ) ); } - - ifAlreadyWriteAM = true; } private long getSDPCurrentId() { @@ -247,9 +405,6 @@ private long getSDPCurrentId() { } private void saveSomeSDP() { - if (ifAlreadyWriteSDP) { - return; - } for (int i = 0; i < 11; i++) { crossChainMessageRepository.putSDPMessage( @@ -258,7 +413,7 @@ private void saveSomeSDP() { "ethid", "am", SDPMsgProcessStateEnum.PENDING, - "", + DigestUtil.sha256Hex(Integer.toString(i)), false, "", new AuthMsgWrapper( @@ -275,7 +430,5 @@ private void saveSomeSDP() { ) ); } - - ifAlreadyWriteSDP = true; } } diff --git a/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/utils/MyRedisServer.java b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/utils/MyRedisServer.java new file mode 100644 index 0000000..31886b9 --- /dev/null +++ b/r-bootstrap/src/test/java/com/alipay/antchain/bridge/relayer/bootstrap/utils/MyRedisServer.java @@ -0,0 +1,34 @@ +/* + * 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.relayer.bootstrap.utils; + +import java.io.IOException; + +import redis.embedded.RedisExecProvider; +import redis.embedded.RedisServer; + +public class MyRedisServer extends RedisServer { + + public MyRedisServer(RedisExecProvider redisExecProvider, Integer port) throws IOException { + super(redisExecProvider, port); + } + + @Override + protected String redisReadyPattern() { + return ".*Ready to accept connections tcp.*"; + } +} diff --git a/r-bootstrap/src/test/resources/application-test.yml b/r-bootstrap/src/test/resources/application-test.yml index 3c237fa..75379ef 100644 --- a/r-bootstrap/src/test/resources/application-test.yml +++ b/r-bootstrap/src/test/resources/application-test.yml @@ -4,13 +4,39 @@ spring: url: jdbc:h2:mem:relayer;DB_CLOSE_DELAY=-1;MODE=MySQL;IGNORECASE=TRUE password: 123 username: root + sql: + init: + data-locations: classpath:data/ddl.sql redis: host: localhost port: 6379 - password: Mychain123!@# logging: file: path: ./logs level: - app: INFO \ No newline at end of file + app: INFO +relayer: + network: + id: 1 + node: + sig_algo: SM3WITHSM2 + server: + mode: https + port: 8082 + tls: + private_key_path: node_keys/relayer/node_tls.key + trust_ca_path: node_keys/relayer/node_tls.crt + crosschain_cert_path: cc_certs/relayer.crt + private_key_path: cc_certs/private_key.pem + plugin_server_manager: + grpc: + auth: + tls: + client: + ca: + path: node_keys/ps/relayer.crt + key: + path: node_keys/ps/relayer.key +server: + port: 8081 \ No newline at end of file diff --git a/r-bootstrap/src/test/resources/cc_certs/antchain.com.crt b/r-bootstrap/src/test/resources/cc_certs/antchain.com.crt new file mode 100644 index 0000000..f5b0366 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/antchain.com.crt @@ -0,0 +1,13 @@ +-----BEGIN DOMAIN NAME CERTIFICATE----- +AAD4AQAAAAABAAAAMQEACgAAAHRlc3Rkb21haW4CAAEAAAABAwBuAAAAAABoAAAA +AAABAAAAAAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABGNN54raT7Kt +WOcfyNUMTbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQOd4qc9Tr6AG/9 +DL01XnMJ/KMEAAgAAACBfVBlAAAAAAUACAAAAAGxMWcAAAAABgCsAAAAAACmAAAA +AAADAAAAMS4wAQABAAAAAAIABAAAAC5jb20DAAwAAABhbnRjaGFpbi5jb20EAG4A +AAAAAGgAAAAAAAEAAAAAAQBbAAAAMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE +Y03nitpPsq1Y5x/I1QxNuYGavLqjewBEh/bgnpGOWVy4CTLq4KpiAgDddePdVA53 +ipz1OvoAb/0MvTVecwn8owUAAAAAAAcAkgAAAAAAjAAAAAAAAwAAAFNNMwEAIAAA +AAayes9PyRFvYCesOPPOp2vfh4f6jVkf8+xVjLeVy3u0AgAKAAAAU00zV0lUSFNN +MgMARwAAADBFAiEAtnWL679saQup+Lsx6WKfkvM4t9NxtR685CFy6yYOUPICICbp +EDg/UXtkMWC9UbvPDSnGSut38Vj7uP8SYEC7NSbO +-----END DOMAIN NAME CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/birdchain.com.crt b/r-bootstrap/src/test/resources/cc_certs/birdchain.com.crt new file mode 100644 index 0000000..b3cee74 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/birdchain.com.crt @@ -0,0 +1,13 @@ +-----BEGIN DOMAIN NAME CERTIFICATE----- +AAD6AQAAAAABAAAAMQEACwAAAHRlc3Rkb21haW4zAgABAAAAAQMAbgAAAAAAaAAA +AAAAAQAAAAABAFsAAAAwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARjTeeK2k+y +rVjnH8jVDE25gZq8uqN7AESH9uCekY5ZXLgJMurgqmICAN11491UDneKnPU6+gBv +/Qy9NV5zCfyjBAAIAAAAgX1QZQAAAAAFAAgAAAABsTFnAAAAAAYArQAAAAAApwAA +AAAAAwAAADEuMAEAAQAAAAACAAQAAAAuY29tAwANAAAAYmlyZGNoYWluLmNvbQQA +bgAAAAAAaAAAAAAAAQAAAAABAFsAAAAwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNC +AARjTeeK2k+yrVjnH8jVDE25gZq8uqN7AESH9uCekY5ZXLgJMurgqmICAN11491U +DneKnPU6+gBv/Qy9NV5zCfyjBQAAAAAABwCSAAAAAACMAAAAAAADAAAAU00zAQAg +AAAA+ZnUKaoV2+ZKGgBwcG6yR+IE00Fv/TFUj0W5DVHkFsECAAoAAABTTTNXSVRI +U00yAwBHAAAAMEUCIQDkD2PdnHJv0PuEuy5E9lhKs0Rg84VlsSqq7YBanS7ymwIg +KR4HYBqgdN/DhxDj8rWUh8jaZm88O3wOcew8xDbY44g= +-----END DOMAIN NAME CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/catchain.com.crt b/r-bootstrap/src/test/resources/cc_certs/catchain.com.crt new file mode 100644 index 0000000..68d97d8 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/catchain.com.crt @@ -0,0 +1,13 @@ +-----BEGIN DOMAIN NAME CERTIFICATE----- +AAD4AQAAAAABAAAAMQEACwAAAHRlc3Rkb21haW4xAgABAAAAAQMAbgAAAAAAaAAA +AAAAAQAAAAABAFsAAAAwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARjTeeK2k+y +rVjnH8jVDE25gZq8uqN7AESH9uCekY5ZXLgJMurgqmICAN11491UDneKnPU6+gBv +/Qy9NV5zCfyjBAAIAAAAgX1QZQAAAAAFAAgAAAABsTFnAAAAAAYArAAAAAAApgAA +AAAAAwAAADEuMAEAAQAAAAACAAQAAAAuY29tAwAMAAAAY2F0Y2hhaW4uY29tBABu +AAAAAABoAAAAAAABAAAAAAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IA +BGNN54raT7KtWOcfyNUMTbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQO +d4qc9Tr6AG/9DL01XnMJ/KMFAAAAAAAHAJEAAAAAAIsAAAAAAAMAAABTTTMBACAA +AADfEIeTGOkyp43RjnSxdQofPjYExEGvzfsozMshs7WjdQIACgAAAFNNM1dJVEhT +TTIDAEYAAAAwRAIgEyAFFOwKfMMvjFLpwSGX8l5+Nw7G/MpNdpuKjzRQ2GMCICTu +lXVDRMIA8zYZNMciKqzYbamNp4Y+/4+ZlExP4asm +-----END DOMAIN NAME CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/dogchain.com.crt b/r-bootstrap/src/test/resources/cc_certs/dogchain.com.crt new file mode 100644 index 0000000..4e454ab --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/dogchain.com.crt @@ -0,0 +1,13 @@ +-----BEGIN DOMAIN NAME CERTIFICATE----- +AAD5AQAAAAABAAAAMQEACwAAAHRlc3Rkb21haW4yAgABAAAAAQMAbgAAAAAAaAAA +AAAAAQAAAAABAFsAAAAwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARjTeeK2k+y +rVjnH8jVDE25gZq8uqN7AESH9uCekY5ZXLgJMurgqmICAN11491UDneKnPU6+gBv +/Qy9NV5zCfyjBAAIAAAAgX1QZQAAAAAFAAgAAAABsTFnAAAAAAYArAAAAAAApgAA +AAAAAwAAADEuMAEAAQAAAAACAAQAAAAuY29tAwAMAAAAZG9nY2hhaW4uY29tBABu +AAAAAABoAAAAAAABAAAAAAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IA +BGNN54raT7KtWOcfyNUMTbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQO +d4qc9Tr6AG/9DL01XnMJ/KMFAAAAAAAHAJIAAAAAAIwAAAAAAAMAAABTTTMBACAA +AABThh6L0gLwAfQduY7g785Djuyx2lIh3D7o94XeTNuJlAIACgAAAFNNM1dJVEhT +TTIDAEcAAAAwRQIgBU9n75wwNmMSQN3tpA8KEstFR0GRlY+f/lVB1hs+yOUCIQCS +FNiKA4DFH+jw9mcDakC3h+42OfUkB6+rbyXeqAqycw== +-----END DOMAIN NAME CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/private_key.pem b/r-bootstrap/src/test/resources/cc_certs/private_key.pem new file mode 100644 index 0000000..dfb4753 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIH9ucFWpafo+yIAN5AZfcUuum4kw+0n4ADBzTkUoVo6ToAoGCCqBHM9V +AYItoUQDQgAEY03nitpPsq1Y5x/I1QxNuYGavLqjewBEh/bgnpGOWVy4CTLq4Kpi +AgDddePdVA53ipz1OvoAb/0MvTVecwn8ow== +-----END EC PRIVATE KEY----- diff --git a/r-bootstrap/src/test/resources/cc_certs/relayer.crt b/r-bootstrap/src/test/resources/cc_certs/relayer.crt new file mode 100644 index 0000000..289ca86 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/relayer.crt @@ -0,0 +1,13 @@ +-----BEGIN RELAYER CERTIFICATE----- +AADxAQAAAAABAAAAMQEAEAAAAGFudGNoYWluLXJlbGF5ZXICAAEAAAADAwBuAAAA +AABoAAAAAAABAAAAAAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABGNN +54raT7KtWOcfyNUMTbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQOd4qc +9Tr6AG/9DL01XnMJ/KMEAAgAAACBfVBlAAAAAAUACAAAAAGxMWcAAAAABgCfAAAA +AACZAAAAAAADAAAAMS4wAQAQAAAAYW50Y2hhaW4tcmVsYXllcgMAbgAAAAAAaAAA +AAAAAQAAAAABAFsAAAAwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARjTeeK2k+y +rVjnH8jVDE25gZq8uqN7AESH9uCekY5ZXLgJMurgqmICAN11491UDneKnPU6+gBv +/Qy9NV5zCfyjBAAAAAAABwCSAAAAAACMAAAAAAADAAAAU00zAQAgAAAAIcahfd6P +ZQtgEwO1CtnA96nnLiMyTVh/kOs7uo5kkTkCAAoAAABTTTNXSVRIU00yAwBHAAAA +MEUCIQDNeOuQTsbKlXnvHgdTu3xrFZJZJe/RzAbTnzrl/RS54wIgaUD9dmzh3Xfg +dXqnUkjKP8DuzR076xcg2LzOvJeloQU= +-----END RELAYER CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/trust_root.crt b/r-bootstrap/src/test/resources/cc_certs/trust_root.crt new file mode 100644 index 0000000..180391f --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/trust_root.crt @@ -0,0 +1,12 @@ +-----BEGIN BCDNS TRUST ROOT CERTIFICATE----- +AADPAQAAAAABAAAAMQEABAAAAHRlc3QCAAEAAAAAAwBuAAAAAABoAAAAAAABAAAA +AAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABGNN54raT7KtWOcfyNUM +TbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQOd4qc9Tr6AG/9DL01XnMJ +/KMEAAgAAACAfVBlAAAAAAUACAAAAACxMWcAAAAABgCJAAAAAACDAAAAAAADAAAA +YmlmAQBuAAAAAABoAAAAAAABAAAAAAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9V +AYItA0IABGNN54raT7KtWOcfyNUMTbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA +3XXj3VQOd4qc9Tr6AG/9DL01XnMJ/KMCAAAAAAAHAJIAAAAAAIwAAAAAAAMAAABT +TTMBACAAAABYmxayz1wf3Mkv5U9AIvoBms1+5XdyrqTC1F7GL0Z7vgIACgAAAFNN +M1dJVEhTTTIDAEcAAAAwRQIgcVIUqZLqzeMXjY5qiNVd6CR0bIwHPXid8ICmLP2V +GKACIQDwKzcpymuI2XpE5cQJII5ZT5M3ijXr0IHJ3DM1ZrQukQ== +-----END BCDNS TRUST ROOT CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/cc_certs/x.com.crt b/r-bootstrap/src/test/resources/cc_certs/x.com.crt new file mode 100644 index 0000000..0e42973 --- /dev/null +++ b/r-bootstrap/src/test/resources/cc_certs/x.com.crt @@ -0,0 +1,13 @@ +-----BEGIN DOMAIN NAME CERTIFICATE----- +AADnAQAAAAABAAAAMQEABAAAAC5jb20CAAEAAAABAwBuAAAAAABoAAAAAAABAAAA +AAEAWwAAADBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABGNN54raT7KtWOcfyNUM +TbmBmry6o3sARIf24J6RjllcuAky6uCqYgIA3XXj3VQOd4qc9Tr6AG/9DL01XnMJ +/KMEAAgAAACBfVBlAAAAAAUACAAAAAGxMWcAAAAABgCgAAAAAACaAAAAAAADAAAA +MS4wAQABAAAAAQIAAAAAAAMABAAAAC5jb20EAG4AAAAAAGgAAAAAAAEAAAAAAQBb +AAAAMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEY03nitpPsq1Y5x/I1QxNuYGa +vLqjewBEh/bgnpGOWVy4CTLq4KpiAgDddePdVA53ipz1OvoAb/0MvTVecwn8owUA +AAAAAAcAkwAAAAAAjQAAAAAAAwAAAFNNMwEAIAAAAGLeNy0u5SC3LJtjlSDjWfX0 +UPvF4CwrHxuYFzPXxhioAgAKAAAAU00zV0lUSFNNMgMASAAAADBGAiEAzQ1RuwRn +9NZlUIoCNtKPFgzGiSFyVd0lSYazfeKzLYQCIQCPgO0q/S+qBpI7P7z6lZ1wtfmW +k5fFEP9PfeuNRnzM2g== +-----END DOMAIN NAME CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/data/ddl.sql b/r-bootstrap/src/test/resources/data/ddl.sql index b5d55c4..0895482 100644 --- a/r-bootstrap/src/test/resources/data/ddl.sql +++ b/r-bootstrap/src/test/resources/data/ddl.sql @@ -14,7 +14,7 @@ * limitations under the License. */ -CREATE TABLE `blockchain` +CREATE TABLE IF NOT EXISTS `blockchain` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product` varchar(64) DEFAULT NULL, @@ -28,7 +28,7 @@ CREATE TABLE `blockchain` UNIQUE KEY `uk_instance` (`blockchain_id`) ); -CREATE TABLE `system_config` +CREATE TABLE IF NOT EXISTS `system_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `conf_key` varchar(128) DEFAULT NULL, @@ -39,7 +39,7 @@ CREATE TABLE `system_config` UNIQUE KEY `conf_key` (`conf_key`) ); -CREATE TABLE `anchor_process` +CREATE TABLE IF NOT EXISTS `anchor_process` ( `id` int(11) NOT NULL AUTO_INCREMENT, `blockchain_product` varchar(64) DEFAULT NULL, @@ -52,7 +52,7 @@ CREATE TABLE `anchor_process` UNIQUE KEY `blockchain_product` (`blockchain_product`, `instance`, `task`) ); -CREATE TABLE `anchor_system_config` +CREATE TABLE IF NOT EXISTS `anchor_system_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `conf_key` varchar(256) DEFAULT NULL, @@ -63,14 +63,15 @@ CREATE TABLE `anchor_system_config` UNIQUE KEY `anchor_conf_key` (`conf_key`) ); -CREATE TABLE `domain_cert` +CREATE TABLE IF NOT EXISTS `domain_cert` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain` varchar(128) DEFAULT NULL, `blockchain_product` varchar(64) DEFAULT NULL, `instance` varchar(128) DEFAULT NULL, - `cert_pk_hash` varchar(64) DEFAULT NULL, - `issuer_pk_hash` varchar(64) DEFAULT NULL, + `subject_oid` blob DEFAULT NULL, + `issuer_oid` blob DEFAULT NULL, + `domain_space` varchar(128) DEFAULT NULL, `domain_cert` longblob, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, @@ -78,10 +79,11 @@ CREATE TABLE `domain_cert` UNIQUE KEY `domain` (`domain`) ); -CREATE TABLE `domain_space_cert` +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, `description` varchar(128) DEFAULT NULL, `domain_space_cert` longblob, `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, @@ -90,7 +92,7 @@ CREATE TABLE `domain_space_cert` UNIQUE KEY `domain_space` (`domain_space`) ); -CREATE TABLE `ucp_pool` +CREATE TABLE IF NOT EXISTS `ucp_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ucp_id` VARBINARY(32) UNIQUE NOT NULL, @@ -118,7 +120,7 @@ CREATE TABLE `ucp_pool` COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; -CREATE TABLE `auth_msg_pool` +CREATE TABLE IF NOT EXISTS `auth_msg_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ucp_id` VARBINARY(32) UNIQUE NOT NULL, @@ -140,7 +142,7 @@ CREATE TABLE `auth_msg_pool` KEY `idx_domainname_processstate` (`domain_name`, `process_state`) ); -CREATE TABLE `sdp_msg_pool` +CREATE TABLE IF NOT EXISTS `sdp_msg_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `auth_msg_id` int(11) DEFAULT NULL, @@ -170,7 +172,7 @@ CREATE TABLE `sdp_msg_pool` KEY `idx_receiverinstance_processstate_receiverblockchainproduct` (`receiver_instance`, `process_state`, `receiver_blockchain_product`) ); -CREATE TABLE `cross_chain_msg_acl` +CREATE TABLE IF NOT EXISTS `cross_chain_msg_acl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `biz_id` varchar(64) DEFAULT NULL, @@ -188,7 +190,7 @@ CREATE TABLE `cross_chain_msg_acl` KEY `exact_valid_rules` (`owner_domain`, `owner_identity_hex`, `grant_domain`, `grant_identity_hex`) ); -CREATE TABLE `relayer_network` +CREATE TABLE IF NOT EXISTS `relayer_network` ( `id` int(11) NOT NULL AUTO_INCREMENT, `network_id` varchar(64) DEFAULT NULL, @@ -201,21 +203,23 @@ CREATE TABLE `relayer_network` UNIQUE KEY `uk_item` (`network_id`, `domain`, `node_id`) ); -CREATE TABLE `relayer_node` +CREATE TABLE IF NOT EXISTS `relayer_node` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `node_id` varchar(64) DEFAULT NULL, - `node_public_key` varchar(1024) DEFAULT NULL, - `domains` varchar(2048) DEFAULT NULL, - `endpoints` varchar(1024) DEFAULT NULL, - `properties` longblob, - `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, - `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, + `id` int(11) NOT NULL AUTO_INCREMENT, + `node_id` varchar(64) DEFAULT NULL, + `node_crosschain_cert` binary DEFAULT NULL, + `node_sig_algo` varchar(255) DEFAULT NULL, + `domains` varchar(2048) DEFAULT NULL, + `endpoints` varchar(1024) DEFAULT NULL, + `blockchain_content` binary DEFAULT NULL, + `properties` longblob, + `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_relayer_node` (`node_id`) ); -CREATE TABLE `auth_msg_archive` +CREATE TABLE IF NOT EXISTS `auth_msg_archive` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ucp_id` VARBINARY(32) UNIQUE NOT NULL, @@ -235,7 +239,7 @@ CREATE TABLE `auth_msg_archive` PRIMARY KEY (`id`) ); -CREATE TABLE `sdp_msg_archive` +CREATE TABLE IF NOT EXISTS `sdp_msg_archive` ( `id` int(11) NOT NULL AUTO_INCREMENT, `auth_msg_id` int(11) DEFAULT NULL, @@ -261,7 +265,7 @@ CREATE TABLE `sdp_msg_archive` PRIMARY KEY (`id`) ); -CREATE TABLE `dt_task` +CREATE TABLE IF NOT EXISTS `dt_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, `node_id` varchar(64) DEFAULT NULL, @@ -276,7 +280,7 @@ CREATE TABLE `dt_task` UNIQUE KEY `uk_task` (`node_id`, `task_type`, `blockchain_id`) ); -CREATE TABLE `dt_active_node` +CREATE TABLE IF NOT EXISTS `dt_active_node` ( `id` int(11) NOT NULL AUTO_INCREMENT, `node_id` varchar(64) DEFAULT NULL, diff --git a/r-bootstrap/src/test/resources/node_keys/ps/relayer.crt b/r-bootstrap/src/test/resources/node_keys/ps/relayer.crt new file mode 100644 index 0000000..1286b2d --- /dev/null +++ b/r-bootstrap/src/test/resources/node_keys/ps/relayer.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgIUHKdJfN0vAGRBpFYlb+PJMjwC1qgwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCQ04xDjAMBgNVBAgMBW15a2V5MQ4wDAYDVQQHDAVteWtl +eTEOMAwGA1UECgwFbXlrZXkxDjAMBgNVBAsMBW15a2V5MRAwDgYDVQQDDAdyZWxh +eWVyMCAXDTIzMDkxMzA3NDUxOVoYDzIxMjMwODIwMDc0NTE5WjBfMQswCQYDVQQG +EwJDTjEOMAwGA1UECAwFbXlrZXkxDjAMBgNVBAcMBW15a2V5MQ4wDAYDVQQKDAVt +eWtleTEOMAwGA1UECwwFbXlrZXkxEDAOBgNVBAMMB3JlbGF5ZXIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5yoied/9zZjN9O2QWvTeCUpB4FodMvtZY +rRxRfNZSerVLel7ikSaNG4bqkM0yQ2sGkq7alSILsrlSXzb7floR2ubEcGd3FFTC +AgJA1j/fUzzycLH9LizmRueRPTmFC7QerFkmqjg/7TTiSZw5rFgZNx0rdtjTCtgc +Rp+vCtIsG9D+XhErxllkzUUuNT/5nwxiXjEYsWQLheW++e78/AaGspJ0PpJiTxaS +tb3EQAFjkwM1ZFfUbwMHunwcrrkEtCxwuZH37Tj56h1ZdJdTP2P9ZLPzKJ9w5Sv/ +8nzDPsAc7kcn3ZG31hi1RIxLTLdGJDmsiPqaasO/tPf0E4R89w/HAgMBAAGjUzBR +MB0GA1UdDgQWBBS8lNjLZ1R80fyfQJZh3ITL+T/5vzAfBgNVHSMEGDAWgBS8lNjL +Z1R80fyfQJZh3ITL+T/5vzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQC5bx0w40xrI5ASqdOhwtvH/exbdtqJ1NaxKDsn0Ry5L9sd0I8GfnHK2/Uw +CcTVe05Skcom6eyjsrqv7Uf6GPmHalRoSbV9stqMZ0+vl3UUebH8s//2v2tZ6GHy +KUoJY60gUsY0Q+Odg8pdeJ+YGddDAN9fzRsrWnGyrPWSTxO8H1LirHIu339otxht +3o2fbFuY3Z3UMXa1KgsbEZj6wRoCRJgfgqYJw72aYFYnsfw3VM7e9TO5A/GLpqtm +nv3wGWl7y0oQ517YzGM2Qilax3BoRGQObI5sd1LUCy74d/JnEGFuXBNvoPBzUlqe +5U6ucdxJcL+bjKOW2y/QEdK3l3c5 +-----END CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/node_keys/ps/relayer.key b/r-bootstrap/src/test/resources/node_keys/ps/relayer.key new file mode 100644 index 0000000..2a46f11 --- /dev/null +++ b/r-bootstrap/src/test/resources/node_keys/ps/relayer.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5yoied/9zZjN9 +O2QWvTeCUpB4FodMvtZYrRxRfNZSerVLel7ikSaNG4bqkM0yQ2sGkq7alSILsrlS +Xzb7floR2ubEcGd3FFTCAgJA1j/fUzzycLH9LizmRueRPTmFC7QerFkmqjg/7TTi +SZw5rFgZNx0rdtjTCtgcRp+vCtIsG9D+XhErxllkzUUuNT/5nwxiXjEYsWQLheW+ ++e78/AaGspJ0PpJiTxaStb3EQAFjkwM1ZFfUbwMHunwcrrkEtCxwuZH37Tj56h1Z +dJdTP2P9ZLPzKJ9w5Sv/8nzDPsAc7kcn3ZG31hi1RIxLTLdGJDmsiPqaasO/tPf0 +E4R89w/HAgMBAAECggEALihtTMGPS4JGpIRO139m/Q+6KLugHacD5cAMWwpizNcD +l57tV+ir5T7DhB85e5VYT6e9nTyT2sgqVpalOFIp2mnCD2fFrMX2UMTi/my8LaWm +bK0m6zm/me5ftau6UJ2ZwE0dUQfVNbye+OFE2OdCc/FHY/cA4128VzoMjhZoJODP +4T/HDUW1SVGu2C9VzsixFSiLDUL/FbwPKNdYsiSZgBoJpGZzBXMdlGk0Ag/4EtY2 +63R4Z6QVfgPJlYcjyDHTg0aA9NUOvcK73+mTSagkxD4yNmlkYkGxebwLUrqYbx/K +GKKz5qiMAtIMT2LVFCHTlTlnbH3/5qXZqNmNofOxUQKBgQDgPdSYSOYuZYecb3Cm +hJKFPvb64oscFA1nyTV+Yko8cCIUAih2jbTJr4jgrFD7YyWnAupp5mhEzUCxCOlW +JV2cSXaRzrg8FDDitX+VYoP7Q7NNZm0sbR7dauo8O2VHiduwPzOnns0XQpd4gEw5 +p0vkj67UM0NhD9Q+uGufZ0KwlQKBgQDUGqJvd6pC2S0Q5UlE7dsQ13evoG97PkS/ +60m23LXR8EVPpcAVCi8l/xY3Uqy7zvVd7pAh4F8GZRiZx/GkKg83nb/GF1BqUNYK +A+ATyD0lzX0ZX0yfM5o5ZY6CRYgaPdnfot99VkDHqRuXZwSSQ+PAc7ima5jF00oo +CXMG5ZJb6wKBgD/CFNJ/18LVZ2uZXARcqXRtZLgM9dSz9uPNmAIpXEY289mQi4S5 +4e9+k4KePCBeHnSQJaqasobtfTlY+U9fLHCliqsGhee/Le8n//CvpCsrIq4dM2lw +VVhLb/JUSQXAMtC4B63fPx0f+AVxJTs3UCLan7ECKmRMpeJ3eVJHf/TJAoGBAJ/9 +00dXsaOpJDhZbBJFhPhIP6zHzS/ewYcvGTSJedD5d57jvWdhfj0gFIb4ovkr3KPV +Hv2evK8bNRpS2vBlFYNzR8RJs8vuW/XEBJOHeLB6N4IbA7YW+5+N/pg/kLGStDTg +K8rkdArngbuL5sPZ2ANEhyVphhy7C3X14sFyDBuvAoGBAI6BNWnvpfYvjjLD8+5P +tk2YCC6C6zo91xjRBnJj6UHWB5T2pV8cJO4m8Ci0oWIh/QI3u/ae8ip57jrqE9T+ +oJpgDLKe5nVBv335qx5HqmwYsCuR1EBdvObJmgEZ9GJHmjqH+KTHAoix9YgAtcse +dmXOcuUOS8XUTpWhqhA9tw8J +-----END PRIVATE KEY----- diff --git a/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.crt b/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.crt new file mode 100644 index 0000000..31d6fe4 --- /dev/null +++ b/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.crt @@ -0,0 +1,47 @@ +-----BEGIN CERTIFICATE----- +MIIDnDCCAoSgAwIBAgIJANoR+ubebhQbMA0GCSqGSIb3DQEBCwUAMHwxETAPBgNV +BAoMCGFudGNoYWluMQ4wDAYDVQQLDAVvZGF0czElMCMGA1UEAwwcYW50Y2hhaW4u +b2RhdHNfdGxzLnNpdC5vZGF0czERMA8GA1UEBAwIdGxzLnJvb3QxHTAbBgNVBAkM +FENOLlpoZWppYW5nLkhhbmd6aG91MB4XDTIzMDYwNTE0Mzc0NloXDTMzMDYwMjE0 +Mzc0NlowcTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQH +DAhIYW5nemhvdTERMA8GA1UECgwIYW50Y2hhaW4xDjAMBgNVBAsMBW9kYXRzMRkw +FwYDVQQDDBBkcy50bHMuc2l0Lm9kYXRzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAz4nUv+ykC+xZuWgupkKQg3VIoTN4yz0o9lriqpufg+Qteh2wniAR +dWjdrkHpNYBJtMMz1bEL84w8yC+kqM5IOFpDsnPHLRqQb2yktcjBssyVe71BvuGU +7wT+N7DQUbIzTg7F+yzCbNij7NjUacV70EKdCkRBqIYSLEKbzMBB4r/huZYuPZad +rhIXuoh6CdotVOE4iZGwUx0pqJCjQHN9DZ5lZX302dK6cqXvtr79BkRUfwUcqmNU +pl18jMUKi+V/S54py92YnJFdpd//QmdaAY7WaDImtnpuafw+tRNZ0FR0ivejic/y +jfqW2HTi7GOtyVlvJa2KHq3oKtGiA0jbjwIDAQABoywwKjAJBgNVHRMEAjAAMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEA +lDmUT3expsbDPiDB0L1R4JVjRck+0KMG0kUKt5GkApwvQOaTXLWpS9XoXxN2j7Hf +UGVHW18KmG3zMn3ZwT5koyPcHohnq8SoDCNGf0XCT9WHaDpSnmZmrwY1zdTNcCkM +kphnHdNERM8xAH1dXX+MW7oqzIxVkQU9NI8NRm+u0aRZUs+kMAoz/NNHUgR+pPQw +GUAzwoASp+LiTYsXM6XBW8OpB3PM6nOEYzpmbzE2LYdHxvS4mkUl74Cyz31L0PSq +Q45YA8S2qdqNCWgo+vIFIJqhZf8ymw9VRHGFpgqufZRbkgAxMWkast2AXGaOjUvB +N92eu9p3hyI/j1XOLD9CRA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIUMsj12N9ug/GDDeXT3miO5e+8JgQwDQYJKoZIhvcNAQEF +BQAwgYExCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDDljJfkuqzlpKnlqIHor5rkv6Hn +lLXlrZDllYbliqHmnI3liqHmnInpmZDlhazlj7gxHjAcBgNVBAsMFeWPr+S/oei6 +q+S7vee9kee7nFJTQTEXMBUGA1UEAwwO6JqC6JqB6YeR5pyNQ0EwHhcNMjExMTEx +MDM1MjQ0WhcNMjQxMTEwMDM1MjQ0WjB8MREwDwYDVQQKDAhhbnRjaGFpbjEOMAwG +A1UECwwFb2RhdHMxJTAjBgNVBAMMHGFudGNoYWluLm9kYXRzX3Rscy5zaXQub2Rh +dHMxETAPBgNVBAQMCHRscy5yb290MR0wGwYDVQQJDBRDTi5aaGVqaWFuZy5IYW5n +emhvdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZ+TkI+HFdukDRP +j28P2a2s+2JALFkOpzEHYpyunY4+XywWfDgClf3xpj80tssvNOSrnFPPx41uImln +euu1vVv18e6YU4Q5D1wGB0YTez9yE4QMLeXqAUZq3U9FmIOK9OO73+4c1dnLjsP6 +jO0yG02bsiiWSScBAHWM9u/Fx053NF3LQfnTl7MtzOCjVpPakl88B9retajjCL3O +1b2Tj0I0wnr07q/2OduB5Ee/MwvTMbfdIXeDSRJPUCp1edW6rRUynWvE0W3pC//Q +vG7TEYUgxfdK7nJYfDLF5IWlcvSBtzeMEMkR2wKsnU5R+acT0ISQ1YtOI1X10uST +6Owo9BUCAwEAAaOBkDCBjTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIF4DAdBgNV +HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEQYJYIZIAYb4QgEBBAQDAgbAMB8G +A1UdIwQYMBaAFCHP1ToZY2a5xRK4kKL101uz69taMB0GA1UdDgQWBBTTyQC3cdbv +dfCP1H2ttk5yy+/54jANBgkqhkiG9w0BAQUFAAOCAQEArvjBz/BLkq5vrmr+9Uhw +tLs/B/H42pIfdIr/rxxnhUcfk5vhoi7AsPWZ9CusdWMwBUgB/sd7uEG8kebdScxZ +o9d0ZpNUCOgGL4RbYy7HUTmWFDGKSjyzuk5FrjW3nNyqXPTWfsPQ4nAINKHQMHi/ +QsuR805AMOS0sI/2PLEc+pRvmzXZ9LfiBrFcT5x+FX9sMPftfCbO5jTHAdz7OfqI +3c+q+TFIM77B+wibYckPuq5S+lZW2V0JZN6mBUEhwfSEtQv+Xs+c9CHsRjMO1svn +I+dxuTNpCjjetJZzx7mwr5e1euara3iiktXYcsqdJeOiasnoWVBho9VojJCDxN3h +3A== +-----END CERTIFICATE----- diff --git a/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.key b/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.key new file mode 100644 index 0000000..21ec300 --- /dev/null +++ b/r-bootstrap/src/test/resources/node_keys/relayer/node_tls.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDPidS/7KQL7Fm5 +aC6mQpCDdUihM3jLPSj2WuKqm5+D5C16HbCeIBF1aN2uQek1gEm0wzPVsQvzjDzI +L6Sozkg4WkOyc8ctGpBvbKS1yMGyzJV7vUG+4ZTvBP43sNBRsjNODsX7LMJs2KPs +2NRpxXvQQp0KREGohhIsQpvMwEHiv+G5li49lp2uEhe6iHoJ2i1U4TiJkbBTHSmo +kKNAc30NnmVlffTZ0rpype+2vv0GRFR/BRyqY1SmXXyMxQqL5X9LninL3ZickV2l +3/9CZ1oBjtZoMia2em5p/D61E1nQVHSK96OJz/KN+pbYdOLsY63JWW8lrYoeregq +0aIDSNuPAgMBAAECggEAUH0rFrgnMzyZ269NEEwWkfVFksdMnL3+ifTbncE3T0aK +YKbtHZZgTwG5n+COGqLDcyiVjNXaRb1owVbA7Hr8RWa0hJwkbhi0VZJ0GtBeVwLD +IrdWrTn9selk0qJvWI/dF/Pg0rYcPWyTvsKlNtRRXYbIMvgf4sUEfUfj9rfFlbOT +6yewZklkPqS1fMXM0D8kZuHyMz/uHnb7LCKq1iBwExQSKMYZ8RWcxzfdTIBCn/fS +XUUNeXXR6xVJxLlZFz9Ul5h3v9R5B50iiznm30+slwCsfaj32dSjj9aDLJfFW39R +RQyx+NDyg5eI8JaJfbHfrH1U0Ei8zCgtIpZ6N6QmGQKBgQD6JAKqVU9d93vkCJb9 +Altksktk+JgFxsCTrKlb7m4iSb/ADxkPa2SgLmVZW31GSFwpsNhhvxIKRmPF4w+G +5XltQmCjbZLp9eWaJthnId2xonC2AHMUbSQisog2+HI5Fb8Q4fqxYFCNgvNSW1Ht +CRowzrb2kUCdZaMTUuiKQVOPWwKBgQDUZlpCq9aeUbS/vcuPuPG1cpvb1VzhupIs +LS1hRyutLdiHndeegKJ6d0psJq9v2lDFimLPLczCZNFoKWFqOOzaRzueJ8ejCYQR +fXld1pqPPC9yCRM1MB831vpUg75JasGT10JaFHAaIddKDB0l7c8CTGYquiO7DH00 +wuyj3jdu3QKBgA1BdUaziKYxJEacUewMgO1gKXCrX9sGglQRFVSC2SFGCTxTUH+p +sEZwzvwiRgxAb2niLkVXy8vxmP32n28FoB6zIs3mU5/EYSt/HX6xo77zHcf3VCHj ++sM/9Mn89oih52Mspo1ZzksBgoV9w2StU878VWPRpLvyk+bFQP96oMP7AoGAUQKI +wo0P2mqHaepVzYdYiUAhOgNy3ZVvUvIYMNYYToEB6RfGuWmOju8Yr49BsoOt8uoJ +LcPmKO6TAAtoYD899zLcBkJd3k0u1gzpUWUcpizqW7AiZ1LnVUDlUX6+APp6woyD +fh/1ccIeftuH8oN1RQcmoH1GS31D8++0mfuTYPECgYA6w5OO+xyE83IiKTaZW3Xk +JsL9lhnbt3PSvPOfr58dOfdypTPVfIrjb8iuwJJtq2mR/A49+KYZEhnvQ/vGEY1s +NB4QIeiB0Otet38M1KcwrrQNapL/G9E3mWMcyH5ng1f15mFZF6cAJplbp+1WgbAy +yLcsggbJXxq480BxP2zKSw== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/r-commons/pom.xml b/r-commons/pom.xml index 44f85ec..55d3c1f 100644 --- a/r-commons/pom.xml +++ b/r-commons/pom.xml @@ -34,5 +34,13 @@ com.alipay.antchain.bridge antchain-bridge-commons + + org.bouncycastle + bcprov-jdk18on + + + org.apache.commons + commons-collections4 + \ No newline at end of file diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/BlockchainStateEnum.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/BlockchainStateEnum.java index 6b6831c..c451869 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/BlockchainStateEnum.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/BlockchainStateEnum.java @@ -17,6 +17,7 @@ package com.alipay.antchain.bridge.relayer.commons.constant; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.annotation.JSONField; import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; import lombok.AllArgsConstructor; @@ -26,10 +27,15 @@ @AllArgsConstructor public enum BlockchainStateEnum { + INIT("INIT"), + RUNNING("RUNNING"), - STOPPED("STOP"); + STOPPED("STOP"), + + UPDATE("UPDATE"); + @JSONField private final String code; public static BlockchainStateEnum parseFromValue(String value) { diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/AMServiceStatusEnum.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/OnChainServiceStatusEnum.java similarity index 89% rename from r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/AMServiceStatusEnum.java rename to r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/OnChainServiceStatusEnum.java index ce4c355..3571781 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/AMServiceStatusEnum.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/OnChainServiceStatusEnum.java @@ -16,9 +16,11 @@ package com.alipay.antchain.bridge.relayer.commons.constant; -public enum AMServiceStatusEnum { +public enum OnChainServiceStatusEnum { INIT, - FINISH_DEPLOY_AM_CONTRACT; + DEPLOY_FINISHED, + + SETUP_FINISHED; } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/SDPMsgProcessStateEnum.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/SDPMsgProcessStateEnum.java index 9475575..8e4bbb6 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/SDPMsgProcessStateEnum.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/SDPMsgProcessStateEnum.java @@ -25,6 +25,10 @@ public enum SDPMsgProcessStateEnum { PENDING("am_msg_pending"), + MSG_ILLEGAL("am_msg_fail"), + + MSG_REJECTED("am_msg_rejected"), + TX_PENDING("tx_pending"), TX_SUCCESS("tx_success"), diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/UpperProtocolTypeBeyondAMEnum.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/UpperProtocolTypeBeyondAMEnum.java index a5af142..a8648b0 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/UpperProtocolTypeBeyondAMEnum.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/constant/UpperProtocolTypeBeyondAMEnum.java @@ -20,8 +20,10 @@ import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.AllArgsConstructor; +import lombok.Getter; @AllArgsConstructor +@Getter public enum UpperProtocolTypeBeyondAMEnum { SDP(0); diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/AntChainBridgeRelayerException.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/AntChainBridgeRelayerException.java index a765ce8..55664b4 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/AntChainBridgeRelayerException.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/AntChainBridgeRelayerException.java @@ -16,6 +16,7 @@ package com.alipay.antchain.bridge.relayer.commons.exception; +import cn.hutool.core.util.StrUtil; import com.alipay.antchain.bridge.commons.exception.base.AntChainBridgeBaseException; public class AntChainBridgeRelayerException extends AntChainBridgeBaseException { @@ -24,6 +25,14 @@ public AntChainBridgeRelayerException(RelayerErrorCodeEnum errorCode, String lon super(errorCode.getErrorCode(), errorCode.getShortMsg(), longMsg); } + public AntChainBridgeRelayerException(RelayerErrorCodeEnum errorCode, String formatStr, Object... objects) { + super(errorCode.getErrorCode(), errorCode.getShortMsg(), StrUtil.format(formatStr, objects)); + } + + public AntChainBridgeRelayerException(RelayerErrorCodeEnum errorCode, Throwable throwable, String formatStr, Object... objects) { + super(errorCode.getErrorCode(), errorCode.getShortMsg(), StrUtil.format(formatStr, objects), throwable); + } + public AntChainBridgeRelayerException(RelayerErrorCodeEnum errorCode, String longMsg, Throwable throwable) { super(errorCode.getErrorCode(), errorCode.getShortMsg(), longMsg, throwable); } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/RelayerErrorCodeEnum.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/RelayerErrorCodeEnum.java index 62bd0d6..be82efd 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/RelayerErrorCodeEnum.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/exception/RelayerErrorCodeEnum.java @@ -57,6 +57,38 @@ public enum RelayerErrorCodeEnum { DAL_SYSTEM_CONFIG_ERROR("010a", "sys config data error"), + DAL_DOMAIN_SPACE_ERROR("010b", "domain space cert data error"), + + CORE_BLOCKCHAIN_ERROR("0201", "blockchain error"), + + CORE_BLOCKCHAIN_CLIENT_INIT_ERROR("0202", "blockchain client init error"), + + CORE_PLUGIN_SERVER_ERROR("0203", "plugin server error"), + + CORE_BBC_CALL_ERROR("0204", "call bbc error"), + + CORE_RELAYER_NETWORK_ERROR("0205", "relayer network error"), + + SERVICE_CORE_PROCESS_AUTH_MSG_PROCESS_FAILED("0301", "process auth msg failed"), + + SERVICE_CORE_PROCESS_PROCESS_CCMSG_FAILED("0302", "process ccmsg failed"), + + SERVICE_COMMITTER_PROCESS_CCMSG_FAILED("0303", "commit ccmsg failed"), + + SERVICE_COMMITTER_PROCESS_COMMIT_SDP_FAILED("0304", "commit ccmsg to blockchain failed"), + + SERVICE_MULTI_ANCHOR_PROCESS_START_ANCHOR_FAILED("0305", "start anchor for blockchain failed"), + + SERVICE_MULTI_ANCHOR_PROCESS_POLLING_TASK_FAILED("0306", "polling block task failed"), + + SERVICE_MULTI_ANCHOR_PROCESS_SYNC_TASK_FAILED("0307", "sync block task failed"), + + SERVICE_MULTI_ANCHOR_PROCESS_REMOTE_AM_PROCESS_FAILED("0308", "remote am process failed"), + + SERVICE_ARCHIVE_PRECESS_FAILED("0309", "archive process failed"), + + SERVER_REQUEST_FROM_RELAYER_REJECT("0401", "relayer request rejected"), + /** * */ diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AnchorProcessHeights.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AnchorProcessHeights.java index 6e43d6a..e346cf0 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AnchorProcessHeights.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AnchorProcessHeights.java @@ -67,6 +67,8 @@ public static String getKey(String product, String blockchainId) { private final Map processHeights = MapUtil.newHashMap(); + private final Map modifiedTimeMap = MapUtil.newHashMap(); + private long lastUpdateTime = 0L; public AnchorProcessHeights(String product, String blockchainId) { diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgPackage.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgPackage.java new file mode 100644 index 0000000..51a1b9b --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgPackage.java @@ -0,0 +1,293 @@ +/* + * 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.relayer.commons.model; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.List; + +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 lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AuthMsgPackage { + + public static int UNORDERED_BIT_MASK = 0x00000001; + public static byte UNORDERED_MSG = 1; + public static byte ORDERED_MSG = 0; + + public static int TRUSTED_BIT_MASK = 0x00000002; + public static byte TRUSTED_FLAG = 1; + public static byte UNTRUSTED_FLAG = 0; + + public static int NOTARY_BIT_MASK = 0x00000004; + public static byte NOTARY_FLAG = 1; + public static byte NON_NOTARY_FLAG = 0; + + public static int FLAGS_LEN = 4; + + public static int RECEIVER_IDENTITY_BYTEARRAY_LEN = 32; + public static int SENDER_DOMAIN_LEN = 128 + 1; // 1 byte for len ; 128 bytes for domain + + public static AuthMsgPackage convertFrom(List sdpMsgWrappers, PTCProofResult ptcProofResult) { + AuthMsgPackage authMsgPackage = new AuthMsgPackage(); + sdpMsgWrappers.forEach( + sdpMsgWrapper -> authMsgPackage.addAmMsg( + Base64.getEncoder().encodeToString( + ObjectUtil.isNull(ptcProofResult) ? + PTCProofResult.generateEmptyProofForAM( // for now, we generate empty proof utils PTC is ready + sdpMsgWrapper.getAuthMsgWrapper() + ).getPtcProof() + : ptcProofResult.getPtcProof() + ), + sdpMsgWrapper.getAuthMsgWrapper() + .getLedgerInfo() + .getOrDefault(AuthMsgWrapper.AM_HINTS, StrUtil.EMPTY), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMessage().encode() + ) + ); + + authMsgPackage.setSdpMsgWrapper(sdpMsgWrappers.get(0)); + + return authMsgPackage; + } + + private int flags = 0; + + private SDPMsgWrapper sdpMsgWrapper; + + private List proofs; + private List hints; + private List amPkgs; + + public AuthMsgPackage() { + this.proofs = ListUtil.toList(); + this.hints = ListUtil.toList(); + this.amPkgs = ListUtil.toList(); + } + + public void addAmMsg(String proof, String hint, byte[] amPkg) { + this.proofs.add(proof); + this.hints.add(hint); + this.amPkgs.add(amPkg); + } + + public void setUnordered(byte flag) { + this.flags = (flag == UNORDERED_MSG) ? + this.flags | UNORDERED_BIT_MASK : + this.flags & (~UNORDERED_BIT_MASK); + } + + public byte getUnordered() { + return (this.flags & UNORDERED_BIT_MASK) == 0 ? ORDERED_MSG : UNORDERED_MSG; + } + + public byte getTrusted() { + return (this.flags & TRUSTED_BIT_MASK) == 0 ? UNTRUSTED_FLAG : TRUSTED_FLAG; + } + + public void setTrusted(byte flag) { + this.flags = (flag == TRUSTED_FLAG) ? + this.flags | TRUSTED_BIT_MASK : + this.flags & (~TRUSTED_BIT_MASK); + } + + public byte getNotary() { + return (this.flags & NOTARY_BIT_MASK) == 0 ? NON_NOTARY_FLAG : NOTARY_FLAG; + } + + public void setNotary(byte flag) { + this.flags = (flag == NOTARY_FLAG) ? + this.flags | NOTARY_BIT_MASK : + this.flags & (~NOTARY_BIT_MASK); + } + + // return byte[] with format: {identity, [proof], [proof]} + // + // identity: 32 byte + // proof: + // - (32) 4bytes + // - + // - (32) 4bytes + // - + // + public byte[] encode() throws Exception { + if (this.proofs.size() == 0 || this.hints.size() == 0) { + return null; + } + if (this.proofs.size() != this.hints.size()) { + return null; + } + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + // 标志 + ByteBuffer dbuf = ByteBuffer.allocate(FLAGS_LEN); + dbuf.putInt(this.flags); + stream.write(dbuf.array()); + + byte[] rawReceiver = HexUtil.decodeHex(sdpMsgWrapper.getMsgReceiver()); + // 增加接受方identity + if (rawReceiver.length == RECEIVER_IDENTITY_BYTEARRAY_LEN) { + try { + stream.write(rawReceiver); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // 填充发送方域名 + byte sender_domain_len = (byte) sdpMsgWrapper.getSenderBlockchainDomain().length(); + ByteBuffer dbuf2 = ByteBuffer.allocate(SENDER_DOMAIN_LEN); + dbuf2.put(sender_domain_len); + dbuf2.put(sdpMsgWrapper.getSenderBlockchainDomain().getBytes()); + stream.write(dbuf2.array()); + + for (int i = 0; i < this.hints.size(); i++) { + String hint = this.hints.get(i); + String proof = this.proofs.get(i); + + // Encode hint into pkg + byte[] hintBytes = hint.getBytes(); + int hintLen = hintBytes.length; + stream.write((hintLen >>> 24) & 0xFF); + stream.write((hintLen >>> 16) & 0xFF); + stream.write((hintLen >>> 8) & 0xFF); + stream.write((hintLen) & 0xFF); + try { + stream.write(hintBytes); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Encode proof into pkg + byte[] proofBytes = Base64.getDecoder().decode(proof); // proof: base64 decode before encode into pkg + int proofLen = proofBytes.length; + stream.write((proofLen >>> 24) & 0xFF); + stream.write((proofLen >>> 16) & 0xFF); + stream.write((proofLen >>> 8) & 0xFF); + stream.write((proofLen) & 0xFF); + + try { + stream.write(proofBytes); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Encode am pkt + if (this.getNotary() == NOTARY_FLAG) { + byte[] amPkg = this.amPkgs.get(i); + + // Encode proof into pkg + int amPkg_len = amPkg.length; + stream.write((amPkg_len >>> 24) & 0xFF); + stream.write((amPkg_len >>> 16) & 0xFF); + stream.write((amPkg_len >>> 8) & 0xFF); + stream.write((amPkg_len) & 0xFF); + + try { + stream.write(amPkg); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return stream.toByteArray(); + } + + public static byte[] extractProofs(byte[] encodedAuthMsgPackage) { + int offset = RECEIVER_IDENTITY_BYTEARRAY_LEN + SENDER_DOMAIN_LEN + FLAGS_LEN; + int proofs_len = encodedAuthMsgPackage.length - offset; + byte[] proofs = new byte[proofs_len]; + System.arraycopy(encodedAuthMsgPackage, offset, proofs, 0, proofs_len); + return proofs; + } + + public static byte[] extractReceiverIdentity(byte[] encodedAuthMsgPackage) { + int offset = FLAGS_LEN; + byte[] identity = new byte[RECEIVER_IDENTITY_BYTEARRAY_LEN]; + System.arraycopy(encodedAuthMsgPackage, offset, identity, 0, RECEIVER_IDENTITY_BYTEARRAY_LEN); + return identity; + } + + public static String extractSenderDomain(byte[] encodedAuthMsgPackage) { + int offset = RECEIVER_IDENTITY_BYTEARRAY_LEN + FLAGS_LEN; + byte[] domain = new byte[SENDER_DOMAIN_LEN - 1]; + byte domain_len = encodedAuthMsgPackage[offset]; + System.arraycopy(encodedAuthMsgPackage, offset + 1, domain, 0, domain_len); + StringBuilder out = new StringBuilder(); + for (int i = 0; i < domain_len; i++) { + out.append((char) (domain[i])); + } + return out.toString(); + } + + + public static int extractFlags(byte[] encodedAuthMsgPackage) { + int offset = 0; + byte[] flags = new byte[FLAGS_LEN]; + System.arraycopy(encodedAuthMsgPackage, offset, flags, 0, FLAGS_LEN); + ByteBuffer wrapped = ByteBuffer.wrap(flags); + return wrapped.getInt(); + } + + public static byte extractUnorderedFlag(byte[] encodedAuthMsgPackage) { + int flags = extractFlags(encodedAuthMsgPackage); + return (flags & UNORDERED_BIT_MASK) == 0 ? ORDERED_MSG : UNORDERED_MSG; + } + + + public static byte extractTrustedFlag(byte[] encodedAuthMsgPackage) { + int flags = extractFlags(encodedAuthMsgPackage); + return (flags & TRUSTED_BIT_MASK) == 0 ? UNTRUSTED_FLAG : TRUSTED_FLAG; + } + + public static byte extractNotaryFlag(byte[] encodedAuthMsgPackage) { + int flags = extractFlags(encodedAuthMsgPackage); + return (flags & NOTARY_BIT_MASK) == 0 ? NON_NOTARY_FLAG : NOTARY_FLAG; + } + + static private int extractUint32(ByteArrayInputStream in) { + int i = in.read(); + i = (i << 8) + in.read(); + i = (i << 8) + in.read(); + i = (i << 8) + in.read(); + return i; + } + + static private String extractString(ByteArrayInputStream in, int len) { + String s = ""; + while (len > 0) { + s = s + (char) (in.read()); + len--; + } + return s; + } + + static private byte[] extractBytes(ByteArrayInputStream in, int len) { + byte[] s = new byte[len]; + in.read(s, 0, len); + return s; + } +} diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgWrapper.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgWrapper.java index 8218a46..3649e8c 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgWrapper.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/AuthMsgWrapper.java @@ -20,12 +20,17 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; +import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory; import com.alipay.antchain.bridge.commons.core.am.AuthMessageV2; import com.alipay.antchain.bridge.commons.core.am.IAuthMessage; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage; import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgProcessStateEnum; import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgTrustLevelEnum; import com.alipay.antchain.bridge.relayer.commons.constant.UpperProtocolTypeBeyondAMEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -35,11 +40,51 @@ @NoArgsConstructor public class AuthMsgWrapper { + public static String AM_TX_ID = "AM_TX_ID"; + public static String AM_HETEROGENEOUS_RANDOM_UUID = "AM_HETEROGENEOUS_RANDOM_UUID"; // only for heterogeneous chain + public static String AM_BLOCK_HEIGHT = "AM_BLOCK_HEIGHT"; + public static String AM_BLOCK_HASH = "AM_BLOCK_HASH"; + public static String AM_RECEIPT_INDEX = "AM_RECEIPT_INDEX"; + public static String AM_HINTS = "AM_HINTS"; + public static String AM_BLOCK_TIMESTAMP = "AM_BLOCK_TIMESTAMP"; // 链上时间戳 + public static String AM_CAPTURE_TIMESTAMP = "AM_CAPTURE_TIMESTAMP"; // 监听时间戳 + public static String AM_SENDER_GAS_USED = "AM_SENDER_GAS_USED"; + + public static AuthMsgWrapper buildFrom( + String product, + String blockchainId, + String domain, + CrossChainMessage crossChainMessage + ) { + if (CrossChainMessage.CrossChainMessageType.AUTH_MSG != crossChainMessage.getType()) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, + "not a valid auth message type: " + crossChainMessage.getType().name() + ); + } + IAuthMessage authMessage = AuthMessageFactory.createAuthMessage(crossChainMessage.getMessage()); + + AuthMsgWrapper wrapper = new AuthMsgWrapper(); + wrapper.setAuthMessage(authMessage); + wrapper.setMsgSender(authMessage.getIdentity().toHex()); + wrapper.setProduct(product); + wrapper.setBlockchainId(blockchainId); + wrapper.setDomain(domain); + wrapper.setVersion(authMessage.getVersion()); + wrapper.setProtocolType(UpperProtocolTypeBeyondAMEnum.parseFromValue(authMessage.getUpperProtocol())); + wrapper.setTrustLevel( + authMessage.getVersion() >= 2 ? + AuthMsgTrustLevelEnum.parseFromValue(((AuthMessageV2) authMessage).getTrustLevel().ordinal()) + : AuthMsgTrustLevelEnum.NEGATIVE_TRUST + ); + return wrapper; + } + private long authMsgId; - private String product; + private String product = StrUtil.EMPTY; - private String blockchainId; + private String blockchainId = StrUtil.EMPTY; private String domain; @@ -61,6 +106,8 @@ public class AuthMsgWrapper { private Map ledgerInfo = MapUtil.newHashMap(); + private boolean isNetworkAM; + public AuthMsgWrapper( String product, String blockchainId, @@ -110,4 +157,12 @@ public String getUcpIdHex() { public byte[] getRawLedgerInfo() { return JSON.toJSONBytes(ledgerInfo); } + + public void addLedgerInfo(String key, String value) { + this.ledgerInfo.put(key, value); + } + + public void setLedgerInfo(String raw) { + JSON.parseObject(raw).forEach((key, value) -> ledgerInfo.put(key, (String) value)); + } } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java index b4f4205..4394ad2 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/BlockchainMeta.java @@ -17,12 +17,20 @@ package com.alipay.antchain.bridge.relayer.commons.model; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; -import com.alipay.antchain.bridge.relayer.commons.constant.AMServiceStatusEnum; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext; +import com.alipay.antchain.bridge.relayer.commons.constant.OnChainServiceStatusEnum; import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.utils.HeteroBBCContextDeserializer; import lombok.Getter; import lombok.Setter; @@ -30,73 +38,77 @@ @Setter public class BlockchainMeta { + public static String createMetaKey(String product, String blockchainId) { + return product + "_" + blockchainId; + } + @Getter @Setter public static class BlockchainProperties { - public static String AM_CLIENT_CONTRACT_ADDRESS = "am_client_contract_address"; - - public static String SDP_MSG_CONTRACT_ADDRESS = "sdp_msg_contract_address"; - - public static String ANCHOR_RUNTIME_STATUS = "anchor_runtime_status"; - - public static String INIT_BLOCK_HEIGHT = "init_block_height"; - - public static String IS_DOMAIN_REGISTERED = "is_domain_registered"; - - public static String HETEROGENEOUS_BBC_CONTEXT = "heterogeneous_bbc_context"; - - public static String PLUGIN_SERVER_ID = "plugin_server_id"; - - public static String AM_SERVICE_STATUS = "am_service_status"; + public static final String AM_SERVICE_STATUS = "am_service_status"; public static BlockchainProperties decode(byte[] rawData) { - BlockchainProperties blockchainProperties = new BlockchainProperties(); - JSON.parseObject(new String(rawData)).getInnerMap() - .forEach((key, value) -> blockchainProperties.getProperties().put(key, (String) value)); - return blockchainProperties; + JSONObject jsonObject = JSON.parseObject(new String(rawData)); + BlockchainProperties properties = jsonObject.toJavaObject(BlockchainProperties.class); + if (ObjectUtil.isNull(properties)) { + return null; + } + jsonObject.keySet().forEach( + key -> { + if (jsonFieldNameSet.contains(key)) { + return; + } + Object val = jsonObject.get(key); + if (val instanceof String) { + properties.getExtraProperties().put(key, (String) val); + } + } + ); + return properties; } - private Map properties = MapUtil.newHashMap(); + private static final Set jsonFieldNameSet = CollectionUtil.newHashSet( + "am_client_contract_address", + "sdp_msg_contract_address", + "anchor_runtime_status", + "init_block_height", + "is_domain_registered", + "heterogeneous_bbc_context", + "plugin_server_id", + AM_SERVICE_STATUS, + "extra_properties" + ); - public String getAmClientContractAddress() { - return properties.get(AM_CLIENT_CONTRACT_ADDRESS); - } + @JSONField(name = "am_client_contract_address") + private String amClientContractAddress; - public String getSdpMsgContractAddress() { - return properties.get(SDP_MSG_CONTRACT_ADDRESS); - } + @JSONField(name = "sdp_msg_contract_address") + private String sdpMsgContractAddress; - public BlockchainStateEnum getBlockchainState() { - return BlockchainStateEnum.parseFromValue(properties.get(ANCHOR_RUNTIME_STATUS)); - } + @JSONField(name = "anchor_runtime_status") + private BlockchainStateEnum anchorRuntimeStatus; - public Long getInitBlockHeight() { - return Long.parseLong(properties.get(INIT_BLOCK_HEIGHT)); - } + @JSONField(name = "init_block_height") + private Long initBlockHeight; - public boolean isDomainRegistered() { - return BooleanUtil.toBoolean(properties.getOrDefault(IS_DOMAIN_REGISTERED, "")); - } + @JSONField(name = "is_domain_registered") + private Boolean isDomainRegistered; - public String getHeterogeneousBbcContext() { - return properties.get(HETEROGENEOUS_BBC_CONTEXT); - } + @JSONField(name = "heterogeneous_bbc_context", deserializeUsing = HeteroBBCContextDeserializer.class) + private DefaultBBCContext bbcContext; - public String getPluginServerId() { - return properties.get(PLUGIN_SERVER_ID); - } + @JSONField(name = "plugin_server_id") + private String pluginServerId; - public AMServiceStatusEnum getAMServiceStatus() { - return AMServiceStatusEnum.valueOf(properties.get(AM_SERVICE_STATUS)); - } + @JSONField(name = AM_SERVICE_STATUS) + private OnChainServiceStatusEnum amServiceStatus; - public String getExtraProperty(String key) { - return properties.get(key); - } + @JSONField(name = "extra_properties") + private Map extraProperties = MapUtil.newHashMap(); public byte[] encode() { - return JSON.toJSONBytes(properties); + return JSON.toJSONBytes(this); } } @@ -116,11 +128,72 @@ public BlockchainMeta( String alias, String desc, byte[] rawProperties + ) { + this(product, blockchainId, alias, desc, BlockchainProperties.decode(rawProperties)); + } + + public BlockchainMeta( + String product, + String blockchainId, + String alias, + String desc, + BlockchainProperties properties ) { this.product = product; this.blockchainId = blockchainId; this.alias = alias; this.desc = desc; - this.properties = BlockchainProperties.decode(rawProperties); + this.properties = properties; + } + + public String getMetaKey() { + return createMetaKey(this.product, this.blockchainId); + } + + public String getPluginServerId() { + return properties.getPluginServerId(); + } + + public void updateProperties(BlockchainProperties properties) { + if (StrUtil.isNotEmpty(properties.getAmClientContractAddress())) { + this.properties.setAmClientContractAddress(properties.getAmClientContractAddress()); + } + if (StrUtil.isNotEmpty(properties.getSdpMsgContractAddress())) { + this.properties.setSdpMsgContractAddress(properties.getSdpMsgContractAddress()); + } + if (ObjectUtil.isNotNull(properties.getAnchorRuntimeStatus())) { + this.properties.setAnchorRuntimeStatus(properties.getAnchorRuntimeStatus()); + } + if (ObjectUtil.isNotNull(properties.getInitBlockHeight())) { + this.properties.setInitBlockHeight(properties.getInitBlockHeight()); + } + if (ObjectUtil.isNotNull(properties.getIsDomainRegistered())) { + this.properties.setIsDomainRegistered(properties.getIsDomainRegistered()); + } + if (ObjectUtil.isNotNull(properties.getBbcContext())) { + this.properties.setBbcContext(properties.getBbcContext()); + } + if (StrUtil.isNotEmpty(properties.getPluginServerId())) { + this.properties.setPluginServerId(properties.getPluginServerId()); + } + if (ObjectUtil.isNotNull(properties.getAmServiceStatus())) { + this.properties.setAmServiceStatus(properties.getAmServiceStatus()); + } + if (ObjectUtil.isNotEmpty(properties.getExtraProperties())) { + this.properties.getExtraProperties().putAll(properties.getExtraProperties()); + } + } + + public void updateProperty(String key, String value) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(key, value); + BlockchainProperties properties = Objects.requireNonNull( + BlockchainProperties.decode(JSON.toJSONBytes(jsonObject)) + ); + updateProperties(properties); + } + + public boolean isRunning() { + return this.properties.getAnchorRuntimeStatus() == BlockchainStateEnum.RUNNING; } } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DistributedTask.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DistributedTask.java index 94fc936..80ef605 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DistributedTask.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DistributedTask.java @@ -37,7 +37,7 @@ public class DistributedTask { private String ext = StrUtil.EMPTY; - private long timeSlice = 0; + private long startTime = 0; private long timeSliceLength = 0; @@ -53,22 +53,22 @@ public DistributedTask( String blockchainProduct, String blockchainId, String ext, - long timeSlice + long startTime ) { this.nodeId = nodeId; this.taskType = taskType; this.blockchainProduct = blockchainProduct; this.blockchainId = blockchainId; this.ext = ext; - this.timeSlice = timeSlice; + this.startTime = startTime; } public boolean ifFinish(long timeSliceLength) { - return (System.currentTimeMillis() - this.timeSlice) > timeSliceLength; + return (System.currentTimeMillis() - this.startTime) > timeSliceLength; } public boolean ifFinish() { - return (System.currentTimeMillis() - this.timeSlice) > timeSliceLength; + return (System.currentTimeMillis() - this.startTime) > timeSliceLength; } public String getUniqueTaskKey() { diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainCertWrapper.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainCertWrapper.java new file mode 100644 index 0000000..8900428 --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainCertWrapper.java @@ -0,0 +1,43 @@ +/* + * 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.relayer.commons.model; + +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DomainCertWrapper { + + private AbstractCrossChainCertificate crossChainCertificate; + + private DomainNameCredentialSubject domainNameCredentialSubject; + + private String blockchainProduct; + + private String blockchainId; + + private String domain; + + private String domainSpace; +} diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainSpaceCertWrapper.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainSpaceCertWrapper.java new file mode 100644 index 0000000..fc85854 --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/DomainSpaceCertWrapper.java @@ -0,0 +1,43 @@ +/* + * 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.relayer.commons.model; + +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class DomainSpaceCertWrapper { + + public DomainSpaceCertWrapper(AbstractCrossChainCertificate domainSpaceCert) { + this.domainSpaceCert = domainSpaceCert; + this.domainSpace = CrossChainCertificateUtil.getCrossChainDomainSpace(domainSpaceCert).getDomain(); + this.parentDomainSpace = CrossChainCertificateUtil.getParentDomainSpace(domainSpaceCert).getDomain(); + this.desc = StrUtil.EMPTY; + } + + private String domainSpace; + + private String parentDomainSpace; + + private String desc; + + private AbstractCrossChainCertificate domainSpaceCert; +} diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/PTCProofResult.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/PTCProofResult.java new file mode 100644 index 0000000..69f176f --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/PTCProofResult.java @@ -0,0 +1,151 @@ +package com.alipay.antchain.bridge.relayer.commons.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVItem; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVPacket; +import lombok.Getter; +import lombok.Setter; + +/** + * ptc证明转化结果 + */ +@Getter +@Setter +public class PTCProofResult { + + public static short TLV_ORACLE_PUBKEY_HASH = 0; + public static short TLV_ORACLE_REQUEST_ID = 1; + public static short TLV_ORACLE_REQUEST_BODY = 2; + public static short TLV_ORACLE_SIGNATURE_TYPE = 3; + public static short TLV_ORACLE_REQUEST = 4; + public static short TLV_ORACLE_RESPONSE_BODY = 5; // 这里填充RESPONSE 内容 + public static short TLV_ORACLE_RESPONSE_SIGNATURE = 6; + public static short TLV_ORACLE_ERROR_CODE = 7; + public static short TLV_ORACLE_ERROR_MSG = 8; + + /** + * 仅用于向异构链mock proof数据 + */ + public static short TLV_PROOF_SENDER_DOMAIN = 9; + + /** + * 返回内容,PTC返回内容,无AM扩展时是PTC原始返回数据,有AM扩展插件时,填充的是AM插件处理后的数据,如果AM扩展执行发生异常,则BODY被清空填充空数组。 + */ + public static short PTC_RESP_FIELD_BODY = 0; + + /** + * 定长 4字节uint32_t,取值0/1, 0 代表访问AM扩展插件失败,OS层面需要重试; + */ + public static short PTC_RESP_FIELD_AMEXT_CALL_SUCCESS = 1; + + /** + * 定长 4字节uint32_t,取值0/1, 0 代表调用执行AM扩展查询发生异常,详细内容见PTC_RESP_FIELD_AMEXT_RETURN + */ + public static short PTC_RESP_FIELD_AMEXT_EXEC_SUCCESS = 2; + + /** + * 字符串,执行AM扩展查询发生异常具体报错信息 + */ + public static short PTC_RESP_FIELD_AMEXT_EXEC_OUTPUT = 3; + + public static PTCProofResult generateEmptyProofForAM(AuthMsgWrapper authMsgWrapper) { + + List respItems = new ArrayList<>(); + respItems.add( + TLVItem.fromBytes( + PTC_RESP_FIELD_BODY, + authMsgWrapper.getAuthMessage().encode() + ) + ); + TLVPacket respPacket = new TLVPacket((short) 0, respItems); + + List tlvItems = new ArrayList<>(); + tlvItems.add( + TLVItem.fromBytes( + TLV_ORACLE_RESPONSE_BODY, + respPacket.encode() + ) + ); + tlvItems.add( + TLVItem.fromUTF8String( + TLV_PROOF_SENDER_DOMAIN, + authMsgWrapper.getDomain() + ) + ); + TLVPacket tlvPacket = new TLVPacket((short) 0, tlvItems); + + PTCProofResult result = new PTCProofResult(); + result.setPtcProof(tlvPacket.encode()); + result.setSuccess(true); + + return result; + } + + // ptcProof原始字节数组 + private byte[] ptcProof; + + // 证明转化组件执行结果 + private boolean isSuccess; + + private int errorCode; + + private String errorMsg; + + // cid的内容 + private byte[] content; + + public PTCProofResult() {} + + public PTCProofResult(byte[] ptcProof) { + + this.ptcProof = ptcProof; + + TLVPacket tlvPacket = TLVPacket.decode(ptcProof); + + List tlvItems = tlvPacket.getTlvItems(); + + Map tlvItemMap = new HashMap(); + + for (TLVItem tlvItem : tlvItems) { + tlvItemMap.put(tlvItem.getType(), tlvItem); + } + + // 读errorCode + if (!tlvItemMap.containsKey(TLV_ORACLE_ERROR_CODE)) { + throw new RuntimeException("error ptc proof, not TLV_ORACLE_ERROR_CODE"); + } + this.errorCode = tlvItemMap.get(TLV_ORACLE_ERROR_CODE).getUint32Value(); + this.isSuccess = (0 == errorCode); + + // 读errorMsg + if (!tlvItemMap.containsKey(TLV_ORACLE_ERROR_MSG)) { + throw new RuntimeException("error ptc proof, not TLV_ORACLE_ERROR_MSG"); + } + this.errorMsg = tlvItemMap.get(TLV_ORACLE_ERROR_MSG).getUtf8String(); + + // 读response + if (!tlvItemMap.containsKey(TLV_ORACLE_RESPONSE_BODY)) { + throw new RuntimeException("error ptc proof, not TLV_ORACLE_RESPONSE_BODY"); + } + byte[] response = tlvItemMap.get(TLV_ORACLE_RESPONSE_BODY).getValue(); + + // 解response TLV Packet + List respTlvItems = TLVPacket.decode(response).getTlvItems(); + + Map respTlvItemMap = new HashMap(); + + for (TLVItem tlvItem : respTlvItems) { + respTlvItemMap.put(tlvItem.getType(), tlvItem); + } + + // 读content + if (!respTlvItemMap.containsKey(PTC_RESP_FIELD_BODY)) { + throw new RuntimeException("error ptc proof, not PTC_RESP_FIELD_BODY"); + } + this.content = respTlvItemMap.get(PTC_RESP_FIELD_BODY).getValue(); + } +} diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainContent.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainContent.java new file mode 100644 index 0000000..4d49da0 --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainContent.java @@ -0,0 +1,265 @@ +/* + * 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.relayer.commons.model; + +import java.security.Signature; +import java.util.Map; +import java.util.stream.Collectors; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alipay.antchain.bridge.commons.bcdns.*; +import com.alipay.antchain.bridge.commons.bcdns.utils.CrossChainCertificateUtil; +import com.alipay.antchain.bridge.commons.core.base.CrossChainDomain; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldNameConstants; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.trie.PatriciaTrie; + +@Getter +@Setter +@FieldNameConstants +@Slf4j +public class RelayerBlockchainContent { + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + public static class ValidationResult { + + private Map blockchainInfoMapValidated = MapUtil.newHashMap(); + + private Map domainSpaceValidated = MapUtil.newHashMap(); + } + + public static RelayerBlockchainContent decodeFromJson(String jsonStr) { + JSONObject jsonObject = JSON.parseObject(jsonStr); + Map relayerBlockchainInfoMap = jsonObject.getJSONObject(Fields.relayerBlockchainInfoTrie) + .entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> RelayerBlockchainInfo.decode((String) entry.getValue()) + )); + Map trustRootCertMap = jsonObject.getJSONObject(Fields.trustRootCertTrie) + .entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> CrossChainCertificateFactory.createCrossChainCertificate(Base64.decode((String) entry.getValue())) + )); + return new RelayerBlockchainContent( + relayerBlockchainInfoMap, + trustRootCertMap + ); + } + + private PatriciaTrie relayerBlockchainInfoTrie; + + private PatriciaTrie trustRootCertTrie; + + public RelayerBlockchainContent( + Map relayerBlockchainInfoMap, + Map trustRootCertMap + ) { + relayerBlockchainInfoTrie = new PatriciaTrie<>( + relayerBlockchainInfoMap.entrySet().stream() + .map(entry -> MapUtil.entry(StrUtil.reverse(entry.getKey()), entry.getValue())) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )) + ); + trustRootCertTrie = new PatriciaTrie<>( + trustRootCertMap.entrySet().stream() + .map(entry -> MapUtil.entry(StrUtil.reverse(entry.getKey()), entry.getValue())) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )) + ); + } + + public String encodeToJson() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put( + Fields.relayerBlockchainInfoTrie, + relayerBlockchainInfoTrie.entrySet().stream().collect( + Collectors.toMap( + entry -> StrUtil.reverse(entry.getKey()), + entry -> entry.getValue().encode() + ) + ) + ); + jsonObject.put( + Fields.trustRootCertTrie, + trustRootCertTrie.entrySet().stream().collect( + Collectors.toMap( + entry -> StrUtil.reverse(entry.getKey()), + entry -> entry.getValue().encode() + ) + ) + ); + return jsonObject.toJSONString(); + } + + public RelayerBlockchainInfo getRelayerBlockchainInfo(String domain) { + return this.relayerBlockchainInfoTrie.get(StrUtil.reverse(domain)); + } + + public AbstractCrossChainCertificate getDomainSpaceCert(String domainSpace) { + return this.trustRootCertTrie.get(StrUtil.reverse(domainSpace)); + } + + public ValidationResult validate(AbstractCrossChainCertificate trustRootCert) { + AbstractCrossChainCertificate myRootCert = getDomainSpaceCert(CrossChainDomain.ROOT_DOMAIN_SPACE); + if (CrossChainCertificateTypeEnum.BCDNS_TRUST_ROOT_CERTIFICATE != myRootCert.getType()) { + throw new RuntimeException("wrong trust root certificate type: " + myRootCert.getType().name()); + } + + BCDNSTrustRootCredentialSubject validatorSubject = BCDNSTrustRootCredentialSubject.decode(trustRootCert.getCredentialSubject()); + BCDNSTrustRootCredentialSubject myRootSubject = BCDNSTrustRootCredentialSubject.decode(myRootCert.getCredentialSubject()); + if ( + ObjectUtil.notEqual( + validatorSubject.getBcdnsRootOwner().encode(), + myRootSubject.getBcdnsRootOwner().encode() + ) + ) { + throw new RuntimeException("root owner not equal"); + } + + ValidationResult result = new ValidationResult(); + result.setBlockchainInfoMapValidated( + this.relayerBlockchainInfoTrie.entrySet().stream().filter( + entry -> { + String domain = StrUtil.reverse(entry.getKey()); + RelayerBlockchainInfo blockchainInfo = entry.getValue(); + + AbstractCrossChainCertificate parentCert = trustRootCert; + for (String domainSpace : blockchainInfo.getDomainSpaceChain()) { + if (StrUtil.equals(domainSpace, CrossChainDomain.ROOT_DOMAIN_SPACE)) { + continue; + } + + AbstractCrossChainCertificate currentDomainSpaceCert = getDomainSpaceCert(domainSpace); + if (result.getDomainSpaceValidated().containsKey(domainSpace)) { + parentCert = currentDomainSpaceCert; + continue; + } + if ( + !validateDomainSpaceCertWithParent( + domainSpace, + currentDomainSpaceCert, + parentCert + ) + ) { + log.error("proof for domain {} is not valid: domain space {} not pass", domain, domainSpace); + return false; + } + parentCert = currentDomainSpaceCert; + result.getDomainSpaceValidated().put(domainSpace, currentDomainSpaceCert); + } + + return validateDomainSpaceCertWithParent( + domain, + blockchainInfo.getDomainCert().getCrossChainCertificate(), + parentCert + ); + } + ).collect(Collectors.toMap( + entry -> StrUtil.reverse(entry.getKey()), + Map.Entry::getValue + )) + ); + + if (this.relayerBlockchainInfoTrie.size() > result.getBlockchainInfoMapValidated().size()) { + log.error( + "not all domains pass validation ( passed: {}, all: {} )", + StrUtil.join(StrUtil.COMMA, result.getBlockchainInfoMapValidated().keySet()), + StrUtil.join(StrUtil.COMMA, this.relayerBlockchainInfoTrie.keySet()) + ); + } else { + log.info("all domains passed validation"); + } + + return result; + } + + private boolean validateDomainSpaceCertWithParent( + String domainOrSpace, + AbstractCrossChainCertificate domainOrSpaceCert, + AbstractCrossChainCertificate parent + ) { + try { + DomainNameCredentialSubject domainNameCredentialSubject = DomainNameCredentialSubject.decode(domainOrSpaceCert.getCredentialSubject()); + if ( + !StrUtil.equals( + domainNameCredentialSubject.getDomainName().getDomain(), + domainOrSpace + ) + ) { + log.error("domain or space name {} not equal with name {} in cert", + domainOrSpace, domainNameCredentialSubject.getDomainName().getDomain()); + return false; + } + + if ( + ObjectUtil.notEqual( + domainOrSpaceCert.getIssuer().encode(), + parent.getIssuer().encode() + ) + ) { + log.error( + "issuer of domain or space cert {} not equal with {} in parent cert", + Base64.encode(domainOrSpaceCert.getIssuer().encode()), + Base64.encode(parent.getIssuer().encode()) + ); + return false; + } + + if (!domainNameCredentialSubject.getParentDomainSpace().equals( + DomainNameCredentialSubject.decode(parent.getCredentialSubject()).getDomainName())) { + log.error( + "wrong parent of domain or space cert {}, expected is {}, but get {}", + Base64.encode(domainOrSpaceCert.getIssuer().encode()), + DomainNameCredentialSubject.decode(parent.getCredentialSubject()).getDomainName(), + domainNameCredentialSubject.getParentDomainSpace() + ); + return false; + } + + Signature verifier = Signature.getInstance(domainOrSpaceCert.getProof().getSigAlgo()); + verifier.initVerify( + CrossChainCertificateUtil.getPublicKeyFromCrossChainCertificate(parent) + ); + verifier.update(domainOrSpaceCert.getEncodedToSign()); + + return verifier.verify(domainOrSpaceCert.getProof().getRawProof()); + } catch (Exception e) { + log.error( + "Failed to validate crosschain cert {} with parent {} for domain or space {}", + domainOrSpaceCert.encodeToBase64(), parent.encodeToBase64(), domainOrSpace, + e + ); + return false; + } + } +} diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainInfo.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainInfo.java index f8c8c92..2cb6ddf 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainInfo.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerBlockchainInfo.java @@ -16,276 +16,138 @@ package com.alipay.antchain.bridge.relayer.commons.model; -import java.io.ByteArrayInputStream; -import java.net.URLDecoder; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum; +import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** - * 区块链信息
- * relayer间握手交换的区块链信息,主要是区块链的跨链信任根 - * - *
- *     // 区块链的跨链认证信任链
- *      {
- * 	        // 1. UDAG层信任根
- * 	        // 2. 证明转化层信任根
- * 	        // 3. AM层信任根
- *      }
- * 
+ * 区块链信息 */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor public class RelayerBlockchainInfo { - //********************************** - // 1. UDAG层信任根 - //********************************** - /** - * 区块链域名的UDNS CA - */ - private String udnsCA; - - /** - * 区块链域名证书 - */ - private String domainCert; - - /** - * UDNS,可选,如果有证明转化协议,可以使用SignUDNS - */ - private String udns; - - //********************************** - // 2. 证明转化层信任根 - //********************************** - - /** - * tee oracle信任根:信任的SGX远程认证服务方IAS,配置IAS的X509证书 - */ - private String teeOracleIasCert; + public RelayerBlockchainInfo( + DomainCertWrapper domainCert, + List domainSpaceChain, + String amContractClientAddresses + ) { + this.domainCert = domainCert; + this.domainSpaceChain = domainSpaceChain; + this.amContractClientAddresses = amContractClientAddresses; + } - /** - * tee oracle信任根:信任的SGXOracle程序版本,配置SGXOracle的开发者 - */ - private String teeOracleMRSigner; + @JSONField + private List domainSpaceChain; /** - * tee oracle信任根:信任的SGXOracle程序版本,配置SGXOracle的代码摘要值 + * 区块链域名证书 */ - private String teeOracleMREnclave; + @JSONField(serialize = false, deserialize = false) + private DomainCertWrapper domainCert; - /** - * SGXOracle节点AVR,及其signUDNS - *
-     *  pair结构为(AVR, signUDNS)
-     * 
- */ - private List> teeOracleProofs; + @JSONField(serialize = false, deserialize = false) + private AbstractCrossChainCertificate ptcCrossChainCert; - //********************************** - // 3. AM层信任根 - //********************************** + @JSONField + private byte[] tpbta; /** * 信任的am合约,配置合约的id */ - private List amContractClientAddresses; + @JSONField + private String amContractClientAddresses; /** * 链的拓展特征,比如mychain是否支持TEE等. * 方便跨链时候传递一些链的特性,在逻辑上适配不同的链。 */ - private Map chainFeatures; + @JSONField + private Map chainFeatures = new HashMap<>(); /** * json编码值 */ - public String getEncode() { - - JSONObject jsonObject = new JSONObject(); - - jsonObject.put("udnsCA", this.udnsCA); - jsonObject.put("domainCert", this.domainCert); - jsonObject.put("udns", this.udns); - - jsonObject.put("teeOracleIasCert", this.teeOracleIasCert); - jsonObject.put("teeOracleMRSigner", this.teeOracleMRSigner); - jsonObject.put("teeOracleMREnclave", this.teeOracleMREnclave); - - JSONArray jsonArray = new JSONArray(); - for (List proof : teeOracleProofs) { - JSONObject teeOracleProofJson = new JSONObject(); - teeOracleProofJson.put("avr", proof.get(0)); - teeOracleProofJson.put("signUDNS", proof.get(1)); - - jsonArray.add(teeOracleProofJson); - } - - jsonObject.put("teeOracleProofs", jsonArray); - - jsonArray = new JSONArray(); - for (String address : amContractClientAddresses) { - jsonArray.add(address); - } - - jsonObject.put("amContractClientAddresses", jsonArray); - jsonObject.put("chainFeatures", JSON.toJSONString(chainFeatures)); - - return jsonObject.toJSONString(); + public String encode() { + JSONObject jsonObject = (JSONObject) JSON.toJSON(this); + return jsonObject + .fluentPut("product", domainCert.getBlockchainProduct()) + .fluentPut("domain", domainCert.getDomain()) + .fluentPut("domainCert", domainCert.getCrossChainCertificate().encode()) + .fluentPut("ptcCrossChainCert", ObjectUtil.isNull(ptcCrossChainCert) ? "" : ptcCrossChainCert.encode()) + .toJSONString(); } /** * 从json解码 * - * @param jsonEncode + * @param jsonData * @return */ - public static RelayerBlockchainInfo decode(String jsonEncode) { - - JSONObject jsonObject = JSONObject.parseObject(jsonEncode); - - RelayerBlockchainInfo blockchainInfo = new RelayerBlockchainInfo(); - - blockchainInfo.setUdnsCA(jsonObject.getString("udnsCA")); - blockchainInfo.setDomainCert(jsonObject.getString("domainCert")); - blockchainInfo.setUdns(jsonObject.getString("udns")); - - blockchainInfo.setTeeOracleIasCert(jsonObject.getString("teeOracleIasCert")); - blockchainInfo.setTeeOracleMRSigner(jsonObject.getString("teeOracleMRSigner")); - blockchainInfo.setTeeOracleMREnclave(jsonObject.getString("teeOracleMREnclave")); - - JSONArray jsonArray = jsonObject.getJSONArray("teeOracleProofs"); - - List> teeOracleProofs = new ArrayList<>(); - for (int i = 0; i < jsonArray.size(); ++i) { - - teeOracleProofs.add(ListUtil.toList(jsonArray.getJSONObject(i).getString("avr"), - jsonArray.getJSONObject(i).getString("signUDNS"))); + public static RelayerBlockchainInfo decode(String jsonData) { + + JSONObject jsonObject = JSONObject.parseObject(jsonData); + + AbstractCrossChainCertificate domainCrossChainCert = CrossChainCertificateFactory.createCrossChainCertificate( + jsonObject.getBytes("domainCert") + ); + Assert.equals( + CrossChainCertificateTypeEnum.DOMAIN_NAME_CERTIFICATE, + domainCrossChainCert.getType() + ); + + DomainCertWrapper domainCertWrapper = new DomainCertWrapper(); + domainCertWrapper.setBlockchainProduct(jsonObject.getString("product")); + domainCertWrapper.setDomain(jsonObject.getString("domain")); + domainCertWrapper.setCrossChainCertificate( + domainCrossChainCert + ); + domainCertWrapper.setDomainNameCredentialSubject( + DomainNameCredentialSubject.decode(domainCrossChainCert.getCredentialSubject()) + ); + byte[] rawPTCCert = jsonObject.getBytes("ptcCrossChainCert"); + AbstractCrossChainCertificate ptcCrossChainCert = null; + if (ObjectUtil.isNotEmpty(rawPTCCert)) { + ptcCrossChainCert = CrossChainCertificateFactory.createCrossChainCertificate( + jsonObject.getBytes("ptcCrossChainCert") + ); + Assert.equals( + CrossChainCertificateTypeEnum.PROOF_TRANSFORMATION_COMPONENT_CERTIFICATE, + ptcCrossChainCert.getType() + ); } - blockchainInfo.setTeeOracleProofs(teeOracleProofs); - - List addresses = new ArrayList<>(); - - for (int i = 0; i < jsonObject.getJSONArray("amContractClientAddresses").size(); ++i) { - addresses.add(jsonObject.getJSONArray("amContractClientAddresses").getString(i)); - } - - blockchainInfo.setAmContractClientAddresses(addresses); - - blockchainInfo.chainFeatures = new HashMap<>(); - JSONObject features = jsonObject.getJSONObject("chainFeatures"); - if (null != features) { - features.forEach((s, o) -> blockchainInfo.chainFeatures.put(s, o.toString())); - } + RelayerBlockchainInfo blockchainInfo = jsonObject.toJavaObject(RelayerBlockchainInfo.class); + blockchainInfo.setDomainCert(domainCertWrapper); + blockchainInfo.setPtcCrossChainCert(ptcCrossChainCert); + domainCertWrapper.setDomainSpace(blockchainInfo.getDomainSpaceChain().get(0)); return blockchainInfo; } - public String getUdnsCA() { - return udnsCA; - } - - public void setUdnsCA(String udnsCA) { - this.udnsCA = udnsCA; - } - - public String getDomainCert() { - return domainCert; - } - - public void setDomainCert(String domainCert) { - this.domainCert = domainCert; - } - - public String getUdns() { - return udns; - } - - public void setUdns(String udns) { - this.udns = udns; - } - - public String getTeeOracleIasCert() { - return teeOracleIasCert; - } - - public Certificate getTeeOracleIasCertObject() { - try { - String rawCert = this.teeOracleIasCert; - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - if (rawCert.contains("%20")) { - rawCert = URLDecoder.decode(rawCert, "UTF-8"); - } - return certificateFactory.generateCertificate(new ByteArrayInputStream(rawCert.getBytes())); - } catch (Exception e) { - throw new RuntimeException("failed to decode certificate: ", e); - } - } - - public void setTeeOracleIasCert(String teeOracleIasCert) { - this.teeOracleIasCert = teeOracleIasCert; - } - - public String getTeeOracleMRSigner() { - return teeOracleMRSigner; - } - - public void setTeeOracleMRSigner(String teeOracleMRSigner) { - this.teeOracleMRSigner = teeOracleMRSigner; - } - - public String getTeeOracleMREnclave() { - return teeOracleMREnclave; - } - - public void setTeeOracleMREnclave(String teeOracleMREnclave) { - this.teeOracleMREnclave = teeOracleMREnclave; - } - - public List getAmContractClientAddresses() { - return amContractClientAddresses; - } - - public void setAmContractClientAddresses(List amContractClientAddresses) { - this.amContractClientAddresses = amContractClientAddresses; - } - - public void setTeeOracleProofs( - List> teeOracleProofs) { - this.teeOracleProofs = teeOracleProofs; - } - - public List> getTeeOracleProofs() { - return teeOracleProofs; - } - - /** - * Getter method for property chainFeatures. - * - * @return property value of chainFeatures - */ - public Map getChainFeatures() { - return chainFeatures; - } - /** * 添加链的特性。 + * * @param key * @param value */ public void addChainFeature(String key, String value) { - if (null == chainFeatures) { - chainFeatures = new HashMap<>(); - } this.chainFeatures.put(key, value); } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerNodeInfo.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerNodeInfo.java index a487615..b1967e2 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerNodeInfo.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/RelayerNodeInfo.java @@ -17,24 +17,23 @@ package com.alipay.antchain.bridge.relayer.commons.model; import java.io.*; -import java.security.KeyFactory; -import java.security.PrivateKey; import java.security.PublicKey; -import java.security.Signature; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; import java.util.List; import java.util.Map; +import cn.hutool.core.codec.Base64; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.commons.bcdns.utils.ObjectIdentityUtil; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -47,6 +46,19 @@ @Setter public class RelayerNodeInfo { + public static String calculateNodeId(AbstractCrossChainCertificate crossChainCertificate) { + Assert.equals( + CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, + crossChainCertificate.getType() + ); + + return DigestUtil.sha256Hex( + RelayerCredentialSubject.decode( + crossChainCertificate.getCredentialSubject() + ).getApplicant().getRawId() + ); + } + @Getter @Setter public static class RelayerNodeProperties { @@ -55,11 +67,6 @@ public static class RelayerNodeProperties { */ public static String TRUSTED_SERVICE_ID = "trusted_service_id"; - /** - * 节点的am合约地址 - */ - public static String RELAYER_BLOCKCHAIN_INFOS = "relayer_blockchain_infos"; - /** * relayer节点是否要求ssl */ @@ -82,33 +89,6 @@ public static RelayerNodeProperties decodeFromJson(String json) { */ private final Map properties = MapUtil.newHashMap(); - public String getRawRelayerBlockchainInfoJson() { - return properties.getOrDefault(RELAYER_BLOCKCHAIN_INFOS, ""); - } - - public Map getRelayerBlockchainInfoMap() { - String raw = getRawRelayerBlockchainInfoJson(); - if (StrUtil.isEmpty(raw)) { - return MapUtil.newHashMap(); - } - - Map blockchainInfoMap = MapUtil.newHashMap(); - JSONObject jsonObject = JSONObject.parseObject(raw); - for (String domain : jsonObject.keySet()) { - blockchainInfoMap.put(domain, RelayerBlockchainInfo.decode(jsonObject.getString(domain))); - } - - return blockchainInfoMap; - } - - public void setRelayerBlockchainInfoMap(Map blockchainInfoMap) { - JSONObject jsonObject = new JSONObject(); - for (String domain : blockchainInfoMap.keySet()) { - jsonObject.put(domain, blockchainInfoMap.get(domain).getEncode()); - } - properties.put(RELAYER_BLOCKCHAIN_INFOS, jsonObject.toJSONString()); - } - public String getTrustedServiceId() { return properties.get(TRUSTED_SERVICE_ID); } @@ -124,8 +104,68 @@ public long getLastHandshakeTime() { public byte[] encode() { return JSON.toJSONBytes(properties); } + + public String encodeToJson() { + return JSON.toJSONString(properties); + } } + public static RelayerNodeInfo decode(byte[] rawData) { + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawData); + DataInputStream stream = new DataInputStream(byteArrayInputStream); + + try { + RelayerNodeInfo info = new RelayerNodeInfo(); + info.setNodeId(stream.readUTF()); + info.setRelayerCrossChainCertificate( + CrossChainCertificateFactory.createCrossChainCertificate( + Base64.decode(stream.readUTF()) + ) + ); + Assert.equals( + CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, + info.getRelayerCrossChainCertificate().getType() + ); + info.setRelayerCredentialSubject( + RelayerCredentialSubject.decode( + info.getRelayerCrossChainCertificate().getCredentialSubject() + ) + ); + + int endpointSize = stream.readInt(); + + while (endpointSize > 0) { + info.addEndpoint(stream.readUTF()); + endpointSize--; + } + + int domainSize = stream.readInt(); + + while (domainSize > 0) { + info.addDomainIfNotExist(stream.readUTF()); + domainSize--; + } + + try { + String rawProperties = stream.readUTF(); + info.setProperties( + RelayerNodeProperties.decodeFromJson(rawProperties) + ); + String rawBlockchainContent = stream.readUTF(); + info.setRelayerBlockchainContent( + RelayerBlockchainContent.decodeFromJson(rawBlockchainContent) + ); + } catch (EOFException e) { + return info; + } + + return info; + + } catch (Exception e) { + throw new RuntimeException("failed to decode relayer node info: ", e); + } + } //************************************************ // 基础属性 @@ -136,10 +176,9 @@ public byte[] encode() { */ private String nodeId; - /** - * 公钥,X509 Public keyInfo格式公钥,使用Base64编码 - */ - private String nodePublicKey; + private AbstractCrossChainCertificate relayerCrossChainCertificate; + + private RelayerCredentialSubject relayerCredentialSubject; /** * 节点接入点数组 "ip:port" @@ -160,44 +199,92 @@ public byte[] encode() { * 从properties中的json解析出的RelayerBlockchainInfo * 用于缓存,重复利用,修改完后,需要dump回properties */ - public Map blockchainInfoMap = null; + private RelayerBlockchainContent relayerBlockchainContent; //************************************************ // 其他属性 //************************************************ - /** - * Relayer对RelayerNodeInfo的签名 - */ - public byte[] signature; + private String sigAlgo; + + public RelayerNodeInfo( + AbstractCrossChainCertificate relayerCrossChainCertificate, + String sigAlgo, + List endpoints, + List domains + ) { + Assert.equals(CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, relayerCrossChainCertificate.getType()); + this.nodeId = RelayerNodeInfo.calculateNodeId(relayerCrossChainCertificate); + this.relayerCrossChainCertificate = relayerCrossChainCertificate; + this.relayerCredentialSubject = RelayerCredentialSubject.decode(relayerCrossChainCertificate.getCredentialSubject()); + this.sigAlgo = sigAlgo; + this.endpoints = endpoints; + this.domains = domains; + } - public RelayerNodeInfo(String nodeId, String nodePublicKey, List endpoints, - List domains) { + public RelayerNodeInfo( + String nodeId, + AbstractCrossChainCertificate relayerCrossChainCertificate, + String sigAlgo, + List endpoints, + List domains + ) { + Assert.equals(CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, relayerCrossChainCertificate.getType()); this.nodeId = nodeId; - this.nodePublicKey = nodePublicKey; + this.relayerCrossChainCertificate = relayerCrossChainCertificate; + this.relayerCredentialSubject = RelayerCredentialSubject.decode(relayerCrossChainCertificate.getCredentialSubject()); + this.sigAlgo = sigAlgo; this.endpoints = endpoints; this.domains = domains; } - public RelayerNodeInfo(String nodeId, String nodePublicKey, String endpointsStr, - String domainsStr) { + public RelayerNodeInfo( + String nodeId, + AbstractCrossChainCertificate relayerCrossChainCertificate, + RelayerCredentialSubject relayerCredentialSubject, + String sigAlgo, + List endpoints, + List domains + ) { this.nodeId = nodeId; - this.nodePublicKey = nodePublicKey; - this.endpoints = ListUtil.toList(StrUtil.split(endpointsStr, "^")); - this.domains = ListUtil.toList(StrUtil.split(domainsStr, "^")); + this.relayerCrossChainCertificate = relayerCrossChainCertificate; + this.relayerCredentialSubject = relayerCredentialSubject; + this.sigAlgo = sigAlgo; + this.endpoints = endpoints; + this.domains = domains; } /** * RelayerNodeInfo编码值,用于交换信息时私钥签名 */ public byte[] getEncode() { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream stream = new DataOutputStream(byteArrayOutputStream); + encodeWithPropertiesExcluded(stream); + return byteArrayOutputStream.toByteArray(); + } + public byte[] encodeWithProperties() { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DataOutputStream stream = new DataOutputStream(byteArrayOutputStream); + encodeWithPropertiesExcluded(stream); + + try { + stream.writeUTF(this.properties.encodeToJson()); + stream.writeUTF(this.relayerBlockchainContent.encodeToJson()); + } catch (Exception e) { + throw new RuntimeException("failed to encode with properties: ", e); + } + + return byteArrayOutputStream.toByteArray(); + } + private void encodeWithPropertiesExcluded(DataOutputStream stream) { try { stream.writeUTF(nodeId); - stream.writeUTF(nodePublicKey); + stream.writeUTF( + Base64.encode(relayerCrossChainCertificate.encode()) + ); stream.writeInt(endpoints.size()); for (String endpoint : endpoints) { @@ -212,104 +299,6 @@ public byte[] getEncode() { } catch (IOException e) { throw new RuntimeException(e); } - - return byteArrayOutputStream.toByteArray(); - } - - public static RelayerNodeInfo decode(byte[] encode) { - - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(encode); - DataInputStream stream = new DataInputStream(byteArrayInputStream); - - try { - RelayerNodeInfo - info = new RelayerNodeInfo(); - info.setNodeId(stream.readUTF()); - info.setNodePublicKey(stream.readUTF()); - - int endpointSize = stream.readInt(); - - while (endpointSize > 0) { - info.addEndpoint(stream.readUTF()); - endpointSize--; - } - - int domainSize = stream.readInt(); - - while (domainSize > 0) { - info.addDomain(stream.readUTF()); - domainSize--; - } - - return info; - - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * 验签 - * - * @return - */ - public boolean verify() { - - try { - KeyFactory factory = KeyFactory.getInstance("RSA"); - - PublicKey publicKey = factory.generatePublic( - new X509EncodedKeySpec(Base64.getDecoder().decode(nodePublicKey))); - - Signature verifier = Signature.getInstance("SHA256WithRSA"); - verifier.initVerify(publicKey); - verifier.update(getEncode()); - - return verifier.verify(signature); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * 签名 - * - * @param key - */ - public void sign(PrivateKey key) { - - try { - Signature signer = Signature.getInstance("SHA256WithRSA"); - signer.initSign(key); - signer.update(getEncode()); - - signature = signer.sign(); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * 签名 - * - * @param key - */ - public void sign(String key) { - - try { - - KeyFactory factory = KeyFactory.getInstance("RSA"); - - PrivateKey privateKey = factory.generatePrivate( - new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key))); - - sign(privateKey); - - } catch (Exception e) { - throw new RuntimeException(e); - } } public void unmarshalProperties(String properties) { @@ -324,34 +313,14 @@ public void addProperty(String key, String value) { this.properties.getProperties().put(key, value); } - public Map getBlockchainInfoMap() { - if (ObjectUtil.isNull(blockchainInfoMap)) { - blockchainInfoMap = properties.getRelayerBlockchainInfoMap(); - } - return blockchainInfoMap; - } - - public void setRelayerNodeInfos(Map map) { - blockchainInfoMap = map; - dumpBlockchainInfos(); - } - - public void addBlockchainInfo(String domain, RelayerBlockchainInfo info) { - if (null == blockchainInfoMap) { - getBlockchainInfoMap(); - } - blockchainInfoMap.put(domain, info); - } - - public void dumpBlockchainInfos() { - properties.setRelayerBlockchainInfoMap(blockchainInfoMap); - } - public void addEndpoint(String endpoint) { this.endpoints.add(endpoint); } - public void addDomain(String domain) { + public void addDomainIfNotExist(String domain) { + if (this.domains.contains(domain)) { + return; + } this.domains.add(domain); } @@ -362,4 +331,11 @@ public void addDomains(List domains) { public Long getLastTimeHandshake() { return properties.getLastHandshakeTime(); } + + public PublicKey getPublicKey() { + return ObjectIdentityUtil.getPublicKeyFromSubject( + relayerCredentialSubject.getApplicant(), + relayerCredentialSubject.getSubjectInfo() + ); + } } \ No newline at end of file diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgCommitResult.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgCommitResult.java index 9c88968..524f276 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgCommitResult.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgCommitResult.java @@ -19,10 +19,12 @@ import cn.hutool.core.util.StrUtil; import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter +@NoArgsConstructor public class SDPMsgCommitResult { public final static int TX_MSG_TRUNCATED_LEN = 160; diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgWrapper.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgWrapper.java index 87b8186..4660672 100644 --- a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgWrapper.java +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/model/SDPMsgWrapper.java @@ -16,6 +16,7 @@ package com.alipay.antchain.bridge.relayer.commons.model; +import cn.hutool.core.util.StrUtil; import com.alipay.antchain.bridge.commons.core.sdp.AbstractSDPMessage; import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; import lombok.AllArgsConstructor; @@ -29,7 +30,11 @@ @NoArgsConstructor public class SDPMsgWrapper { - private long id; + public final static int UNORDERED_SDP_MSG_SEQ = -1; + + public final static String UNORDERED_SDP_MSG_SESSION = "UNORDERED"; + + private Long id; private AuthMsgWrapper authMsgWrapper; @@ -61,7 +66,7 @@ public SDPMsgWrapper( AbstractSDPMessage sdpMessage ) { this( - 0, + null, authMsgWrapper, receiverBlockchainProduct, receiverBlockchainId, @@ -113,4 +118,27 @@ public String getMsgReceiver() { public int getMsgSequence() { return this.sdpMessage.getSequence(); } + + public boolean isBlockchainSelfCall() { + return StrUtil.equals(getSenderBlockchainDomain(), getReceiverBlockchainDomain()) + && StrUtil.equalsIgnoreCase(getMsgSender(), getMsgReceiver()); + } + + /** + * getSessionKey returns session key e.g: "domainA.idA:domainB.idB" + */ + public String getSessionKey() { + String key = String.format( + "%s.%s:%s.%s", + getSenderBlockchainDomain(), + getMsgSender(), + getReceiverBlockchainDomain(), + getMsgReceiver() + ); + if (UNORDERED_SDP_MSG_SEQ == getMsgSequence()) { + // 将无序消息单拎出来,完全异步发送 + return String.format("%s-%s", UNORDERED_SDP_MSG_SESSION, key); + } + return key; + } } diff --git a/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/utils/HeteroBBCContextDeserializer.java b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/utils/HeteroBBCContextDeserializer.java new file mode 100644 index 0000000..6f6cfb3 --- /dev/null +++ b/r-commons/src/main/java/com/alipay/antchain/bridge/relayer/commons/utils/HeteroBBCContextDeserializer.java @@ -0,0 +1,44 @@ +/* + * 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.relayer.commons.utils; + +import java.lang.reflect.Type; + +import com.alibaba.fastjson.parser.DefaultJSONParser; +import com.alibaba.fastjson.parser.JSONToken; +import com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer; +import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext; +import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext; + +public class HeteroBBCContextDeserializer extends JavaObjectDeserializer { + @Override + public T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { + if (parser.getLexer().token() == JSONToken.LITERAL_STRING) { + type = String.class; + String contextJson = super.deserialze(parser, type, fieldName); + AbstractBBCContext bbcContext = new DefaultBBCContext(); + bbcContext.decodeFromBytes(contextJson.getBytes()); + return (T) bbcContext; + } + return super.deserialze(parser, type, fieldName); + } + + @Override + public int getFastMatchToken() { + return 0; + } +} diff --git a/r-core/.gitignore b/r-core/.gitignore index 5ff6309..4a1bb73 100644 --- a/r-core/.gitignore +++ b/r-core/.gitignore @@ -35,4 +35,6 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +**/*.xjb \ No newline at end of file diff --git a/r-core/pom.xml b/r-core/pom.xml index 385b7c2..ac3fa55 100644 --- a/r-core/pom.xml +++ b/r-core/pom.xml @@ -22,6 +22,106 @@ com.alipay.antchain.bridge r-dal + + com.alipay.antchain.bridge + antchain-bridge-spi + + + io.grpc + grpc-netty-shaded + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + org.springframework.boot + spring-boot-starter-web-services + + + wsdl4j + wsdl4j + 1.6.3 + + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + + + + + 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} + + + + + compile + compile-custom + + + + + + + + + + + + + + + + + + + + + + + + + + org.codehaus.mojo + jaxws-maven-plugin + 2.6 + + + + wsimport + + + + + + ${project.basedir}/src/main/resources/relayer_node_peer.wsdl + + com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated + + ${project.basedir}/src/main/java + + + http://127.0.0.1:8082/WSEndpointServer?wsdl + + + + + + \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/AMClientContractHeteroBlockchainImpl.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/AMClientContractHeteroBlockchainImpl.java new file mode 100644 index 0000000..7975ea8 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/AMClientContractHeteroBlockchainImpl.java @@ -0,0 +1,98 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +import java.util.List; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.HeterogeneousBlock; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AMClientContractHeteroBlockchainImpl implements IAMClientContract { + + public static int RECEIVER_IDENTITY_BYTEARRAY_LEN = 32; + + public static int SENDER_DOMAIN_LEN = 128 + 1; // 1 byte for len ; 128 bytes for domain + + public static int FLAGS_LEN = 4; + + public static byte[] extractProofs(byte[] encodedAmMsgPkg) { + int offset = RECEIVER_IDENTITY_BYTEARRAY_LEN + SENDER_DOMAIN_LEN + FLAGS_LEN; + int proofs_len = encodedAmMsgPkg.length - offset; + byte[] proofs = new byte[proofs_len]; + System.arraycopy(encodedAmMsgPkg, offset, proofs, 0, proofs_len); + return proofs; + } + + private IBBCServiceClient bbcServiceClient; + + public AMClientContractHeteroBlockchainImpl(IBBCServiceClient bbcServiceClient) { + this.bbcServiceClient = bbcServiceClient; + } + + @Override + public AbstractBlockchainClient.SendResponseResult recvPkgFromRelayer(byte[] pkg, String serviceId) { + try { + CrossChainMessageReceipt receipt = bbcServiceClient.relayAuthMessage(extractProofs(pkg)); + if (ObjectUtil.isNull(receipt)) { + return new AbstractBlockchainClient.SendResponseResult( + "", + false, + false, + "EMPTY RESP", + "EMPTY RESP" + ); + } + if (!receipt.isSuccessful()) { + return new AbstractBlockchainClient.SendResponseResult( + receipt.getTxhash(), + false, + false, + "HETERO COMMIT FAILED", + receipt.getErrorMsg() + ); + } + + log.info("successful to relay message to domain {} with tx {}", this.bbcServiceClient.getDomain(), receipt.getTxhash()); + return new AbstractBlockchainClient.SendResponseResult( + receipt.getTxhash(), + receipt.isConfirmed(), + receipt.isSuccessful(), + "SUCCESS", + receipt.getErrorMsg() + ); + } catch (Exception e) { + log.error("failed to relay message to {}", this.bbcServiceClient.getDomain(), e); + return new AbstractBlockchainClient.SendResponseResult( + "", + false, + false, + "UNKNOWN INTERNAL ERROR", + "UNKNOWN INTERNAL ERROR" + ); + } + } + + @Override + public void setProtocol(String protocolContract, String protocolType) { + this.bbcServiceClient.setProtocol(protocolContract, protocolType); + } + + @Override + public List parseAMRequest(AbstractBlock abstractBlock) { + if (!(abstractBlock instanceof HeterogeneousBlock)) { + throw new RuntimeException("Invalid abstract block type"); + } + return ((HeterogeneousBlock) abstractBlock).toAuthMsgWrappers(); + } + + @Override + public void deployContract() { + this.bbcServiceClient.setupAuthMessageContract(); + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/GRpcBBCPluginManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/GRpcBBCPluginManager.java new file mode 100644 index 0000000..229b837 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/GRpcBBCPluginManager.java @@ -0,0 +1,570 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +import java.io.ByteArrayInputStream; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.pluginserver.service.CrossChainServiceGrpc; +import com.alipay.antchain.bridge.pluginserver.service.Empty; +import com.alipay.antchain.bridge.pluginserver.service.Response; +import com.alipay.antchain.bridge.relayer.commons.constant.PluginServerStateEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.PluginServerDO; +import com.alipay.antchain.bridge.relayer.commons.model.PluginServerInfo; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.GRpcBBCServiceClient; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.GRpcPluginServerClient; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IPluginServerClient; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.exception.PluginServerConnectionFailException; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.exception.PluginServerRegistrationFailException; +import com.alipay.antchain.bridge.relayer.core.utils.PluginServerUtils; +import com.alipay.antchain.bridge.relayer.dal.repository.IPluginServerRepository; +import com.google.common.collect.Sets; +import io.grpc.ManagedChannel; +import io.grpc.TlsChannelCredentials; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.OpenSsl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Slf4j +public class GRpcBBCPluginManager implements IBBCPluginManager { + + private final ExecutorService clientExecutorService; + + private final ScheduledExecutorService heartbeatExecutorService; + + private final Resource tlsClientKeyFile; + + private final Resource tlsClientCaFile; + + private IPluginServerRepository pluginServerRepository; + + private TransactionTemplate transactionTemplate; + + private final Map pluginServerClientMap = new ConcurrentHashMap<>(); + + private final Map heartbeatFutureMap = new HashMap<>(); + + private final Map blockingStubMap = new HashMap<>(); + + private final long heartbeatDelayedTime; + + private final int errorLimitForHeartbeat; + + public GRpcBBCPluginManager( + String clientKeyPath, + String clientCaPath, + IPluginServerRepository pluginServerRepository, + TransactionTemplate transactionTemplate, + ExecutorService clientExecutorService, + ScheduledExecutorService heartbeatExecutorService, + long heartbeatDelayedTime, + int errorLimitForHeartbeat + ) { + this.tlsClientKeyFile = ResourceUtil.getResourceObj(clientKeyPath); + this.tlsClientCaFile = ResourceUtil.getResourceObj(clientCaPath); + this.pluginServerRepository = pluginServerRepository; + this.transactionTemplate = transactionTemplate; + this.clientExecutorService = clientExecutorService; + this.heartbeatExecutorService = heartbeatExecutorService; + this.heartbeatDelayedTime = heartbeatDelayedTime; + this.errorLimitForHeartbeat = errorLimitForHeartbeat; + } + + /** + * 创建插件服务的client + *
+     *     处理逻辑:
+     *      1. 获取指定 id 的插件服务 pluginServerDO (应当已存在且为ready状态)
+     *      2. 获取插件服务 pluginServerDO 的 client
+     *      3. 如果 client 不存在则执行 startPluginServerClient 创建并连接插件(同时添加心跳、更新数据库)
+     *     使用场景:
+     *      区块链获取插件时需要先判断插件 client 是否存在,不存在时调用当前方法创建 client
+     * 
+ * + * @param psId + * @param product + * @param domain + * @return + */ + @Override + public IBBCServiceClient createBBCClient(String psId, String product, String domain) { + PluginServerDO pluginServerDO = this.pluginServerRepository.getPluginServer(psId); + if (ObjectUtil.isEmpty(pluginServerDO)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("data of plugin server %s not found", psId) + ); + } + if (PluginServerStateEnum.READY != pluginServerDO.getState()) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("Plugin server %s not started", psId) + ); + } + + IPluginServerClient psClient = this.getPluginServerClient(psId); + if (ObjectUtil.isNull(psClient)) { + log.info("client of plugin server {} not started and start it now", psId); + this.startPluginServerClient(pluginServerDO); + } + if (!this.checkIfProductSupport(psId, product)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("Plugin server %s supports no product %s", psId, product) + ); + } + + return new GRpcBBCServiceClient(psId, product, domain, this.blockingStubMap.get(psId)); + } + + private boolean checkIfProductSupport(String psId, String product) { + IPluginServerClient psClient = this.getPluginServerClient(psId); + if (ObjectUtil.isNull(psClient)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("client of plugin server %s not started and start it now", psId) + ); + } + + Map res = psClient.ifProductSupport(ListUtil.toList(product)); + if (ObjectUtil.isEmpty(res)) { + return false; + } + return res.getOrDefault(product, false); + } + + /** + * 注册指定id的插件服务 + *
+     *     处理逻辑:
+     *      1.获取指定 id 的插件服务 pluginServerDO (应当不存在)
+     *      2.创建 pluginServerDO 并设置为 init 状态
+     *      3.创建 pluginServerDO 的 client (调用 startPluginServerClient 创建并连接)
+     *     使用场景:
+     *      首次注册插件服务,注册后不需要 startPluginServer
+     * 
+ * + * @param psId + * @param address + * @param properties + */ + @Override + @Transactional + public void registerPluginServer(String psId, String address, String properties) { + PluginServerDO pluginServerDO = pluginServerRepository.getPluginServer(psId); + if (ObjectUtil.isNotEmpty(pluginServerDO)) { + throw new PluginServerRegistrationFailException(String.format("plugin server %s already registered", psId)); + } + + pluginServerDO = new PluginServerDO(); + pluginServerDO.setPsId(psId); + pluginServerDO.setAddress(address); + pluginServerDO.setState(PluginServerStateEnum.INIT); + pluginServerDO.setProperties(PluginServerDO.PluginServerProperties.decode(properties.getBytes())); + + pluginServerRepository.insertNewPluginServer(pluginServerDO); + log.info("Plugin server {} has been registered as INIT", psId); + + startPluginServerClient(pluginServerDO); + log.info("Plugin server {} works READY as required", psId); + } + + private CrossChainServiceGrpc.CrossChainServiceBlockingStub tryConnectionWithPluginServer(PluginServerDO pluginServerDO) { + Response response; + CrossChainServiceGrpc.CrossChainServiceBlockingStub stub; + try { + stub = getPluginServerGRpcStub(pluginServerDO); + response = stub.heartbeat(Empty.getDefaultInstance()); + } catch (Exception e) { + throw new PluginServerConnectionFailException( + String.format("test connection with plugin server %s failed, %b, %s", + pluginServerDO.getPsId(), + OpenSsl.isAlpnSupported(), + System.getProperty("java.version")), + e + ); + } + if (!ObjectUtil.isNotNull(response) || response.getCode() != 0) { + throw new PluginServerConnectionFailException( + String.format("test connection with plugin server %s end with empty response or error code shows something wrong", + pluginServerDO.getPsId()) + ); + } + return stub; + } + + private CrossChainServiceGrpc.CrossChainServiceBlockingStub createPluginServerGRpcStub(PluginServerDO pluginServerDO) { + String commonName = PluginServerUtils.getPluginServerCertX509CommonName(pluginServerDO.getProperties().getPluginServerCert()); + if (StrUtil.isEmpty(commonName)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("failed to get common name from x509 subject for plugin server %s", pluginServerDO.getPsId()) + ); + } + + Pair addresPair = this.decodeAddress(pluginServerDO.getAddress()); + + ManagedChannel channel; + try { + TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder(); + tlsBuilder.keyManager(this.tlsClientCaFile.getStream(), this.tlsClientKeyFile.getStream()); + tlsBuilder.trustManager( + new ByteArrayInputStream(pluginServerDO.getProperties().getPluginServerCert().getBytes()) + ); + channel = NettyChannelBuilder.forAddress(addresPair.getKey(), addresPair.getValue(), tlsBuilder.build()) + .executor(this.clientExecutorService) + .overrideAuthority(commonName) + .build(); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("failed to create client for psId %s with %s", pluginServerDO.getPsId(), pluginServerDO.getAddress()), + e + ); + } + + return CrossChainServiceGrpc.newBlockingStub(channel); + } + + private CrossChainServiceGrpc.CrossChainServiceBlockingStub getPluginServerGRpcStub(PluginServerDO pluginServerDO) { + if (this.blockingStubMap.containsKey(pluginServerDO.getPsId())) { + return this.blockingStubMap.get(pluginServerDO.getPsId()); + } + this.blockingStubMap.put(pluginServerDO.getPsId(), this.createPluginServerGRpcStub(pluginServerDO)); + return this.blockingStubMap.get(pluginServerDO.getPsId()); + } + + /** + * 删除指定id的插件服务 + *
+     *     调用前提:
+     *      检查插件服务是否存在已绑定的区块链,当不存在绑定区块链时才可以调用
+     *     处理逻辑:
+     *      1.如果插件服务不存在,直接返回
+     *      2.如果插件服务存在,先停止心跳(如果有)并移除client,然后从数据库删除条目
+     * 
+ * + * @param psId + */ + @Override + public void deletePluginServer(String psId) { + PluginServerDO pluginServerDO = this.pluginServerRepository.getPluginServer(psId); + if (ObjectUtil.isEmpty(pluginServerDO)) { + return; + } + + if (this.heartbeatFutureMap.containsKey(psId)) { + if (!this.heartbeatFutureMap.get(psId).isDone() + && !this.heartbeatFutureMap.get(psId).cancel(false)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("Plugin server %s heartbeat task stop failed", psId) + ); + } + this.heartbeatFutureMap.remove(psId); + } + + this.pluginServerClientMap.remove(psId); + this.blockingStubMap.remove(psId); + + this.pluginServerRepository.deletePluginServer(pluginServerDO); + } + + /** + * 启动指定id的插件服务 + *
+     *     处理逻辑:
+     *      1.获取指定id的插件服务 pluginServerDO (应当已存在且状态不为init或ready)
+     *      2.创建 pluginServerDO 的 client (调用 startPluginServerClient 创建并连接)
+     *     使用场景:
+     *      启动被主动停止(手动调用了 stopPluginServer )或被动异常(心跳丢失)的插件服务
+     * 
+ * + * @param psId + */ + @Override + public void startPluginServer(String psId) { + PluginServerDO pluginServerDO = this.pluginServerRepository.getPluginServer(psId); + if (ObjectUtil.isEmpty(pluginServerDO)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("data of plugin server %s not found", psId) + ); + } + + if (PluginServerStateEnum.INIT == pluginServerDO.getState()) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("plugin server %s is initiating", psId) + ); + } + if (PluginServerStateEnum.READY == pluginServerDO.getState()) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("plugin server %s already started", psId) + ); + } + + this.startPluginServerClient(pluginServerDO); + + log.info("client of plugin server {} started", psId); + } + + @Override + public void forceStartPluginServer(String psId) { + PluginServerDO pluginServerDO = this.pluginServerRepository.getPluginServer(psId); + if (ObjectUtil.isEmpty(pluginServerDO)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("data of plugin server %s not found", psId) + ); + } + + this.startPluginServerClient(pluginServerDO); + + log.info("force to start client of plugin server {}", psId); + } + + private void startPluginServerClient(PluginServerDO pluginServerDO) { + this.pluginServerClientMap.put( + pluginServerDO.getPsId(), + new GRpcPluginServerClient( + pluginServerDO.getPsId(), + tryConnectionWithPluginServer(pluginServerDO), + errorLimitForHeartbeat + ) + ); + this.addHeartbeatTask(pluginServerDO.getPsId()); + if (pluginServerDO.getState() != PluginServerStateEnum.READY) { + this.pluginServerRepository.updatePluginServerState(pluginServerDO.getPsId(), PluginServerStateEnum.READY); + } + } + + private void addHeartbeatTask(String psId) { + if (this.heartbeatFutureMap.containsKey(psId)) { + log.info("heartbeat task already exists for plugin server {}", psId); + return; + } + + IPluginServerClient pluginServerClient = this.pluginServerClientMap.get(psId); + ScheduledFuture future = this.heartbeatExecutorService.scheduleWithFixedDelay( + () -> { + Lock lock; + try { + lock = pluginServerRepository.getHeartbeatLock(psId); + } catch (Exception e) { + log.error("Failed to get heartbeat lock for plugin server {}: ", psId, e); + return; + } + + transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + PluginServerStateEnum stateEnum = null; + try { + if (lock.tryLock()) { + stateEnum = pluginServerRepository.getPluginServerStateEnum(psId); + if (ObjectUtil.isNull(stateEnum) || stateEnum == PluginServerStateEnum.NOT_FOUND) { + log.info("Plugin server {} is not found, so stop the heartbeat", psId); + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("Plugin server %s state not found", psId) + ); + } + if (stateEnum != PluginServerStateEnum.READY + && stateEnum != PluginServerStateEnum.HEARTBEAT_LOST) { + log.error("Plugin server {} is not READY or HEARTBEAT_LOST but has a heartbeat task running", psId); + return; + } + + PluginServerInfo info = pluginServerClient.heartbeat(); + updatePluginServer(psId, info); + if (PluginServerStateEnum.HEARTBEAT_LOST == stateEnum) { + pluginServerRepository.updatePluginServerState(psId, PluginServerStateEnum.READY); + } + + log.info( + "Heartbeat task success for plugin server {} : ( products: {} , domains: {} )", + psId, + String.join(",", ObjectUtil.defaultIfNull(info.getProducts(), ListUtil.of())), + String.join(",", ObjectUtil.defaultIfNull(info.getDomains(), ListUtil.of())) + ); + } + } catch (Exception e) { + if (PluginServerStateEnum.HEARTBEAT_LOST != stateEnum && PluginServerStateEnum.STOP != stateEnum) { + log.error("heartbeat failed for plugin server {} and freeze this plugin server", psId, e); + freezePluginServer(psId); + } else if (PluginServerStateEnum.STOP == stateEnum) { + log.warn("heartbeat failed for plugin server {} which is STOP", psId); + // throw exception to stop the scheduled task + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("heartbeat is supposed to stop cause that plugin server %s's state is %s", psId, stateEnum), + e + ); + } else { + log.warn("heartbeat failed for plugin server {} and keeping HEARTBEAT_LOST", psId, e); + } + } finally { + lock.unlock(); + } + } + } + ); + }, + 1000, + heartbeatDelayedTime, + TimeUnit.MILLISECONDS + ); + this.heartbeatFutureMap.put(psId, future); + } + + private void freezePluginServer(String psId) { + try { + this.pluginServerRepository.updatePluginServerState(psId, PluginServerStateEnum.HEARTBEAT_LOST); + } catch (Exception e) { + log.error("failed to freeze plugin server {}", psId, e); + } + } + + private boolean ifNeedUpdatePluginServer(PluginServerDO pluginServerDO, PluginServerInfo pluginServerInfo) { + Set products = Sets.newHashSet( + ObjectUtil.defaultIfNull(pluginServerDO.getProductsSupported(), new HashSet<>()) + ); + Set domains = Sets.newHashSet( + ObjectUtil.defaultIfNull(pluginServerDO.getDomainsServing(), new HashSet<>()) + ); + Set productsNew = Sets.newHashSet( + ObjectUtil.defaultIfNull(pluginServerInfo.getProducts(), new HashSet<>()) + ); + Set domainsNew = Sets.newHashSet( + ObjectUtil.defaultIfNull(pluginServerInfo.getDomains(), new HashSet<>()) + ); + + if (products.size() != productsNew.size() || domains.size() != domainsNew.size()) { + return true; + } + + int productsSize = products.size(); + products.addAll(productsNew); + if (products.size() > productsSize) { + return true; + } + + int domainsSize = domains.size(); + domains.addAll(domainsNew); + return domains.size() > domainsSize; + } + + private Pair decodeAddress(String address) { + String[] arr = address.split(":"); + if (arr.length != 2) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + "Invalid address: " + address + ); + } + return Pair.of(arr[0], Integer.valueOf(arr[1])); + } + + /** + * 停止指定id的插件服务 + *
+     *     处理逻辑:
+     *      1.判断该插件服务是否在心跳任务集合中(应当在),如果不在说明该服务已经停止,会抛出异常
+     *      2.如果心跳任务还在执行则停止心跳任务
+     *      3.移除插件服务的 client
+     *      4.移除链的 stub
+     *      5.移除心跳任务
+     *      6.更新数据库状态为`stop`,schedule 的 Cleaner 会根据`stop`状态清除链的 client
+     *     使用场景:
+     *      cli中可以手动停止插件服务
+     * 
+ * + * @param psId + */ + @Override + public void stopPluginServer(String psId) { + if ( + this.heartbeatFutureMap.containsKey(psId) + && !this.heartbeatFutureMap.get(psId).isDone() + && !this.heartbeatFutureMap.get(psId).cancel(false) + ) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + String.format("Plugin server %s heartbeat task stop failed", psId) + ); + } + + this.pluginServerClientMap.remove(psId); + this.blockingStubMap.remove(psId); + this.heartbeatFutureMap.remove(psId); + + this.pluginServerRepository.updatePluginServerState(psId, PluginServerStateEnum.STOP); + } + + @Override + public IPluginServerClient getPluginServerClient(String psId) { + return this.pluginServerClientMap.get(psId); + } + + @Override + public List getAllPluginServerId() { + return ListUtil.toList(this.pluginServerClientMap.keySet()); + } + + @Override + public PluginServerStateEnum getPluginServerState(String psId) { + return this.pluginServerRepository.getPluginServerStateEnum(psId); + } + + @Override + public List getProductsSupportedByPsId(String psId) { + return this.pluginServerRepository.getProductsSupportedOfPluginServer(psId); + } + + @Override + public List getDomainsSupportedByPsId(String psId) { + return this.pluginServerRepository.getDomainsServingOfPluginServer(psId); + } + + @Override + public PluginServerInfo getPluginServerInfo(String psId) { + return this.pluginServerRepository.getPluginServerInfo(psId); + } + + @Override + public void updatePluginServerInfo(String psId) { + if (!this.pluginServerClientMap.containsKey(psId)) { + forceStartPluginServer(psId); + } + updatePluginServer(psId, this.pluginServerClientMap.get(psId).heartbeat()); + } + + private void updatePluginServer(String psId, PluginServerInfo info) { + if (ObjectUtil.isEmpty(info)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, + "null response from heartbeat for plugin server " + psId + ); + } + + if (ifNeedUpdatePluginServer(pluginServerRepository.getPluginServer(psId), info)) { + pluginServerRepository.updatePluginServerInfo(psId, info); + } + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IAMClientContract.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IAMClientContract.java new file mode 100644 index 0000000..0bd50a4 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IAMClientContract.java @@ -0,0 +1,46 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +import java.util.List; + +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; + +/** + * 抽象的AMClient服务合约 + *

+ * 不同的链有不同的实现。 + */ +public interface IAMClientContract { + + AbstractBlockchainClient.SendResponseResult recvPkgFromRelayer(byte[] pkg, String serviceId); + + /** + * 添加AM上层协议 + * + * @param protocolContract 协议合约地址 + * @param protocolType 协议类型 + * @return + */ + void setProtocol(String protocolContract, String protocolType); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // 非合约接口,合约的数据解析接口 + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * 解析区块里的合约发出的AM消息 + * + * @param abstractBlock + * @return + */ + List parseAMRequest(AbstractBlock abstractBlock); + + /** + * 部署合约 + * + * @param contractId + * @return + */ + void deployContract(); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IBBCPluginManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IBBCPluginManager.java new file mode 100644 index 0000000..accfc66 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/IBBCPluginManager.java @@ -0,0 +1,37 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +import java.util.List; + +import com.alipay.antchain.bridge.relayer.commons.constant.PluginServerStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.PluginServerInfo; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IPluginServerClient; + +public interface IBBCPluginManager { + + IBBCServiceClient createBBCClient(String psId, String product, String domain); + + void registerPluginServer(String psId, String address, String properties); + + void deletePluginServer(String psId); + + void startPluginServer(String psId); + + void forceStartPluginServer(String psId); + + void stopPluginServer(String psId); + + IPluginServerClient getPluginServerClient(String psId); + + List getAllPluginServerId(); + + PluginServerStateEnum getPluginServerState(String psId); + + List getProductsSupportedByPsId(String psId); + + List getDomainsSupportedByPsId(String psId); + + PluginServerInfo getPluginServerInfo(String psId); + + void updatePluginServerInfo(String psId); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java new file mode 100644 index 0000000..974467d --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/ISDPMsgClientContract.java @@ -0,0 +1,36 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +/** + * 抽象的SDPMsgClient服务合约。 + *

+ * 不同的链有不同的实现。 + */ +public interface ISDPMsgClientContract { + + /** + * 设置AM协议合约 + * + * @param amContract am协议合约地址 + * @return + */ + void setAmContract(String amContract); + + /** + * Query the sequence number of the cross-chain direction + * + * @param senderDomain + * @param from + * @param receiverDomain + * @param to + * @return long + */ + long querySDPMsgSeqOnChain(String senderDomain, String from, String receiverDomain, String to); + + /** + * 部署合约 + * + * @param contractId + * @return + */ + void deployContract(); +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java new file mode 100644 index 0000000..d3d99b1 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bbc/SDPMsgClientHeteroBlockchainImpl.java @@ -0,0 +1,43 @@ +package com.alipay.antchain.bridge.relayer.core.manager.bbc; + +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SDPMsgClientHeteroBlockchainImpl implements ISDPMsgClientContract { + + private IBBCServiceClient bbcServiceClient; + + public SDPMsgClientHeteroBlockchainImpl(IBBCServiceClient bbcServiceClient) { + this.bbcServiceClient = bbcServiceClient; + } + + @Override + public void setAmContract(String amContract) { + this.bbcServiceClient.setAmContract(amContract); + this.bbcServiceClient.setLocalDomain(bbcServiceClient.getDomain()); + } + + @Override + public long querySDPMsgSeqOnChain(String senderDomain, String from, String receiverDomain, String to) { + try { + return this.bbcServiceClient.querySDPMessageSeq(senderDomain, from, receiverDomain, to); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BBC_CALL_ERROR, + String.format( + "failed to queryP2PMsgSeqOnChain for channel ( senderDomain: %s, from: %s, receiverDomain: %s, to: %s )", + senderDomain, from, receiverDomain, to + ), + e + ); + } + } + + @Override + public void deployContract() { + this.bbcServiceClient.setupSDPMessageContract(); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/BCDNSManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/BCDNSManager.java new file mode 100644 index 0000000..c70f392 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/BCDNSManager.java @@ -0,0 +1,97 @@ +/* + * 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.relayer.core.manager.bcdns; + +import java.util.List; +import java.util.Map; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.BCDNSTrustRootCredentialSubject; +import com.alipay.antchain.bridge.relayer.commons.model.DomainSpaceCertWrapper; +import com.alipay.antchain.bridge.relayer.dal.repository.IBCDNSRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class BCDNSManager implements IBCDNSManager { + + @Resource + private IBCDNSRepository bcdnsRepository; + + @Override + public Map getAllTrustRootCerts() { + return null; + } + + @Override + public AbstractCrossChainCertificate getTrustRootCert(String domainSpace) { + return null; + } + + @Override + public Map getTrustRootCertChain(String domainSpace) { + return null; + } + + @Override + public List getDomainSpaceChain(String domainSpace) { + return null; + } + + @Override + public BCDNSTrustRootCredentialSubject getTrustRootCredentialSubject(String domainSpace) { + return null; + } + + @Override + public AbstractCrossChainCertificate getTrustRootCertForRootDomain() { + return null; + } + + @Override + public BCDNSTrustRootCredentialSubject getTrustRootCredentialSubjectForRootDomain() { + return null; + } + + @Override + public boolean validateCrossChainCertificate(AbstractCrossChainCertificate certificate) { + return false; + } + + @Override + public boolean validateDomainCertificate(AbstractCrossChainCertificate certificate, List domainSpaceChain) { + return false; + } + + @Override + public void saveDomainSpaceCerts(Map domainSpaceCerts) { + for (Map.Entry entry : domainSpaceCerts.entrySet()) { + try { + if (bcdnsRepository.hasDomainSpaceCert(entry.getKey())) { + log.warn("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); + } + } + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/IBCDNSManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/IBCDNSManager.java new file mode 100644 index 0000000..bf51a95 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/bcdns/IBCDNSManager.java @@ -0,0 +1,46 @@ +/* + * 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.relayer.core.manager.bcdns; + +import java.util.List; +import java.util.Map; + +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.BCDNSTrustRootCredentialSubject; + +public interface IBCDNSManager { + + Map getAllTrustRootCerts(); + + AbstractCrossChainCertificate getTrustRootCert(String domainSpace); + + Map getTrustRootCertChain(String domainSpace); + + List getDomainSpaceChain(String domainSpace); + + AbstractCrossChainCertificate getTrustRootCertForRootDomain(); + + BCDNSTrustRootCredentialSubject getTrustRootCredentialSubject(String domainSpace); + + BCDNSTrustRootCredentialSubject getTrustRootCredentialSubjectForRootDomain(); + + boolean validateCrossChainCertificate(AbstractCrossChainCertificate certificate); + + boolean validateDomainCertificate(AbstractCrossChainCertificate certificate, List domainSpaceChain); + + void saveDomainSpaceCerts(Map domainSpaceCerts); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java new file mode 100644 index 0000000..c321556 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/BlockchainManager.java @@ -0,0 +1,541 @@ +/* + * 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.relayer.core.manager.blockchain; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.util.ObjectUtil; +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.DefaultBBCContext; +import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.OnChainServiceStatusEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.UpperProtocolTypeBeyondAMEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AnchorProcessHeights; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainAnchorProcess; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.HeteroBlockchainClient; +import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class BlockchainManager implements IBlockchainManager { + + @Resource + private IBlockchainRepository blockchainRepository; + + @Resource + private BlockchainClientPool blockchainClientPool; + + @Override + public void addBlockchain( + String product, + String blockchainId, + String pluginServerId, + String alias, + String desc, + Map clientConfig + ) { + try { + BlockchainMeta.BlockchainProperties blockchainProperties = BlockchainMeta.BlockchainProperties.decode( + JSON.toJSONBytes(clientConfig) + ); + if (ObjectUtil.isNotNull(blockchainProperties.getAnchorRuntimeStatus())) { + log.warn( + "add blockChain information (id : {}) contains anchor runtime status : {} and it will be removed", + blockchainId, blockchainProperties.getAnchorRuntimeStatus().getCode() + ); + } + blockchainProperties.setAnchorRuntimeStatus(BlockchainStateEnum.INIT); + blockchainProperties.setPluginServerId(pluginServerId); + + addBlockchain(new BlockchainMeta(product, blockchainId, alias, desc, blockchainProperties)); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to add new blockchain {} - {} with plugin server {}", + product, blockchainId, pluginServerId + ); + } + } + + @Override + public void addBlockchain(BlockchainMeta blockchainMeta) { + log.info( + "add blockchain {} - {} with plugin server {}", + blockchainMeta.getProduct(), blockchainMeta.getBlockchainId(), blockchainMeta.getProperties().getPluginServerId() + ); + + try { + if (isBlockchainExists(blockchainMeta.getProduct(), blockchainMeta.getBlockchainId())) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "blockchain {}-{} already exists", + blockchainMeta.getProduct(), blockchainMeta.getBlockchainId() + ); + } + + // 检查客户端配置是否正确 + AbstractBlockchainClient client = blockchainClientPool.createClient(blockchainMeta); + /* 记录当前区块链高度为初始锚定高度 */ + blockchainMeta.getProperties().setInitBlockHeight( + client.getLastBlockHeight() + ); + + blockchainRepository.saveBlockchainMeta(blockchainMeta); + + log.info("[BlockchainManager] add blockchain {} success", blockchainMeta.getMetaKey()); + + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to add new blockchain {} with plugin server {}", + blockchainMeta.getMetaKey(), blockchainMeta.getProperties().getPluginServerId() + ); + } + } + + @Override + public void updateBlockchain(String product, String blockchainId, String pluginServerId, String alias, String desc, Map clientConfig) { + try { + BlockchainMeta blockchainMeta = getBlockchainMeta(product, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new RuntimeException(StrUtil.format("none blockchain found for {}-{}", product, blockchainId)); + } + + AbstractBlockchainClient client = blockchainClientPool.createClient(blockchainMeta); + if (ObjectUtil.isNull(client)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_CLIENT_INIT_ERROR, + "null blockchain client for {}-{}", product, blockchainId + ); + } + + BlockchainMeta.BlockchainProperties blockchainProperties = BlockchainMeta.BlockchainProperties.decode( + JSON.toJSONBytes(clientConfig) + ); + if (ObjectUtil.isNull(blockchainProperties)) { + throw new RuntimeException(StrUtil.format("none blockchain properties built for {}-{}", product, blockchainId)); + } + + blockchainMeta.setAlias(alias); + blockchainMeta.setDesc(desc); + blockchainMeta.updateProperties(blockchainProperties); + if (updateBlockchainMeta(new BlockchainMeta(product, blockchainId, alias, desc, blockchainProperties))) { + throw new RuntimeException( + StrUtil.format( + "failed to update meta for blockchain {} - {} into DB", + product, blockchainId + ) + ); + } + + + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to add new blockchain {} - {} with plugin server {}", + product, blockchainId + ); + } + } + + @Override + public void updateBlockchainProperty(String product, String blockchainId, String confKey, String confValue) { + log.info("update property (key: {}, val: {}) for blockchain {} - {}", confKey, confValue, product, blockchainId); + + try { + BlockchainMeta blockchainMeta = getBlockchainMeta(product, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "none blockchain found for {}-{}", product, blockchainId + ); + } + + blockchainMeta.updateProperty(confKey, confValue); + if (!updateBlockchainMeta(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "failed to update property (key: {}, val: {}) for blockchain {} - {} into DB", + confKey, confValue, product, blockchainId + ); + } + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, + e, + "failed to update property (key: {}, val: {}) for blockchain {} - {} with unknown exception", + confKey, confValue, product, blockchainId + ); + } + log.info("successful to update property (key: {}, val: {}) for blockchain {} - {}", confKey, confValue, product, blockchainId); + } + + @Override + public boolean hasBlockchain(String domain) { + return blockchainRepository.hasBlockchain(domain); + } + + @Override + public DomainCertWrapper getDomainCert(String domain) { + return blockchainRepository.getDomainCert(domain); + } + + @Override + public void deployAMClientContract(String product, String blockchainId) { + try { + BlockchainMeta blockchainMeta = this.getBlockchainMeta(product, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new RuntimeException(StrUtil.format("none blockchain found for {}-{}", product, blockchainId)); + } + deployHeteroBlockchainAMContract(blockchainMeta); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to deploy am contract for blockchain {} - {}", + product, blockchainId + ); + } + } + + @Override + public void startBlockchainAnchor(String product, String blockchainId) { + log.info("start blockchain anchor {} - {}", product, blockchainId); + + try { + BlockchainMeta blockchainMeta = this.getBlockchainMeta(product, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new RuntimeException(StrUtil.format("none blockchain found for {}-{}", product, blockchainId)); + } + + // 已启动的不重复启动 + if (blockchainMeta.isRunning()) { + return; + } + + blockchainMeta.getProperties().setAnchorRuntimeStatus(BlockchainStateEnum.RUNNING); + if (!updateBlockchainMeta(blockchainMeta)) { + throw new RuntimeException( + StrUtil.format( + "failed to update meta for blockchain {} - {} into DB", + product, blockchainId + ) + ); + } + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to start blockchain {} - {}", + product, blockchainId + ); + } + } + + @Override + public void stopBlockchainAnchor(String product, String blockchainId) { + log.info("stop blockchain anchor {} - {}", product, blockchainId); + + try { + BlockchainMeta blockchainMeta = this.getBlockchainMeta(product, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new RuntimeException(StrUtil.format("none blockchain found for {}-{}", product, blockchainId)); + } + + // 已启动的不重复启动 + if (BlockchainStateEnum.STOPPED == blockchainMeta.getProperties().getAnchorRuntimeStatus()) { + return; + } + + blockchainMeta.getProperties().setAnchorRuntimeStatus(BlockchainStateEnum.STOPPED); + if (!updateBlockchainMeta(blockchainMeta)) { + throw new RuntimeException( + StrUtil.format( + "failed to update meta for blockchain {} - {} into DB", + product, blockchainId + ) + ); + } + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to stop blockchain {} - {}", + product, blockchainId + ); + } + } + + @Override + public BlockchainMeta getBlockchainMeta(String product, String blockchainId) { + return blockchainRepository.getBlockchainMeta(product, blockchainId); + } + + @Override + public BlockchainMeta getBlockchainMetaByDomain(String domain) { + return blockchainRepository.getBlockchainMetaByDomain(domain); + } + + @Override + public String getBlockchainDomain(String product, String blockchainId) { + return blockchainRepository.getBlockchainDomain(product, blockchainId); + } + + @Override + public boolean updateBlockchainMeta(BlockchainMeta blockchainMeta) { + return blockchainRepository.updateBlockchainMeta(blockchainMeta); + } + + @Override + public List getAllBlockchainMeta() { + return blockchainRepository.getAllBlockchainMeta(); + } + + @Override + public BlockchainAnchorProcess getBlockchainAnchorProcess(String product, String blockchainId) { + AnchorProcessHeights heights = blockchainRepository.getAnchorProcessHeights(product, blockchainId); + if (ObjectUtil.isNull(heights)) { + log.error("null heights for blockchain {}-{}", product, blockchainId); + return null; + } + return BlockchainAnchorProcess.convertFrom(heights); + } + + @Override + public List getAllServingBlockchains() { + try { + return blockchainRepository.getBlockchainMetaByState(BlockchainStateEnum.RUNNING); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to query all serving blockchains" + ); + } + } + + @Override + public List getAllStoppedBlockchains() { + try { + return blockchainRepository.getBlockchainMetaByState(BlockchainStateEnum.STOPPED); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to query all stopped blockchains" + ); + } + } + + @Override + public boolean checkIfDomainPrepared(String domain) { + try { + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMetaByDomain(domain); + if (ObjectUtil.isNull(blockchainMeta)) { + return false; + } + } catch (Exception e) { + log.error("failed to query blockchain by domain {}", domain, e); + return false; + } + return true; + } + + @Override + public boolean checkIfDomainRunning(String domain) { + try { + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMetaByDomain(domain); + if (ObjectUtil.isNull(blockchainMeta)) { + return false; + } + return blockchainMeta.isRunning(); + } catch (Exception e) { + log.error("failed to query blockchain by domain {}", domain, e); + return false; + } + } + + @Override + public boolean checkIfDomainAMDeployed(String domain) { + try { + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMetaByDomain(domain); + if (ObjectUtil.isNull(blockchainMeta)) { + return false; + } + return OnChainServiceStatusEnum.DEPLOY_FINISHED == blockchainMeta.getProperties().getAmServiceStatus(); + } catch (Exception e) { + log.error("failed to query blockchain by domain {}", domain, e); + return false; + } + } + + @Override + public List getBlockchainsByPluginServerId(String pluginServerId) { + try { + return blockchainRepository.getBlockchainMetaByPluginServerId(pluginServerId).stream() + .map(BlockchainMeta::getBlockchainId) + .collect(Collectors.toList()); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to query blockchains by plugin server id {}" + ); + } + } + + @Override + public void updateSDPMsgSeq(String receiverProduct, String receiverBlockchainId, String senderDomain, String from, String to, long newSeq) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "update SDP msg seq not supported for now" + ); + } + + @Override + public long querySDPMsgSeq(String receiverProduct, String receiverBlockchainId, String senderDomain, String from, String to) { + try { + AbstractBlockchainClient blockchainClient = blockchainClientPool.getClient(receiverProduct, receiverBlockchainId); + if (ObjectUtil.isNull(blockchainClient)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "failed to get blockchain client for blockchain ( product: {}, bcId: {})", + receiverProduct, receiverBlockchainId + ); + } + + return blockchainClient.getSDPMsgClientContract().querySDPMsgSeqOnChain( + senderDomain, + from, + blockchainClient.getDomain(), + to + ); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + e, + "failed to query blockchains by plugin server id {}" + ); + } + } + + private boolean isBlockchainExists(String product, String blockchainId) { + return blockchainRepository.hasBlockchain(product, blockchainId); + } + + private void deployHeteroBlockchainAMContract(BlockchainMeta blockchainMeta) { + AbstractBlockchainClient blockchainClient = blockchainClientPool.getClient( + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ); + if (ObjectUtil.isNull(blockchainClient)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "failed to get blockchain client for blockchain ( product: {}, bcId: {}, pluginServerId: {} )", + blockchainMeta.getProduct(), blockchainMeta.getBlockchainId(), blockchainMeta.getPluginServerId() + ); + } + + if (!(blockchainClient instanceof HeteroBlockchainClient)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "wrong type of client for blockchain ( product: {}, bcId: {}, pluginServer: {})", + blockchainMeta.getProduct(), blockchainMeta.getBlockchainId(), blockchainMeta.getPluginServerId() + ); + } + + AbstractBBCContext bbcContext = blockchainClient.queryBBCContext(); + + if ( + ObjectUtil.isNull(bbcContext.getAuthMessageContract()) + || ContractStatusEnum.INIT == bbcContext.getAuthMessageContract().getStatus() + ) { + blockchainClient.getAMClientContract().deployContract(); + } + + if ( + ObjectUtil.isNull(bbcContext.getSdpContract()) + || ContractStatusEnum.INIT == bbcContext.getSdpContract().getStatus() + ) { + blockchainClient.getSDPMsgClientContract().deployContract(); + } + + bbcContext = blockchainClient.queryBBCContext(); + blockchainClient.getAMClientContract() + .setProtocol( + bbcContext.getSdpContract().getContractAddress(), + Integer.toString(UpperProtocolTypeBeyondAMEnum.SDP.getCode()) + ); + + blockchainClient.getSDPMsgClientContract() + .setAmContract(bbcContext.getAuthMessageContract().getContractAddress()); + + bbcContext = blockchainClient.queryBBCContext(); + + blockchainMeta.getProperties().setAmClientContractAddress( + bbcContext.getAuthMessageContract().getContractAddress() + ); + blockchainMeta.getProperties().setSdpMsgContractAddress( + bbcContext.getSdpContract().getContractAddress() + ); + blockchainMeta.getProperties().setBbcContext( + (DefaultBBCContext) bbcContext + ); + + blockchainClient.setBlockchainMeta(blockchainMeta); + updateBlockchainMeta(blockchainMeta); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/IBlockchainManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/IBlockchainManager.java new file mode 100644 index 0000000..66dd61a --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/blockchain/IBlockchainManager.java @@ -0,0 +1,237 @@ +/* + * 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.relayer.core.manager.blockchain; + +import java.util.List; +import java.util.Map; + +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainAnchorProcess; + +/** + * 该Manager提供接入区块链的系列生命周期管理接口,通过该接口可以添加区块链、更新查询区块链元信息、管理链上合约等业务操作 + */ +public interface IBlockchainManager { + + /** + * 添加一条需要监听的区块链,该接口要求传入区块链必要的客户端配置,如节点ip、节点连接证书、区块链账号及私钥等。 + *

+ * 该接口仅保存区块链的元信息,具体启动监听的动作需要通过另外一个接口触发。 + * + *

+     *
+     *  该接口会做以下事情
+     *   1. 检查提供的区块链节点是否可达
+     *   2. 检查提供的区块链连接证书是否可用
+     *
+     *  完成以上检查,则保存一条可连接的区块链的信息,否则返回配置信息有误。
+     *
+     * 
+ * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @param pluginServerId 插件服务的ID,空字符串意味着未指定 + * @param alias 区块链别名 + * @param desc 描述 + * @param clientConfig 区块链的客户端配置 + * @return true:配置信息可用 false:配置信息不可用 + */ + void addBlockchain(String product, String blockchainId, String pluginServerId, String alias, String desc, + Map clientConfig); + + void addBlockchain(BlockchainMeta blockchainMeta); + + /** + * 更新区块链信息,该接口要求传入区块链必要的客户端配置,如节点ip、节点连接证书、区块链账号及私钥等。 + *

+ * 该接口仅更新区块链的元信息,具体启动、重启监听等动作需要通过另外一个接口触发。 + * + *

+     *
+     *  该接口会做以下事情
+     *   1. 检查提供的区块链节点是否可达
+     *   2. 检查提供的区块链连接证书是否可用
+     *
+     *  完成以上检查,则保存一条可连接的区块链的信息,否则返回配置信息有误。
+     *
+     * 
+ * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @param alias 区块链别名 + * @param desc 描述 + * @param clientConfig 区块链的客户端配置 + * @return true:配置信息可用 false:配置信息不可用 + */ + void updateBlockchain(String product, String blockchainId, String pluginServerId, String alias, String desc, + Map clientConfig); + + /** + * 更新区块链单个配置 + * + * @param product + * @param blockchainId + * @param confKey + * @param confValue + * @return + */ + void updateBlockchainProperty(String product, String blockchainId, String confKey, String confValue); + + boolean hasBlockchain(String domain); + + DomainCertWrapper getDomainCert(String domain); + + /** + * 为指定区块链部署 AM合约 + * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @return true:部署成功 false:部署失败 + */ + void deployAMClientContract(String product, String blockchainId); + + /** + * 启动指定的区块链anchor(区块链监听程序) + *

+ * 启动区块链的anchor后,anchor会持续监听区块链的每一个区块链上的请求 + * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @return true:启动成功 false:启动失败 + */ + void startBlockchainAnchor(String product, String blockchainId); + + /** + * 暂停指定的区块链anchor(区块链监听程序) + *

+ * 暂停区块链的anchor后,anchor会停止监听区块链, + * 历史已监听的进度会持久化保存,再次启动anchor会从上次暂停的进度继续监听 + * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @return true:暂停成功 false:暂停失败 + */ + void stopBlockchainAnchor(String product, String blockchainId); + + /** + * 获取区块链元信息,包括以下信息 + *

  • 区块链的客户端配置信息
  • + *
  • anchor的运行状态
  • + *
  • 链上合约地址
  • + * + * @param product 区块链产品类型 + * @param blockchainId 区块链id + * @return 元信息模型 + */ + BlockchainMeta getBlockchainMeta(String product, String blockchainId); + + BlockchainMeta getBlockchainMetaByDomain(String domain); + + String getBlockchainDomain(String product, String blockchainId); + + /** + * 更新区块链元信息 + * + * @param blockchainMeta + * @return + */ + boolean updateBlockchainMeta(BlockchainMeta blockchainMeta); + + /** + * 获取所有区块链元信息 + * + * @return + */ + List getAllBlockchainMeta(); + + /** + * 查询区块链同步信息 + * + * @param product + * @param blockchainId + * @return + */ + BlockchainAnchorProcess getBlockchainAnchorProcess(String product, String blockchainId); + + /** + * 查询当前正在服务的区块链,已经注册跨链还没stop的链 + * + * @return + */ + List getAllServingBlockchains(); + + List getAllStoppedBlockchains(); + + /** + * 检查domain是否具有blockchain数据 + * + * @param domain + * @return + */ + boolean checkIfDomainPrepared(String domain); + + /** + * 检查domain对应的链在运行中 + * + * @param domain + * @return + */ + boolean checkIfDomainRunning(String domain); + + /** + * 检查domain是否完成了AM合约的部署 + * + * @param domain + * @return + */ + boolean checkIfDomainAMDeployed(String domain); + + /** + * 获取绑定指定插件服务的所有链 + * + * @param pluginServerId + * @return + */ + List getBlockchainsByPluginServerId(String pluginServerId); + + /** + * Update the sdp msg sequence for the channel identified + * by tuple ( senderDomain, from, receiverDomain, to ). + * + * @param receiverProduct product of receiver blockchain + * @param receiverBlockchainId id of receiver blockchain + * @param senderDomain sender domain + * @param from sender contract of the msg + * @param to receiver contract of the msg + * @param newSeq new sequence number + */ + void updateSDPMsgSeq(String receiverProduct, String receiverBlockchainId, String senderDomain, String from, String to, long newSeq); + + /** + * Query the sdp msg sequence for the channel identified + * by tuple ( senderDomain, from, receiverDomain, to ). + * + * @param receiverProduct product of receiver blockchain + * @param receiverBlockchainId id of receiver blockchain + * @param senderDomain sender domain + * @param from sender contract of the msg + * @param to receiver contract of the msg + */ + long querySDPMsgSeq(String receiverProduct, String receiverBlockchainId, String senderDomain, String from, String to); +} + diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/GovernManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/GovernManager.java new file mode 100644 index 0000000..6ed1d15 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/GovernManager.java @@ -0,0 +1,58 @@ +/* + * 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.relayer.core.manager.gov; + +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.CrossChainMsgACLItem; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMsgACLRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class GovernManager implements IGovernManager { + + @Resource + private ICrossChainMsgACLRepository crossChainMsgACLRepository; + + @Override + public boolean verifyCrossChainMsgACL(String ownerDomain, String ownerIdHex, String grantDomain, String grantIdHex) { + CrossChainMsgACLItem crossChainMsgACLItem = new CrossChainMsgACLItem(); + crossChainMsgACLItem.setOwnerDomain(ownerDomain); + crossChainMsgACLItem.setOwnerIdentityHex(ownerIdHex); + crossChainMsgACLItem.setGrantDomain(grantDomain); + crossChainMsgACLItem.setGrantIdentityHex(grantIdHex); + + return crossChainMsgACLRepository.checkItem(crossChainMsgACLItem); + } + + @Override + public void addCrossChainMsgACL(CrossChainMsgACLItem crossChainMsgACLItem) { + crossChainMsgACLRepository.saveItem(crossChainMsgACLItem); + } + + @Override + public void delCrossChainMsgACL(String bizId) { + crossChainMsgACLRepository.deleteItem(bizId); + } + + @Override + public CrossChainMsgACLItem getCrossChainMsgACL(String bizId) { + return crossChainMsgACLRepository.getItemByBizId(bizId); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/IGovernManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/IGovernManager.java new file mode 100644 index 0000000..dcca9f3 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/gov/IGovernManager.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.relayer.core.manager.gov; + +import com.alipay.antchain.bridge.relayer.commons.model.CrossChainMsgACLItem; + +public interface IGovernManager { + + boolean verifyCrossChainMsgACL(String ownerDomain, String ownerIdHex, String grantDomain, String grantIdHex); + + void addCrossChainMsgACL(CrossChainMsgACLItem crossChainMsgACLItem); + + void delCrossChainMsgACL(String bizId); + + CrossChainMsgACLItem getCrossChainMsgACL(String bizId); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerCredentialManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerCredentialManager.java new file mode 100644 index 0000000..e75e552 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerCredentialManager.java @@ -0,0 +1,47 @@ +/* + * 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.relayer.core.manager.network; + +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.relayer.core.types.network.request.RelayerRequest; +import com.alipay.antchain.bridge.relayer.core.types.network.response.RelayerResponse; + +public interface IRelayerCredentialManager { + + /** + * @param relayerRequest + */ + void signRelayerRequest(RelayerRequest relayerRequest); + + /** + * @param relayerResponse + */ + void signRelayerResponse(RelayerResponse relayerResponse); + + AbstractCrossChainCertificate getLocalRelayerCertificate(); + + RelayerCredentialSubject getLocalRelayerCredentialSubject(); + + boolean validateRelayerRequest(RelayerRequest relayerRequest); + + boolean validateRelayerResponse(RelayerResponse relayerResponse); + + String getLocalNodeId(); + + String getLocalNodeSigAlgo(); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerNetworkManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerNetworkManager.java new file mode 100644 index 0000000..aa56d44 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/IRelayerNetworkManager.java @@ -0,0 +1,215 @@ +package com.alipay.antchain.bridge.relayer.core.manager.network; + +import java.util.List; + +import com.alipay.antchain.bridge.relayer.commons.constant.RelayerNodeSyncStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainInfo; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerHealthInfo; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNetwork; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; + +/** + * 该Manager提供个管理RelayerNetwork的系列管理接口 + */ +public interface IRelayerNetworkManager { + + //********************************************** + // relayer节点管理 + //********************************************** + + /** + * 获取Relayer自身基本信息 + * + * @return + */ + RelayerNodeInfo getRelayerNodeInfo(); + + /** + * 获取Relayer自身基本信息,并且带上relayerBlockchainInfos。 + * + * @return + */ + RelayerNodeInfo getRelayerNodeInfoWithContent(); + + /** + * 获取对应domain的relayerBlockchainInfos。 + * + * @param domain + * @return + */ + RelayerBlockchainInfo getRelayerBlockchainInfo(String domain); + + /** + * 添加Relayer节点,仅仅只是新增relayer节点信息到数据库,需要另外调用syncRelayerNode去同步节点信息 + * + * @param nodeInfo + * @return + */ + void addRelayerNode(RelayerNodeInfo nodeInfo); + + /** + * 将除了域名列表和RelayerBlockchainInfo之外的信息存储起来 + * + * @param nodeInfo + * @return + */ + void addRelayerNodeWithoutDomainInfo(RelayerNodeInfo nodeInfo); + + /** + * 向relayer node中添加属性 + * + * @param nodeId + * @param key + * @param value + * @return + */ + void addRelayerNodeProperty(String nodeId, String key, String value); + + /** + * 获取Relayer节点 + * + * @param nodeId + * @return + */ + RelayerNodeInfo getRelayerNode(String nodeId); + + /** + * 同步网络里Relayer节点信息,该同步动作会向远程relayer节点请求信息,包括: + *
  • 预言机Evidence + *
  • 支持的domain + *
  • 支持的domain对应的预言机签名udns(含预言机c_key、预言机签名) + * + * 得到以上信息后,会执行对应的校验 + *
  • 使用本地配置的预言机信任根校验预言机Evidence(如果本地没有配置信任根,则会不会校验信任根,并且将远程信任根设置为信任根) + *
  • 校验relayer合法持有domain(避免路由欺诈) + *
  • 校验预言机签名udns的合法性 + * + * 以上步骤均完成后,节点状态就变更为sync状态,表示已完成元信息同步 + * + * @param networkId 网络id + * @param nodeId 节点id + * @return + */ + void syncRelayerNode(String networkId, String nodeId); + + //********************************************** + // relayer 网络管理 + //********************************************** + + /** + * 查找domain name所在的网络 + * + * @param domainName + * @return + */ + RelayerNetwork findNetworkByDomainName(String domainName); + + //********************************************** + // relayer 网络管理 + //********************************************** + + /** + * 查找domain name所在的网络 + * + * @param domainName + * @return + */ + RelayerNetwork.Item findNetworkItemByDomainName(String domainName); + + /** + * 往Relayer网络新增路由信息 + * + * @param domain + * @param nodeId + * @return + */ + boolean addRelayerNetworkItem(String networkId, String domain, String nodeId); + + /** + * 往Relayer网络新增路由信息 + * + * @param networkId + * @param domain + * @param nodeId + * @param syncState + * @return + */ + boolean addRelayerNetworkItem(String networkId, String domain, String nodeId, RelayerNodeSyncStateEnum syncState); + + /** + * 删除对应的item + * + * @param domain + * @param nodeId + * @return + */ + boolean deleteRelayerNetworkItem(String domain, String nodeId); + + /** + * 获取指定id的网络 + * + * @param networkId + * @return + */ + RelayerNetwork getRelayerNetwork(String networkId); + + /** + * 获取domain对应的relayer的Node Info + * + * @param domain + * @return + */ + RelayerNodeInfo getRelayerNodeInfoForDomain(String domain); + + /** + * 从DiscoveryServer获取对应域名的relayer node info。 + * @param domain + * @return + */ + RelayerNodeInfo getRemoteRelayerNodeInfo(String domain); + + /** + * 注册域名到DiscoveryServer + * + * @param nodeInfo + */ + void registerDomainToDiscoveryServer(RelayerNodeInfo nodeInfo, String networkId) throws Exception; + + /** + * 更新域名到DiscoveryServer + * + * @param nodeInfo + */ + void updateDomainToDiscoveryServer(RelayerNodeInfo nodeInfo) throws Exception; + + /** + * 删除域名到DiscoveryServer + * + * @param nodeInfo + */ + void deleteDomainToDiscoveryServer(RelayerNodeInfo nodeInfo) throws Exception; + + /** + * 在amRequest和udagRequest的时候,从发现中心获取域名对应的relayer信息, + * 尝试握手获取并验证对应的信息,存储域名到数据库。 + * TODO: Need to implement + * @param domainName + * @return + */ + boolean tryHandshake(String domainName, RelayerNodeInfo remoteNodeInfo); + + /** + * 更新relayerNodeInfo + * + * @param nodeInfo + * @return + */ + boolean updateRelayerNode(RelayerNodeInfo nodeInfo); + + /** + * Obtain health information about the relayer node, including the node ip address, port number, and whether the node is alive + * + * @return + */ + List healthCheckRelayers(); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerCredentialManager.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerCredentialManager.java new file mode 100644 index 0000000..40bcb0c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerCredentialManager.java @@ -0,0 +1,133 @@ +/* + * 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.relayer.core.manager.network; + +import java.security.PrivateKey; +import java.security.Signature; + +import javax.annotation.Resource; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.core.manager.bcdns.IBCDNSManager; +import com.alipay.antchain.bridge.relayer.core.types.network.request.RelayerRequest; +import com.alipay.antchain.bridge.relayer.core.types.network.response.RelayerResponse; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@Getter +public class RelayerCredentialManager implements IRelayerCredentialManager { + + @Value("#{relayerCoreConfig.localRelayerCrossChainCertificate}") + private AbstractCrossChainCertificate localRelayerCertificate; + + @Value("#{relayerCoreConfig.localRelayerCredentialSubject}") + private RelayerCredentialSubject localRelayerCredentialSubject; + + @Value("#{relayerCoreConfig.localPrivateKey}") + private PrivateKey localRelayerPrivateKey; + + @Value("#{relayerCoreConfig.localRelayerNodeId}") + private String localNodeId; + + @Value("${relayer.network.node.sig_algo:SHA256WithRSA}") + private String localNodeSigAlgo; + + @Resource + private IBCDNSManager bcdnsManager; + + @Override + public void signRelayerRequest(RelayerRequest relayerRequest) { + try { + relayerRequest.setNodeId(localNodeId); + relayerRequest.setSenderRelayerCertificate(localRelayerCertificate); + relayerRequest.setSigAlgo(localNodeSigAlgo); + + Signature signer = Signature.getInstance(localNodeSigAlgo); + signer.initSign(localRelayerPrivateKey); + signer.update(relayerRequest.rawEncode()); + relayerRequest.setSignature(signer.sign()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + e, + "failed to sign for request type {}", relayerRequest.getRequestType().getCode() + ); + } + } + + @Override + public void signRelayerResponse(RelayerResponse relayerResponse) { + try { + relayerResponse.setRemoteRelayerCertificate(localRelayerCertificate); + relayerResponse.setSigAlgo(localNodeSigAlgo); + + Signature signer = Signature.getInstance(localNodeSigAlgo); + signer.initSign(localRelayerPrivateKey); + signer.update(relayerResponse.rawEncode()); + relayerResponse.setSignature(signer.sign()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "failed to sign response", + e + ); + } + } + + @Override + public boolean validateRelayerRequest(RelayerRequest relayerRequest) { + if (!bcdnsManager.validateCrossChainCertificate(relayerRequest.getSenderRelayerCertificate())) { + return false; + } + if ( + ObjectUtil.notEqual( + CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, + relayerRequest.getSenderRelayerCertificate().getType() + ) + ) { + return false; + } + + return relayerRequest.verify(); + } + + @Override + public boolean validateRelayerResponse(RelayerResponse relayerResponse) { + if (!bcdnsManager.validateCrossChainCertificate(relayerResponse.getRemoteRelayerCertificate())) { + return false; + } + if ( + ObjectUtil.notEqual( + CrossChainCertificateTypeEnum.RELAYER_CERTIFICATE, + relayerResponse.getRemoteRelayerCertificate().getType() + ) + ) { + return false; + } + + return relayerResponse.verify(); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerNetworkManagerImpl.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerNetworkManagerImpl.java new file mode 100644 index 0000000..d6d2426 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/manager/network/RelayerNetworkManagerImpl.java @@ -0,0 +1,459 @@ +/* + * 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.relayer.core.manager.network; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +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.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.RelayerNodeSyncStateEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.*; +import com.alipay.antchain.bridge.relayer.core.manager.bcdns.IBCDNSManager; +import com.alipay.antchain.bridge.relayer.core.types.network.IRelayerClientPool; +import com.alipay.antchain.bridge.relayer.core.types.network.RelayerClient; +import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.IRelayerNetworkRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.ISystemConfigRepository; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Getter +@Component +public class RelayerNetworkManagerImpl implements IRelayerNetworkManager { + + @Value("${relayer.network.node.crosschain_cert_path:null}") + private String relayerCrossChainCertPath; + + @Value("${relayer.network.node.private_key_path}") + private String relayerPrivateKeyPath; + + @Value("${relayer.network.node.server.mode:https}") + private String localNodeServerMode; + + @Resource + private IRelayerNetworkRepository relayerNetworkRepository; + + @Resource + private IBlockchainRepository blockchainRepository; + + @Resource + private ISystemConfigRepository systemConfigRepository; + + @Resource + private IRelayerCredentialManager relayerCredentialManager; + + @Resource + private IBCDNSManager bcdnsManager; + + @Resource + private IRelayerClientPool relayerClientPool; + + private final Cache relayerNodeInfoCache = CacheUtil.newLRUCache(10, 3_000); + + @Override + public RelayerNodeInfo getRelayerNodeInfo() { + try { + if (relayerNodeInfoCache.containsKey(relayerCredentialManager.getLocalNodeId())) { + return relayerNodeInfoCache.get(relayerCredentialManager.getLocalNodeId()); + } + RelayerNodeInfo localNodeInfo = new RelayerNodeInfo( + relayerCredentialManager.getLocalNodeId(), + relayerCredentialManager.getLocalRelayerCertificate(), + relayerCredentialManager.getLocalRelayerCredentialSubject(), + relayerCredentialManager.getLocalNodeSigAlgo(), + systemConfigRepository.getLocalEndpoints(), + blockchainRepository.getBlockchainDomainsByState(BlockchainStateEnum.RUNNING) + ); + relayerNodeInfoCache.put(relayerCredentialManager.getLocalNodeId(), localNodeInfo); + return localNodeInfo; + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "failed to get local relayer node info", + e + ); + } + } + + @Override + @Transactional + public RelayerNodeInfo getRelayerNodeInfoWithContent() { + RelayerNodeInfo nodeInfo = getRelayerNodeInfo(); + + Map blockchainInfoMap = nodeInfo.getDomains().stream() + .map(this::getRelayerBlockchainInfo) + .collect(Collectors.toMap( + info -> info.getDomainCert().getDomain(), + info -> info + )); + Map trustRootCertChain = blockchainInfoMap.values().stream() + .map(info -> this.bcdnsManager.getTrustRootCertChain(info.getDomainCert().getDomainSpace())) + .reduce( + (map1, map2) -> { + map1.putAll(map2); + return map1; + } + ).orElse(MapUtil.newHashMap()); + + RelayerBlockchainContent content = new RelayerBlockchainContent( + blockchainInfoMap, + trustRootCertChain + ); + + nodeInfo.setRelayerBlockchainContent(content); + + return nodeInfo; + } + + @Override + public RelayerBlockchainInfo getRelayerBlockchainInfo(String domain) { + DomainCertWrapper domainCertWrapper = blockchainRepository.getDomainCert(domain); + if (ObjectUtil.isNull(domainCertWrapper)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "none domain cert found for {}", domain + ); + } + + List domainSpaceChain = bcdnsManager.getDomainSpaceChain(domainCertWrapper.getDomainSpace()); + if (ObjectUtil.isEmpty(domainSpaceChain)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "none domain space chain found for {}", domainCertWrapper.getDomainSpace() + ); + } + + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMeta( + domainCertWrapper.getBlockchainProduct(), + domainCertWrapper.getBlockchainId() + ); + if (ObjectUtil.isEmpty(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "none blockchain meta found for {}", domain + ); + } + + return new RelayerBlockchainInfo( + domainCertWrapper, + domainSpaceChain, + blockchainMeta.getProperties().getAmClientContractAddress() + ); + } + + @Override + public void addRelayerNode(RelayerNodeInfo nodeInfo) { + log.info("add relayer node {} with endpoints {}", nodeInfo.getNodeId(), StrUtil.join(StrUtil.COMMA, nodeInfo.getEndpoints())); + + try { + if (relayerNetworkRepository.hasRelayerNode(nodeInfo.getNodeId())) { + log.warn("relayer node {} already exists", nodeInfo.getNodeId()); + return; + } + if (ObjectUtil.isEmpty(nodeInfo.getEndpoints())) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "relayer info not enough" + ); + } + + // 如果公钥以及domain信息没设置,则远程请求补充 + if (ObjectUtil.isNull(nodeInfo.getRelayerCrossChainCertificate())) { + RelayerClient relayerClient = relayerClientPool.getRelayerClient(nodeInfo); + if (ObjectUtil.isNull(relayerClient)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + "failed to create relayer client for relayer {} with endpoint {}", + nodeInfo.getNodeId(), StrUtil.join(StrUtil.COMMA, nodeInfo.getEndpoints()) + ); + } + + RelayerNodeInfo relayerNodeInfo = relayerClient.getRelayerNodeInfo(); + if (ObjectUtil.isNull(relayerNodeInfo)) { + throw new RuntimeException("null relayer node info from remote relayer"); + } + + nodeInfo.setRelayerCrossChainCertificate(relayerNodeInfo.getRelayerCrossChainCertificate()); + nodeInfo.setRelayerCredentialSubject(relayerNodeInfo.getRelayerCredentialSubject()); + relayerNodeInfo.getDomains().forEach( + domain -> { + if (!nodeInfo.getDomains().contains(domain)) { + nodeInfo.addDomainIfNotExist(domain); + } + } + ); + } + + relayerNetworkRepository.addRelayerNode(nodeInfo); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + e, + "failed to add relayer {} with endpoint {}", + nodeInfo.getNodeId(), StrUtil.join(StrUtil.COMMA, nodeInfo.getEndpoints()) + ); + } + } + + @Override + public void addRelayerNodeWithoutDomainInfo(RelayerNodeInfo nodeInfo) { + try { + if (relayerNetworkRepository.hasRelayerNode(nodeInfo.getNodeId())) { + log.warn("relayer node {} already exist", nodeInfo.getNodeId()); + return; + } + List domains = nodeInfo.getDomains(); + RelayerBlockchainContent relayerBlockchainContent = nodeInfo.getRelayerBlockchainContent(); + + nodeInfo.setDomains(ListUtil.toList()); + ; + nodeInfo.setRelayerBlockchainContent(null); + + relayerNetworkRepository.addRelayerNode(nodeInfo); + + nodeInfo.setDomains(domains); + nodeInfo.setRelayerBlockchainContent(relayerBlockchainContent); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + e, + "failed to add relayer {} without domain stuff", + nodeInfo.getNodeId() + ); + } + } + + @Override + public void addRelayerNodeProperty(String nodeId, String key, String value) { + try { + relayerNetworkRepository.updateRelayerNodeProperty(nodeId, key, value); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + e, + "failed to add relayer property {} - {}", + nodeId, key, value + ); + } + } + + @Override + public RelayerNodeInfo getRelayerNode(String nodeId) { + return relayerNetworkRepository.getRelayerNode(nodeId); + } + + @Override + @Transactional + public void syncRelayerNode(String networkId, String nodeId) { + log.info("begin sync relayer node {} in network {}", nodeId, networkId); + + try { + RelayerNodeInfo relayerNode = relayerNetworkRepository.getRelayerNode(nodeId); + if (null == relayerNode) { + throw new RuntimeException(StrUtil.format("relayer {} not exist", nodeId)); + } + + log.info("relayer node {} has {} domain", nodeId, relayerNode.getDomains().size()); + + RelayerBlockchainContent relayerBlockchainContent = relayerClientPool.getRelayerClient(relayerNode).getRelayerBlockchainContent(); + RelayerBlockchainContent.ValidationResult validationResult = relayerBlockchainContent.validate( + bcdnsManager.getTrustRootCertForRootDomain() + ); + + validationResult.getBlockchainInfoMapValidated().forEach( + (key, value) -> { + try { + processRelayerBlockchainInfo( + networkId, + key, + relayerNode, + value + ); + } catch (Exception e) { + log.error("failed process blockchain info for {} from relayer {}", key, nodeId, e); + } + log.info("sync domain {} success from relayer {}", key, nodeId); + } + ); + + relayerNode.setRelayerBlockchainContent(relayerBlockchainContent); + if (!relayerNetworkRepository.updateRelayerNode(relayerNode)) { + throw new RuntimeException( + StrUtil.format("update relayer info fail {} ", relayerNode.getNodeId()) + ); + } + + bcdnsManager.saveDomainSpaceCerts(validationResult.getDomainSpaceValidated()); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_RELAYER_NETWORK_ERROR, + e, + "failed to sync relayer node {}", + nodeId + ); + } + } + + @Override + public RelayerNetwork findNetworkByDomainName(String domainName) { + return relayerNetworkRepository.getRelayerNetworkByDomain(domainName); + } + + @Override + public RelayerNetwork.Item findNetworkItemByDomainName(String domainName) { + return relayerNetworkRepository.getNetworkItem(domainName); + } + + @Override + public boolean addRelayerNetworkItem(String networkId, String domain, String nodeId) { + return addRelayerNetworkItem( + networkId, + domain, + nodeId, + RelayerNodeSyncStateEnum.INIT + ); + } + + @Override + public boolean addRelayerNetworkItem(String networkId, String domain, String nodeId, RelayerNodeSyncStateEnum syncState) { + try { + relayerNetworkRepository.addNetworkItem( + networkId, + domain, + nodeId, + syncState + ); + } catch (Exception e) { + log.error( + "failed to add network item (network_id: {}, domain: {}, node_id: {}, state: {})", + networkId, domain, nodeId, syncState.getCode(), + e + ); + return false; + } + return true; + } + + @Override + public boolean deleteRelayerNetworkItem(String domain, String nodeId) { + try { + relayerNetworkRepository.deleteNetworkItem(domain, nodeId); + } catch (Exception e) { + log.error("failed to delete relayer network (domain: {}, node_id: {})", domain, nodeId, e); + return false; + } + return true; + } + + @Override + public RelayerNetwork getRelayerNetwork(String networkId) { + return relayerNetworkRepository.getRelayerNetwork(networkId); + } + + @Override + @Transactional + public RelayerNodeInfo getRelayerNodeInfoForDomain(String domain) { + String remoteNodeId = relayerNetworkRepository.getRelayerNodeIdForDomain(domain); + if (StrUtil.isEmpty(remoteNodeId)) { + return relayerNetworkRepository.getRelayerNode(remoteNodeId); + } + + RelayerNodeInfo localRelayerNodeInfo = getRelayerNodeInfo(); + return localRelayerNodeInfo.getDomains().contains(domain) ? localRelayerNodeInfo : null; + } + + @Override + public RelayerNodeInfo getRemoteRelayerNodeInfo(String domain) { + return null; + } + + @Override + public void registerDomainToDiscoveryServer(RelayerNodeInfo nodeInfo, String networkId) throws Exception { + throw new RuntimeException("not implemented"); + } + + @Override + public void updateDomainToDiscoveryServer(RelayerNodeInfo nodeInfo) throws Exception { + throw new RuntimeException("not implemented"); + } + + @Override + public void deleteDomainToDiscoveryServer(RelayerNodeInfo nodeInfo) throws Exception { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean tryHandshake(String domainName, RelayerNodeInfo remoteNodeInfo) { + return false; + } + + @Override + public boolean updateRelayerNode(RelayerNodeInfo nodeInfo) { + return false; + } + + @Override + public List healthCheckRelayers() { + return relayerNetworkRepository.getAllRelayerHealthInfo(); + } + + private void processRelayerBlockchainInfo(String networkId, String domain, RelayerNodeInfo relayerNode, + RelayerBlockchainInfo relayerBlockchainInfo) { +// if ( +// !bcdnsManager.validateDomainCertificate( +// relayerBlockchainInfo.getDomainCert().getCrossChainCertificate(), +// relayerBlockchainInfo.getDomainSpaceChain() +// ) +// ) { +// throw new RuntimeException("Invalid domain certificate for domain " + domain); +// } + //TODO: validate ptc certificate + //TODO: validate domain tpbta + + if (relayerNetworkRepository.hasNetworkItem(networkId, domain, relayerNode.getNodeId())) { + relayerNetworkRepository.updateNetworkItem(networkId, domain, relayerNode.getNodeId(), RelayerNodeSyncStateEnum.SYNC); + } else { + addRelayerNetworkItem(networkId, domain, relayerNode.getNodeId(), RelayerNodeSyncStateEnum.SYNC); + } + relayerNode.addDomainIfNotExist(domain); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/AnchorProcess.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/AnchorProcess.java new file mode 100644 index 0000000..abc4ed3 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/AnchorProcess.java @@ -0,0 +1,116 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor; + +import java.util.concurrent.ExecutorService; + +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.service.anchor.tasks.*; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RedissonClient; +import org.springframework.transaction.support.TransactionTemplate; + +/** + * anchorProcess对象是一条区块链的锚定器,包括同步远端最新高度、同步账本、处理账本等锚定任务,以及向区块链提交交易的操作接口。 + * + *
    + * 该对象有以下结构
    + *  1. 区块链配置上下文 ProcessContext
    + *  2. 三组任务
    + *   - 最新高度同步任务
    + *   - 账本同步任务
    + *   - 账本处理任务
    + *  3. 一个提交器,该提交器封装了像该区块链提交tx的逻辑
    + * 
    + */ +@Getter +@Setter +@Slf4j +public class AnchorProcess { + + /** + * process上下文 + */ + private AnchorProcessContext processContext; + + // 三组锚定任务 + private BlockPollingTask blockPollingTask; + + private BlockSyncTask blockSyncTask; + + private BlockNotifyTask notifyTask; + + public AnchorProcess( + BlockchainMeta chainMeta, + TransactionTemplate transactionTemplate, + BlockchainClientPool blockchainClientPool, + RedissonClient redisson, + ExecutorService blockSyncTaskThreadsPool, + ReceiverService receiverService, + int blockCacheCapacity, + int blockCacheTTL, + int syncBatchSize, + int syncAsyncQuerySize, + int maxDiffBetweenSyncAndNotify, + int notifyBatchSize + ) { + // init context + this.processContext = new AnchorProcessContext( + chainMeta, + transactionTemplate, + blockchainClientPool, + redisson, + blockSyncTaskThreadsPool, + receiverService, + new CachedBlockQueue( + redisson, + blockCacheCapacity, + blockCacheTTL + ), + blockCacheCapacity, + blockCacheTTL, + syncBatchSize, + syncAsyncQuerySize, + maxDiffBetweenSyncAndNotify, + notifyBatchSize + ); + + // init tasks + this.blockPollingTask = new BlockPollingTask(this.processContext); + this.blockSyncTask = new BlockSyncTask(this.processContext); + this.notifyTask = new BlockNotifyTask(this.processContext); + } + + public void run() { + log.info("start anchor process for {} ", processContext.getBlockchainMeta().getMetaKey()); + + try { + // 同步最新高度 + this.blockPollingTask.doProcess(); + // 同步远程区块 + this.blockSyncTask.doProcess(); + // 区块处理任务 + this.notifyTask.doProcess(); + } catch (Exception e) { + log.error("anchor process failed for {} : ", processContext.getBlockchainMeta().getMetaKey(), e); + return; + } + + log.info("success to run anchor process for {} : ", processContext.getBlockchainMeta().getMetaKey()); + } + + public void updateBlockchainMetaIntoClient(BlockchainMeta blockchainMeta) { + processContext.getBlockchainClient().setBlockchainMeta(blockchainMeta); + } + + public String getDomain() { + return processContext.getBlockchainClient().getDomain(); + } + + public void setDomain(String domain) { + processContext.getBlockchainClient().setDomain(domain); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/MultiAnchorProcessService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/MultiAnchorProcessService.java new file mode 100644 index 0000000..35e35d7 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/MultiAnchorProcessService.java @@ -0,0 +1,205 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import lombok.Getter; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +/** + * AnchorProcess管理器,负责AnchorProcess的声明周期管理、执行。 + */ +@Service +@Slf4j +@Getter +public class MultiAnchorProcessService { + + /** + * anchorProcess + */ + private final ConcurrentMap anchorProcessMap = MapUtil.newConcurrentHashMap(); + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private BlockchainClientPool blockchainClientPool; + + @Resource + private RedissonClient redisson; + + @Resource + private ExecutorService blockSyncTaskThreadsPool; + + @Resource + private TransactionTemplate transactionTemplate; + + @Resource + private ReceiverService receiverService; + + @Value("${relayer.service.anchor.sync_task.batch_size:32}") + private int syncTaskBatchSize; + + @Value("${relayer.service.anchor.sync_task.async_size:10}") + private int syncTaskAsyncQuerySize; + + @Value("${relayer.service.anchor.sync_task.max_diff_with_notify:100}") + private int maxDiffBetweenSyncAndNotify; + + @Value("${relayer.service.anchor.notify_task.batch_size:32}") + private int notifyTaskBatchSize; + + @Value("${relayer.service.anchor.block_cache_capacity:100}") + private int blockCacheCapacity; + + @Value("${relayer.service.anchor.block_cache_ttl:300000}") + private int blockCacheTTL; + + /** + * 启动指定anchorProcess + * + * @param blockchainProduct + * @param blockchainId + */ + public void runAnchorProcess(String blockchainProduct, String blockchainId) { + AnchorProcess anchorProcess = getAnchorProcess(blockchainProduct, blockchainId); + if (ObjectUtil.isNull(anchorProcess)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_START_ANCHOR_FAILED, + "null anchor process for {}-{}", blockchainProduct, blockchainId + ); + } + // 触发执行 + anchorProcess.run(); + } + + /** + * 获取anchorProcess对象 + * + * @param blockchainProduct + * @param blockchainId + * @return + */ + @Synchronized + public AnchorProcess getAnchorProcess(String blockchainProduct, String blockchainId) { + if (!anchorProcessMap.containsKey(getAnchorProcessKey(blockchainProduct, blockchainId))) { + log.info("build new anchor process object for {}-{}", blockchainProduct, blockchainId); + newAnchorProcess(blockchainProduct, blockchainId); + } + + AnchorProcess anchorProcess = anchorProcessMap.getOrDefault( + getAnchorProcessKey(blockchainProduct, blockchainId), + null + ); + if (ObjectUtil.isNotNull(anchorProcess)) { + updateAnchorProcess(anchorProcess); + } + + return anchorProcess; + } + + private String getAnchorProcessKey(String blockchainProduct, String blockchainId) { + return blockchainProduct + "_" + blockchainId; + } + + /** + * 添加anchorProcess对象 + */ + private void newAnchorProcess(String blockchainProduct, String blockchainId) { + + BlockchainMeta blockchainMeta = blockchainManager.getBlockchainMeta(blockchainProduct, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_START_ANCHOR_FAILED, + "none blockchain meta found for {}-{}", + blockchainProduct, blockchainId + ); + } + + AnchorProcess anchorProcess = new AnchorProcess( + blockchainMeta, + transactionTemplate, + blockchainClientPool, + redisson, + blockSyncTaskThreadsPool, + receiverService, + blockCacheCapacity, + blockCacheTTL, + syncTaskBatchSize, + syncTaskAsyncQuerySize, + maxDiffBetweenSyncAndNotify, + notifyTaskBatchSize + ); + if (ObjectUtil.isNull(anchorProcess)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_START_ANCHOR_FAILED, + "null anchor process returned for {}-{}", + blockchainProduct, blockchainId + ); + } + this.anchorProcessMap.putIfAbsent( + anchorProcess.getProcessContext().getBlockchainMeta().getMetaKey(), + anchorProcess + ); + } + + /** + * 更新anchorProcess对象 + * + * @param anchorProcess + */ + private void updateAnchorProcess(AnchorProcess anchorProcess) { + + // 这里可以使用缓存,隔几分钟才能更新到最新的配置,没必要每次都更新 + BlockchainMeta blockchainMeta = blockchainManager.getBlockchainMeta( + anchorProcess.getProcessContext().getBlockchainMeta().getProduct(), + anchorProcess.getProcessContext().getBlockchainMeta().getBlockchainId() + ); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_START_ANCHOR_FAILED, + "none blockchain meta found when update anchor process for {}", + anchorProcess.getProcessContext().getBlockchainMeta().getMetaKey() + ); + } + anchorProcess.updateBlockchainMetaIntoClient(blockchainMeta); + + log.info("update anchor blockchain meta for : {}_{}", blockchainMeta.getProduct(), blockchainMeta.getBlockchainId()); + + + // 如果部署了跨链服务,就同步domain name信息 + if ( + anchorProcess.getProcessContext().getBlockchainClient().ifHasDeployedAMClientContract() + && StrUtil.isEmpty(anchorProcess.getDomain()) + ) { + String domain = blockchainManager.getBlockchainDomain( + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ); + if (StrUtil.isEmpty(domain)) { + return; + } + log.info( + "update domain name info anchor for {} - {}", + anchorProcess.getProcessContext().getBlockchainMeta().getMetaKey(), + domain + ); + anchorProcess.setDomain(domain); + } + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/context/AnchorProcessContext.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/context/AnchorProcessContext.java new file mode 100644 index 0000000..d0cf30e --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/context/AnchorProcessContext.java @@ -0,0 +1,109 @@ +/* + * 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.relayer.core.service.anchor.context; + +import java.util.concurrent.ExecutorService; + +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.service.anchor.tasks.IBlockQueue; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; +import lombok.Getter; +import lombok.Setter; +import org.redisson.api.RedissonClient; +import org.springframework.transaction.support.TransactionTemplate; + +@Getter +@Setter +public class AnchorProcessContext { + + private String anchorProduct; + + private String anchorBlockchainId; + + private BlockchainClientPool blockchainClientPool; + + private ReceiverService receiverService; + + private IBlockQueue blockQueue; + + private TransactionTemplate transactionTemplate; + + private RedissonClient redisson; + + private ExecutorService blockSyncTaskThreadsPool; + + private int blockCacheCapacity; + + private int blockCacheTTL; + + private int syncBatchSize; + + private int syncAsyncQuerySize; + + private int maxDiffBetweenSyncAndNotify; + + private int notifyBatchSize; + + public AnchorProcessContext( + BlockchainMeta blockchainMeta, + TransactionTemplate transactionTemplate, + BlockchainClientPool blockchainClientPool, + RedissonClient redisson, + ExecutorService blockSyncTaskThreadsPool, + ReceiverService receiverService, + IBlockQueue blockQueue, + int blockCacheCapacity, + int blockCacheTTL, + int syncBatchSize, + int syncAsyncQuerySize, + int maxDiffBetweenSyncAndNotify, + int notifyBatchSize + ) { + this.blockchainClientPool = blockchainClientPool; + this.anchorProduct = blockchainMeta.getProduct(); + this.anchorBlockchainId = blockchainMeta.getBlockchainId(); + this.blockQueue = blockQueue; + this.transactionTemplate = transactionTemplate; + this.redisson = redisson; + this.blockSyncTaskThreadsPool = blockSyncTaskThreadsPool; + this.receiverService = receiverService; + this.blockCacheCapacity = blockCacheCapacity; + this.blockCacheTTL = blockCacheTTL; + this.syncBatchSize = syncBatchSize; + this.syncAsyncQuerySize = syncAsyncQuerySize; + this.maxDiffBetweenSyncAndNotify = maxDiffBetweenSyncAndNotify; + this.notifyBatchSize = notifyBatchSize; + + // init blockchain client + blockchainClientPool.createClient(blockchainMeta); + } + + public AbstractBlockchainClient getBlockchainClient() { + return blockchainClientPool.getClient(anchorProduct, anchorBlockchainId); + } + + public IBlockchainRepository getBlockchainRepository() { + return blockchainClientPool.getBlockchainRepository(); + } + + public BlockchainMeta getBlockchainMeta() { + return blockchainClientPool.getClient(anchorProduct, anchorBlockchainId).getBlockchainMeta(); + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockBaseTask.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockBaseTask.java new file mode 100644 index 0000000..0bcec5e --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockBaseTask.java @@ -0,0 +1,83 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.tasks; + +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import lombok.Getter; + +@Getter +public abstract class BlockBaseTask { + + private final BlockTaskTypeEnum taskType; + + private final AnchorProcessContext processContext; + + public BlockBaseTask( + BlockTaskTypeEnum taskName, + AnchorProcessContext processContext + ) { + this.taskType = taskName; + this.processContext = processContext; + } + + public abstract void doProcess(); + + public void saveRemoteBlockHeaderHeight(long height) { + processContext.getBlockchainRepository().setAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.POLLING.getCode(), + height + ); + } + + protected long getRemoteBlockHeaderHeight() { + return processContext.getBlockchainRepository().getAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.POLLING.getCode() + ); + } + + protected long getLocalBlockHeaderHeight() { + return Math.max( + processContext.getBlockchainMeta().getProperties().getInitBlockHeight(), + processContext.getBlockchainRepository().getAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.SYNC.getCode() + ) + ); + } + + protected void saveLocalBlockHeaderHeight(long height) { + processContext.getBlockchainRepository().setAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.SYNC.getCode(), + height + ); + } + + public long getNotifyBlockHeaderHeight(String workerType) { + return Math.max( + processContext.getBlockchainRepository().getAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.NOTIFY.toNotifyWorkerHeightType(workerType) + ), + processContext.getBlockchainMeta().getProperties().getInitBlockHeight() + ); + } + + public long getSystemNotifyBlockHeaderHeight(String contract) { + return Long.MAX_VALUE; + } + + public void saveNotifyBlockHeaderHeight(String workerType, long height) { + processContext.getBlockchainRepository().setAnchorProcessHeight( + processContext.getBlockchainMeta().getProduct(), + processContext.getBlockchainMeta().getBlockchainId(), + BlockTaskTypeEnum.NOTIFY.toNotifyWorkerHeightType(workerType), + height + ); + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockNotifyTask.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockNotifyTask.java new file mode 100644 index 0000000..12eadd8 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockNotifyTask.java @@ -0,0 +1,194 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.tasks; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.service.anchor.workers.AuthMessageWorker; +import com.alipay.antchain.bridge.relayer.core.service.anchor.workers.BlockWorker; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * notify任务,用于处理区块里面的数据,将区块里的相关请求转发给核心引擎 + *

    + * anchor会处理链上多种合约,每种合约的处理进度可能不一样,故每种合约会有对应一个进度。 + *

    + * 该任务在处理的时候,会读取不同的合约进度条去处理。 + * + *

    + * NotifyTask类的结构:
    + *
    + * BlockNotifyTask
    + *  |-- Set(contract) // 要处理的合约集合
    + *  |     |-- notify_height // 每个合约有个已处理高度
    + *  |     |-- set(worker) // 每种合约有对应的workers
    + *  |
    + *  |-- processBlock()
    + *        // NotifyTask的主逻辑:每个合约读取对应的高度,批量读取区块,使用对应的workers去处理
    + *        set(contract).each().getEssentialHeader(notify_height + 1).set(worker).each.process()
    + *
    + * 
    + */ +@Getter +@Slf4j +public class BlockNotifyTask extends BlockBaseTask { + + /** + * 合约的workers + */ + private final Map> workersByTask = new HashMap<>(); + + public BlockNotifyTask( + AnchorProcessContext processContext + ) { + super( + BlockTaskTypeEnum.NOTIFY, + processContext + ); + + // AM合约需要解析请求及上链结果 + workersByTask.put( + NotifyTaskTypeEnum.CROSSCHAIN_MSG_WORKER, + ListUtil.toList( + new AuthMessageWorker(processContext) + ) + ); + } + + @Override + public void doProcess() { + try { + processBlock(); + } catch (Exception e) { + throw new RuntimeException("process block fail.", e); + } + } + + private void processBlock() { + + // 每个合约读取对应的高度,批量读取区块,使用对应的workers去处理 + for (NotifyTaskTypeEnum notifyTaskType : workersByTask.keySet()) { + + // 如果合约还未部署,不进行该任务 + if (!ifDeployContract(notifyTaskType)) { + log.info( + "blockchain {} has not deployed {} contract yeah, wait for it.", + getProcessContext().getBlockchainMeta().getMetaKey(), notifyTaskType.getCode() + ); + continue; + } + + // 查看已处理、未处理的区块 + long localBlockHeaderHeight = getLocalBlockHeaderHeight(); + long notifyBlockHeaderHeight = notifyTaskType == NotifyTaskTypeEnum.SYSTEM_WORKER ? + getSystemNotifyBlockHeaderHeight(notifyTaskType.getCode()) : + getNotifyBlockHeaderHeight(notifyTaskType.getCode()); + log.info( + "blockchain {} notify task {} has localBlockHeaderHeight {} and notifyBlockHeaderHeight {} now", + getProcessContext().getBlockchainMeta().getMetaKey(), + notifyTaskType.getCode(), + localBlockHeaderHeight, + notifyBlockHeaderHeight + ); + + if (notifyBlockHeaderHeight >= localBlockHeaderHeight) { + log.info( + "height {} of notify task {} equals to local height {} for blockchain {}", + notifyBlockHeaderHeight, + notifyTaskType.getCode(), + localBlockHeaderHeight, + getProcessContext().getBlockchainMeta().getMetaKey() + ); + continue; + } + + // 批量处理 + // each process task will process the gap of local block header and notify header now. + long endHeight = Math.min( + localBlockHeaderHeight, + notifyBlockHeaderHeight + getProcessContext().getNotifyBatchSize() + ); + long currentHeight = notifyBlockHeaderHeight + 1; + + log.info( + "notify task {} for blockchain {} is processing from blockHeight {} to endHeight {}", + notifyTaskType.getCode(), + getProcessContext().getBlockchainMeta().getMetaKey(), + currentHeight, + endHeight + ); + + for (; currentHeight <= endHeight; ++currentHeight) { + + AbstractBlock block = getProcessContext().getBlockQueue().getBlockFromQueue(currentHeight); + if (ObjectUtil.isNull(block)) { + log.error( + "blockchain {} notify task {} can't find block {} from block queue so skip the failed task", + getProcessContext().getBlockchainMeta().getMetaKey(), + notifyTaskType.getCode(), + currentHeight + ); + break; + } + log.info( + "blockchain {} notify task {} is processing the block {}", + getProcessContext().getBlockchainMeta().getMetaKey(), + notifyTaskType.getCode(), + currentHeight + ); + + boolean processResult = true; + + // 责任链模式,一个区块交给各个worker各处理一遍,且都要处理成功 + // TODO 一个worker处理失败,会导致该区块会全部重做一遍,这样子worker可能会收到同一个区块多次,需要能有幂等处理能力,这点可以优化 + for (BlockWorker worker : workersByTask.get(notifyTaskType)) { + if (!worker.process(block)) { + log.error( + "worker process block failed: [ blockchain: {}, height: {} ]", + getProcessContext().getBlockchainMeta().getMetaKey(), + currentHeight + ); + processResult = false; + break; + } + } + + // 处理成功,则持久化区块高度 + if (processResult) { + saveNotifyBlockHeaderHeight(notifyTaskType.getCode(), currentHeight); + log.info( + "successful to process block (height: {}) in notify task {} from chain (product: {}, blockchain_id: {})", + block.getHeight(), + notifyTaskType.getCode(), + block.getProduct(), + block.getBlockchainId() + ); + } else { + log.error( + "failed to process block (height: {}) in notify task {} from chain (product: {}, blockchain_id: {})", + block.getHeight(), + notifyTaskType.getCode(), + block.getProduct(), + block.getBlockchainId() + ); + break; + } + } + } + } + + private boolean ifDeployContract(NotifyTaskTypeEnum taskType) { + + // 这里面用了processContext里的内存变量(合约地址)来判断是否已部署合约,所以需要为何该内存变量是最新的 + // (如果serviceManager部署了合约,anchorProcess的这个processContext相关变量也要更新) + if (NotifyTaskTypeEnum.CROSSCHAIN_MSG_WORKER == taskType) { + return getProcessContext().getBlockchainClient().ifHasDeployedAMClientContract(); + } + return NotifyTaskTypeEnum.SYSTEM_WORKER == taskType; + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockPollingTask.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockPollingTask.java new file mode 100644 index 0000000..ac5d7b9 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockPollingTask.java @@ -0,0 +1,37 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.tasks; + +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import lombok.extern.slf4j.Slf4j; + +/** + * block polling task, it is responsible for sync remote block header + */ +@Slf4j +public class BlockPollingTask extends BlockBaseTask { + + public BlockPollingTask(AnchorProcessContext processContext) { + super(BlockTaskTypeEnum.POLLING, processContext); + } + + @Override + public void doProcess() { + try { + saveRemoteBlockHeaderHeight(queryRemoteBlockHeaderHeight()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_POLLING_TASK_FAILED, + e, + "query remote block header height failed for {}", + getProcessContext().getBlockchainMeta().getMetaKey() + ); + } + } + + private long queryRemoteBlockHeaderHeight() { + long blockHeaderHeight = getProcessContext().getBlockchainClient().getLastBlockHeight(); + log.info("polling height {} remote block header from {}", blockHeaderHeight, getProcessContext().getBlockchainMeta().getMetaKey()); + return blockHeaderHeight; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockSyncTask.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockSyncTask.java new file mode 100644 index 0000000..ca3fc5e --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockSyncTask.java @@ -0,0 +1,118 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.tasks; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import cn.hutool.core.lang.Assert; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import lombok.extern.slf4j.Slf4j; + +/** + * sync remote block, and update local block header. + */ +@Slf4j +public class BlockSyncTask extends BlockBaseTask { + + public BlockSyncTask( + AnchorProcessContext processContext + ) { + super(BlockTaskTypeEnum.SYNC, processContext); + Assert.isTrue(processContext.getSyncBatchSize() > 0); + } + + @Override + public void doProcess() { + try { + poll(); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_SYNC_TASK_FAILED, + e, + "failed to sync block for {}", + getProcessContext().getBlockchainMeta().getMetaKey() + ); + } + } + + private void poll() { + long localBlockHeaderHeight = getLocalBlockHeaderHeight(); + long remoteBlockHeaderHeight = getRemoteBlockHeaderHeight(); + + if (localBlockHeaderHeight >= remoteBlockHeaderHeight) { + log.info( + "local block synced {} had equal to remote header {} for blockchain {}", + localBlockHeaderHeight, + remoteBlockHeaderHeight, + getProcessContext().getBlockchainMeta().getMetaKey() + ); + return; + } + + long ccmsgHeight = getNotifyBlockHeaderHeight(NotifyTaskTypeEnum.CROSSCHAIN_MSG_WORKER.getCode()); + if (localBlockHeaderHeight >= ccmsgHeight + getProcessContext().getMaxDiffBetweenSyncAndNotify()) { + log.info( + "local block synced {} had greater than remote block {} plus with maxHeightDiff {} for blockchain {}", + localBlockHeaderHeight, + remoteBlockHeaderHeight, + getProcessContext().getMaxDiffBetweenSyncAndNotify(), + getProcessContext().getBlockchainMeta().getMetaKey() + ); + return; + } + + // each process task will process the gap of local block header and remote header now. + long endHeight = Math.min(remoteBlockHeaderHeight, localBlockHeaderHeight + getProcessContext().getSyncBatchSize()); + long currentHeight = localBlockHeaderHeight + 1; + + while (currentHeight <= endHeight) { + + long syncBatch = (endHeight - currentHeight + 1) >= getProcessContext().getSyncAsyncQuerySize() ? + getProcessContext().getSyncAsyncQuerySize() : (endHeight - currentHeight + 1); + + List blocks = queryRemoteBlock(currentHeight, syncBatch); + + if (blocks.isEmpty()) { + log.error( + "query remote block from {} to {} failed for {}", + currentHeight, + currentHeight + syncBatch, + getProcessContext().getBlockchainMeta().getMetaKey() + ); + break; + } + blocks.forEach( + block -> getProcessContext().getBlockQueue().putBlockIntoQueue(block) + ); + saveLocalBlockHeaderHeight(blocks.get(blocks.size() - 1).getHeight()); + currentHeight = blocks.get(blocks.size() - 1).getHeight() + 1; + } + } + + public List queryRemoteBlock(long height, long size) { + List> blockFutures = new ArrayList<>((int) size); + for (long queryHeight = height; queryHeight < height + size; ++queryHeight) { + long finalQueryHeight = queryHeight; + blockFutures.add( + getProcessContext().getBlockSyncTaskThreadsPool().submit( + () -> getProcessContext().getBlockchainClient().getEssentialHeaderByHeight(finalQueryHeight) + ) + ); + } + return blockFutures.stream().map( + future -> { + try { + return future.get(); + } catch (Exception e) { + throw new RuntimeException("failed to get block from future object: ", e); + } + } + ).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockTaskTypeEnum.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockTaskTypeEnum.java new file mode 100644 index 0000000..1549b58 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/BlockTaskTypeEnum.java @@ -0,0 +1,40 @@ +/* + * 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.relayer.core.service.anchor.tasks; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum BlockTaskTypeEnum { + + POLLING("polling"), + + SYNC("sync"), + + NOTIFY("notify"); + + private final String code; + + public String toNotifyWorkerHeightType(String workerName) { + Assert.equals(this, NOTIFY); + return StrUtil.format("{}_{}", code, workerName); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/CachedBlockQueue.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/CachedBlockQueue.java new file mode 100644 index 0000000..7f4a75f --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/CachedBlockQueue.java @@ -0,0 +1,129 @@ +/* + * 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.relayer.core.service.anchor.tasks; + +import java.util.concurrent.TimeUnit; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.HeterogeneousBlock; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RBucket; +import org.redisson.api.RedissonClient; +import org.redisson.client.codec.ByteArrayCodec; + +@Getter +@Setter +@Slf4j +public class CachedBlockQueue implements IBlockQueue { + + private AnchorProcessContext processContext; + + private final Cache blockCache; + + private final RedissonClient redisson; + + private final int blockCacheTTL; + + private long latestBlockHeightFetched = Long.MAX_VALUE; + +// private final ArrayBlockingQueue blockingQueue; + + public CachedBlockQueue( + RedissonClient redisson, + int blockCacheCapacity, + int blockCacheTTL + ) { + this.blockCache = CacheUtil.newFIFOCache(blockCacheCapacity); + this.redisson = redisson; + this.blockCacheTTL = blockCacheTTL; + } + + public void putBlockIntoQueue(AbstractBlock block) { + putBlockIntoCache(block); + } + + public AbstractBlock getBlockFromQueue(long height) { + if (blockCache.containsKey(getMemCacheKey(height))) { + return blockCache.get(getMemCacheKey(height)); + } + + if (height <= latestBlockHeightFetched) { + // 从redis二级缓存读取 + try { + log.debug("try to get block from redis {}-{}", processContext.getBlockchainMeta().getMetaKey(), height); + RBucket bucket = redisson.getBucket( + getRedisCacheKey(processContext.getAnchorProduct(), processContext.getAnchorBlockchainId(), height), + ByteArrayCodec.INSTANCE + ); + byte[] blockBin = bucket.get(); + if (ObjectUtil.isNotEmpty(blockBin)) { + HeterogeneousBlock heterogeneousBlock = new HeterogeneousBlock(); + heterogeneousBlock.decode(blockBin); + log.info("get block from redis {}-{}", processContext.getBlockchainMeta().getMetaKey(), height); + return heterogeneousBlock; + } + } catch (Exception e) { + log.error( + "failed to read block ( product: {}, blockchain_id: {}, height: {} ) from redis.", + processContext.getAnchorProduct(), processContext.getAnchorBlockchainId(), height, + e + ); + } + } + + AbstractBlock block = processContext.getBlockchainClient().getEssentialHeaderByHeight(height); + putBlockIntoCache(block); + if (height > latestBlockHeightFetched || latestBlockHeightFetched == Long.MAX_VALUE) { + latestBlockHeightFetched = height; + } + + return block; + } + + private void putBlockIntoCache(AbstractBlock block) { + + log.debug("put block {} from blockchain {}-{} into cache", block.getHeight(), block.getProduct(), block.getBlockchainId()); + + String key = getMemCacheKey(block.getHeight()); + blockCache.put(key, block); + + log.info("put block into redis {}-{}-{}", block.getProduct(), block.getBlockchainId(), block.getHeight()); + redisson.getBucket( + getRedisCacheKey(block.getProduct(), block.getBlockchainId(), block.getHeight()), + ByteArrayCodec.INSTANCE + ).setAsync( + block.encode(), + blockCacheTTL, + TimeUnit.MILLISECONDS + ); + } + + private String getMemCacheKey(long blockHeight) { + return Long.toString(blockHeight); + } + + private String getRedisCacheKey(String product, String blockchainId, long blockHeight) { + return StrUtil.format("{}^{}^{}", product, blockchainId, blockHeight); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/IBlockQueue.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/IBlockQueue.java new file mode 100644 index 0000000..beb3f15 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/IBlockQueue.java @@ -0,0 +1,26 @@ +/* + * 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.relayer.core.service.anchor.tasks; + +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; + +public interface IBlockQueue { + + void putBlockIntoQueue(AbstractBlock block); + + AbstractBlock getBlockFromQueue(long height); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/NotifyTaskTypeEnum.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/NotifyTaskTypeEnum.java new file mode 100644 index 0000000..c43e5ba --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/tasks/NotifyTaskTypeEnum.java @@ -0,0 +1,33 @@ +/* + * 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.relayer.core.service.anchor.tasks; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum NotifyTaskTypeEnum { + + CROSSCHAIN_MSG_WORKER("CONTRACT_AM_CLIENT"), + + SYSTEM_WORKER("CONTRACT_SYSTEM"), + + ORACLE_WORKER("CONTRACT_ORACLE"); + + private final String code; +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/AuthMessageWorker.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/AuthMessageWorker.java new file mode 100644 index 0000000..b597c1d --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/AuthMessageWorker.java @@ -0,0 +1,57 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.workers; + +import java.util.List; + +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.HeterogeneousBlock; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * amclient合约worker,负责处理区块里面的am消息,转发am消息给OracleServiceAM消息执行引擎 + */ +@Slf4j +@Getter +public class AuthMessageWorker extends BlockWorker { + + private final ReceiverService receiver; + + public AuthMessageWorker(AnchorProcessContext processContext) { + super(processContext); + this.receiver = processContext.getReceiverService(); + } + + @Override + public boolean process(AbstractBlock block) { + return dealHeteroBlockchain(block); + } + + public boolean dealHeteroBlockchain(AbstractBlock block) { + try { + HeterogeneousBlock heteroBlock = (HeterogeneousBlock) block; + if (heteroBlock.getCrossChainMessages().isEmpty()) { + return true; + } + + List authMessages = heteroBlock.toAuthMsgWrappers(); + if (authMessages.isEmpty()) { + return true; + } + + receiver.receiveAM(authMessages); + } catch (Exception e) { + log.error( + "failed to process block {} from blockchain {}-{}", + block.getHeight(), + block.getProduct(), + block.getBlockchainId(), + e + ); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/BlockWorker.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/BlockWorker.java new file mode 100644 index 0000000..c572bad --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/anchor/workers/BlockWorker.java @@ -0,0 +1,28 @@ +package com.alipay.antchain.bridge.relayer.core.service.anchor.workers; + +import com.alipay.antchain.bridge.relayer.core.service.anchor.context.AnchorProcessContext; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlock; +import lombok.Getter; + +/** + * 区块worker,用于处理同步到的区块。 + *

    + * 设计worker目的是为了扩展anchor的能力,同一个区块里,存在不同的交易需要有不同的流程处理。 + */ +@Getter +public abstract class BlockWorker { + + private final AnchorProcessContext processContext; + + public BlockWorker(AnchorProcessContext processContext) { + this.processContext = processContext; + } + + /** + * 处理区块,返回是否处理成功,如果返回false,外层会一直重复该区块直到处理成功 + * + * @param block + * @return + */ + public abstract boolean process(AbstractBlock block); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/archive/ArchiveService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/archive/ArchiveService.java new file mode 100644 index 0000000..ed2424b --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/archive/ArchiveService.java @@ -0,0 +1,182 @@ +/* + * 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.relayer.core.service.archive; + +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.impl.BlockchainIdleDCache; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +public class ArchiveService { + private static final String SESSION_AM_LOCK = "archive_am_lock_"; + + @Value("${relayer.service.archive.batch_size:64}") + private int archiveBatchSize; + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private BlockchainIdleDCache blockchainIdleDCache; + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource + private TransactionTemplate transactionTemplate; + + @Resource + private RedissonClient redisson; + + /** + * 执行指定区块的分布式调度任务 + * + * @param blockchainProduct + * @param blockchainId + */ + public void process(String blockchainProduct, String blockchainId) { + + log.debug("begin archive {}-{}", blockchainProduct, blockchainId); + + try { + String domain = blockchainManager.getBlockchainDomain(blockchainProduct, blockchainId); + if (StrUtil.isEmpty(domain)) { + log.info("blockchain has no domain cert so skip it this time: {}-{}", blockchainProduct, + blockchainId); + return; + } + + if (blockchainIdleDCache.ifAMArchiveIdle(blockchainProduct, blockchainId)) { + log.info("archive process : blockchain is idle {}-{}.", blockchainProduct, blockchainId); + return; + } + + // 分别捞出待处理流水 + List sdpMsgWrappers = crossChainMessageRepository.peekTxFinishedSDPMessageIds( + blockchainProduct, + blockchainId, + archiveBatchSize + ); + + if (sdpMsgWrappers.isEmpty()) { + blockchainIdleDCache.setLastEmptyAMArchiveTime(blockchainProduct, blockchainId); + log.debug("sdp msgs to archive is empty for {}-{}", blockchainProduct, blockchainId); + return; + } + + log.info("archive msg size {} for {}-{}", blockchainProduct, blockchainId, sdpMsgWrappers.size()); + + transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + + Lock lock = getArchiveSessionLock(domain); + if (!lock.tryLock()) { + log.info("AMCommitter: unable to get the archive lock: {}", domain); + return; + } + try { + List sdpIds = sdpMsgWrappers.stream().map(SDPMsgWrapper::getId).collect(Collectors.toList()); + int updateCount = crossChainMessageRepository.archiveSDPMessages(sdpIds); + if (updateCount != sdpMsgWrappers.size()) { + log.debug("failed to archive sdp msg ids : {}", StrUtil.join(",", sdpIds)); + throw new RuntimeException( + StrUtil.format( + "sdp archive count is not equal to batch size : batch size is {}, update count is {}", + sdpIds.size(), updateCount + ) + ); + } + + updateCount = crossChainMessageRepository.deleteSDPMessages(sdpIds); + if (updateCount != sdpIds.size()) { + log.debug("failed to delete sdp msg ids : {}", StrUtil.join(",", sdpIds)); + throw new RuntimeException( + StrUtil.format( + "sdp archive count is not equal to batch size : batch size is {}, update count is {}", + sdpIds.size(), updateCount + ) + ); + } + + List amIds = sdpMsgWrappers.stream() + .map(SDPMsgWrapper::getAuthMsgWrapper) + .map(AuthMsgWrapper::getAuthMsgId) + .collect(Collectors.toList()); + updateCount = crossChainMessageRepository.archiveAuthMessages(amIds); + if (updateCount != amIds.size()) { + log.debug("failed to archive am msg ids : {}", StrUtil.join(",", amIds)); + throw new RuntimeException( + StrUtil.format( + "am archive count is not equal to batch size : batch size is {}, update count is {}", + amIds.size(), updateCount + ) + ); + } + + updateCount = crossChainMessageRepository.deleteAuthMessages(amIds); + if (updateCount != amIds.size()) { + log.debug("failed to delete am msg ids : {}", StrUtil.join(",", amIds)); + throw new RuntimeException( + StrUtil.format( + "am archive count is not equal to batch size : batch size is {}, update count is {}", + amIds.size(), updateCount + ) + ); + } + } finally { + lock.unlock(); + } + } + } + ); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_ARCHIVE_PRECESS_FAILED, + e, + "failed to process archive task for {}-{}", + blockchainProduct, blockchainId + ); + } + + } + + private Lock getArchiveSessionLock(String domain) { + return redisson.getLock(SESSION_AM_LOCK + domain); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/committer/CommitterService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/committer/CommitterService.java new file mode 100644 index 0000000..b8ad654 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/committer/CommitterService.java @@ -0,0 +1,523 @@ +package com.alipay.antchain.bridge.relayer.core.service.committer; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.locks.Lock; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgPackage; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgCommitResult; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.core.service.anchor.MultiAnchorProcessService; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.ISystemConfigRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.impl.BlockchainIdleDCache; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +public class CommitterService { + + @Resource(name = "committerServiceThreadsPool") + private ExecutorService committerServiceThreadsPool; + + @Resource + private BlockchainIdleDCache blockchainIdleDCache; + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private BlockchainClientPool blockchainClientPool; + + @Resource + private MultiAnchorProcessService multiAnchorProcessService; + + @Resource + private ISystemConfigRepository systemConfigRepository; + + @Resource + private TransactionTemplate transactionTemplate; + + @Value("${relayer.service.committer.ccmsg.batch_size:128}") + private int commitBatchSize; + + @Value("${relayer.service.committer.threads.core_size:32}") + private int committerServiceCoreSize; + + public void process(String blockchainProduct, String blockchainId) { + + if (isBusyBlockchain(blockchainProduct, blockchainId)) { + log.info("blockchain {}-{} are too busy to receive new message", blockchainProduct, blockchainId); + return; + } + + List sdpMsgWrappers = new ArrayList<>(); + + if (this.blockchainIdleDCache.ifAMCommitterIdle(blockchainProduct, blockchainId)) { + log.info("blockchain {}-{} has no messages processed recently, so skip it this committing process", blockchainProduct, blockchainId); + } else { + sdpMsgWrappers = crossChainMessageRepository.peekSDPMessages( + blockchainProduct, + blockchainId, + SDPMsgProcessStateEnum.PENDING, + commitBatchSize + ); + } + + if (sdpMsgWrappers.size() > 0) { + log.info("peek {} sdp msg for blockchain {} from pool", sdpMsgWrappers.size(), blockchainId); + } else { + this.blockchainIdleDCache.setLastEmptyAMSendQueueTime(blockchainProduct, blockchainId); + log.debug("[committer] peek zero sdp msg for blockchain {} from pool", blockchainId); + } + + // keyed by session key(msg.sender:msg.receiver) + Map> sdpMsgsMap = groupSession( + sdpMsgWrappers, + committerServiceCoreSize + ); + + if (sdpMsgsMap.size() > 0) { + log.info("peek {} sdp msg sessions for blockchain {} from pool", sdpMsgsMap.size(), blockchainId); + } else { + log.debug("peek zero sdp msg sessions for blockchain {} from pool", blockchainId); + } + + List futures = new ArrayList<>(); + // 记录当前SDP消息数目,用于性能统计 + for (Map.Entry> entry : sdpMsgsMap.entrySet()) { + futures.add( + committerServiceThreadsPool.submit( + wrapRequestTask( + entry.getKey(), + entry.getValue() + ) + ) + ); + } + + // 等待执行完成 + do { + List checkFutures = Lists.reverse(Lists.newArrayList(futures)); + for (Future future : checkFutures) { + try { + future.get(); + } catch (InterruptedException e) { + log.error("worker interrupted exception for blockchain {}-{}.", blockchainProduct, blockchainId, e); + } catch (ExecutionException e) { + log.error("worker execution fail for blockchain {}-{}.", blockchainProduct, blockchainId, e); + } finally { + if (future.isDone()) { + futures.remove(future); + } + } + } + } while (!futures.isEmpty()); + } + + private boolean isBusyBlockchain(String blockchainProduct, String blockchainId) { + + String pendingLimit = systemConfigRepository.getSystemConfig( + StrUtil.format("{}-{}-{}", "PENDING_LIMIT", blockchainProduct, blockchainId) + ); + + boolean busy = false; + if (!StrUtil.isEmpty(pendingLimit)) { + busy = crossChainMessageRepository.countSDPMessagesByState( + blockchainProduct, + blockchainId, + SDPMsgProcessStateEnum.PENDING + ) >= Integer.parseInt(pendingLimit); + } + + return busy; + } + + private Map> groupSession(List sdpMsgWrappers, int remainingWorkerNum) { + + // keyed by session key(msg.sender:msg.receiver) + Map> sdpMsgsMap = new HashMap<>(); + + for (SDPMsgWrapper msg : sdpMsgWrappers) { + String sessionKey = msg.getSessionKey(); + if (!sdpMsgsMap.containsKey(sessionKey)) { + sdpMsgsMap.put(sessionKey, new ArrayList<>()); + } + sdpMsgsMap.get(sessionKey).add(msg); + } + + // 当前情况下,线程池剩余的线程数 + int leftWorkerNum = remainingWorkerNum - sdpMsgsMap.size(); + + // 如果线程池有资源剩余,就将Unordered类型的消息拿出来,充分利用剩余资源 + if (leftWorkerNum >= 1) { + // 所有的Unordered消息的Map + // - key 使用session key + // - value 是该session的SDP消息 + Map> unorderedMap = new HashMap<>(); + + // 所有的Unordered消息的总数 + int totalSize = 0; + for (Map.Entry> entry : sdpMsgsMap.entrySet()) { + if (StrUtil.startWith(entry.getKey(), SDPMsgWrapper.UNORDERED_SDP_MSG_SESSION)) { + unorderedMap.put(entry.getKey(), entry.getValue()); + totalSize += entry.getValue().size(); + } + } + + // 如果无序消息的总数大于0,就按各个session的消息数目比例,均分掉剩余的线程 + if (unorderedMap.size() > 0) { + // 因为要重新分配后,在add回unorderedMap,所以这里先删除 + unorderedMap.keySet().forEach(sdpMsgsMap::remove); + leftWorkerNum += unorderedMap.size(); + + // sessionNum是后面要用到多少个线程,每个session一个线程 + // 如果没有那么多的消息,就把消息总数作为新分配的session数目 + int sessionNum = Math.min(leftWorkerNum, totalSize); + + // 将原先的session,拆分到一个或者多个新session,将消息均匀分到这些新的session中 + for (Map.Entry> entry : unorderedMap.entrySet()) { + // count 就是该session需要拆分为新session的数目 + // 按消息占总体的比例分配,最小为1 + int count = Math.max(sessionNum * entry.getValue().size() / totalSize, 1); + + // 将消息均分到信息的session中,直接add到p2pMsgsMap + for (int i = 0; i < entry.getValue().size(); i++) { + // 新的session key,利用余数均匀分配消息到新的session中 + String key = String.format("%s-%d", entry.getKey(), i % count); + if (!sdpMsgsMap.containsKey(key)) { + sdpMsgsMap.put(key, Lists.newArrayList()); + } + sdpMsgsMap.get(key).add(entry.getValue().get(i)); + } + } + } + } + + return sdpMsgsMap; + } + + private Runnable wrapRequestTask(String sessionName, List sessionMsgs) { + return () -> { + + Lock sessionLock = crossChainMessageRepository.getSessionLock(sessionName); + sessionLock.lock(); + log.info("get distributed lock for session {}", sessionName); + try { + transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + // 这是个分布式并发任务,加了session锁后,要check下每个p2p消息的最新状态,防止重复处理 + List sessionMsgsUpdate = filterOutdatedMsg(sessionMsgs); + + // p2p按seq排序,后续需要按序提交 + sortSDPMsgList(sessionMsgsUpdate); + + for (SDPMsgWrapper sdpMsgWrapper : sessionMsgsUpdate) { + // 逐笔提交,但包装为数组调用批量更新接口 + log.info("committing msg of id {} for session {}", sdpMsgWrapper.getId(), sessionName); + batchCommitSDPMsg(sessionName, ListUtil.toList(sdpMsgWrapper)); + } + } + } + ); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_COMMITTER_PROCESS_CCMSG_FAILED, + e, + "failed to commit session {} with {} messages", + sessionName, sessionMsgs.size() + ); + } finally { + sessionLock.unlock(); + log.info("release distributed lock for session {}", sessionName); + } + }; + } + + private List filterOutdatedMsg(List sessionMsgs) { + return sessionMsgs.stream().filter( + sdpMsgWrapper -> + crossChainMessageRepository.getSDPMessage(sdpMsgWrapper.getId(), true) + .getProcessState() == SDPMsgProcessStateEnum.PENDING + ).collect(Collectors.toList()); + } + + /** + * 已经上链过的消息直接更新为已上链(可能种种原因,之前上链时候的hash丢失了未更新到db) + * + * @param msgSet + */ + private void updateExpiredMsg(ParsedSDPMsgSet msgSet) { + if (msgSet.getExpired().size() != 0) { + for (SDPMsgWrapper msg : msgSet.getExpired()) { + msg.setProcessState(SDPMsgProcessStateEnum.TX_SUCCESS); + log.info("AMCommitter: am has commit on chain {}", msg.getAuthMsgWrapper().getAuthMsgId()); + if (!crossChainMessageRepository.updateSDPMessage(msg)) { + throw new RuntimeException("database update failed"); + } + } + } + } + + private void batchCommitSDPMsg(String sessionName, List msgs) { + + String receiverProduct = msgs.get(0).getReceiverBlockchainProduct(); + String receiverBlockchainId = msgs.get(0).getReceiverBlockchainId(); + + ParsedSDPMsgSet msgSet = parseSDPMsgList(receiverProduct, receiverBlockchainId, msgs); + + // 处理脏数据 + updateExpiredMsg(msgSet); + + // 处理新数据 + if (msgSet.getUpload().size() == 0) { + return; + } + + log.info("AMCommitter: {} messages should uploaded for session {}", sessionName, msgSet.getUpload().size()); + + // 提交上链 + try { + + msgSet.getUpload().forEach( + sdpMsgWrapper -> sdpMsgWrapper.setAuthMsgWrapper( + crossChainMessageRepository.getAuthMessage(sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId()) + ) + ); + + SDPMsgCommitResult res = commitAmPkg( + msgSet.getUpload().get(0).getReceiverBlockchainProduct(), + msgSet.getUpload().get(0).getReceiverBlockchainId(), + AuthMsgPackage.convertFrom(msgSet.getUpload(), null) + ); + + // Send tx result situations: + // + // - unknown_exception: unknown exception from upper operations + // - tx_sent_failed: failed to send tx, returned with errcode and errmsg + // - tx_success: tx has been sent successfully + // - tx_pending: tx has been sent but pending to execute + // 1. revert error, returned with REVERT_ERROR and errmsg + // 2. other chain error, returned with errcode and errmsg + // 3. success + + if (!res.isCommitSuccess()) { + log.error("AMCommitter: amPkg for session {} commit failed, error msg: {}", sessionName, res.getFailReason()); + throw new RuntimeException("failed to commit msgs"); + } + + for (SDPMsgWrapper msg : msgSet.getUpload()) { + msg.setTxSuccess(res.isCommitSuccess()); + msg.setTxHash(res.getTxHash()); + msg.setTxFailReason(res.getFailReason()); + msg.setProcessState(calculateProcessState(res)); + + if (!crossChainMessageRepository.updateSDPMessage(msg)) { + throw new RuntimeException("database update failed"); + } + } + log.info("AMCommitter: messages for session {} status updated in database", sessionName); + + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_COMMITTER_PROCESS_COMMIT_SDP_FAILED, + e, + "failed to commit msg for session {}", sessionName + ); + } + } + + private SDPMsgProcessStateEnum calculateProcessState(SDPMsgCommitResult res) { + if (res.isConfirmed()) { + return res.isCommitSuccess() ? SDPMsgProcessStateEnum.TX_SUCCESS : SDPMsgProcessStateEnum.TX_FAILED; + } + return SDPMsgProcessStateEnum.TX_PENDING; + } + + public SDPMsgCommitResult commitAmPkg(String receiverProduct, String receiverBlockchainId, AuthMsgPackage pkg) { + SDPMsgCommitResult cr = new SDPMsgCommitResult(); + + try { + AbstractBlockchainClient client = blockchainClientPool.getClient(receiverProduct, receiverBlockchainId); + if (ObjectUtil.isNull(client)) { + throw new RuntimeException( + StrUtil.format( + "No client found for blockchain {}-{} when committing msg. Could be anchor process not start yet or anchor service for this blockchain has been stopped", + receiverProduct, receiverBlockchainId + ) + ); + } + + AbstractBlockchainClient.SendResponseResult res = client.getAMClientContract() + .recvPkgFromRelayer(pkg.encode(), ""); + cr.setTxHash(res.getTxId()); + cr.setConfirmed(res.isConfirmed()); + cr.setCommitSuccess(res.isSuccess()); + cr.setFailReason(res.getErrorMessage()); + + return cr; + + } catch (Exception e) { + log.error("[txCommitter] commitAmPkg error", e); + cr.setFailReason(e.getMessage()); + return cr; + } + } + + public long getSDPMsgSeqOnChain(String product, String blockchainId, SDPMsgWrapper sdpMsgWrapper) { + + try { + return blockchainClientPool.getClient(product, blockchainId) + .getSDPMsgClientContract() + .querySDPMsgSeqOnChain( + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver() + ); + + } catch (Exception e) { + log.error( + "getSDPMsgSeqOnChain failed for ( sender_blockchain: {}-{}, sender: {}, receiver_blockchain: {}-{}, receiver: {} ) : ", + sdpMsgWrapper.getSenderBlockchainProduct(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainProduct(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + e + ); + return 0; + } + } + + @Getter + @AllArgsConstructor + public static class ParsedSDPMsgSet { + + /** + * expired msg shouldn't upload to chain + */ + private final List expired; + + /** + * should upload to chain + */ + private final List upload; + + public ParsedSDPMsgSet() { + this.expired = new ArrayList<>(); + this.upload = new ArrayList<>(); + } + } + + private ParsedSDPMsgSet parseSDPMsgList(String product, String blockchainId, List msgs) { + long seqOnChain = -1; + ParsedSDPMsgSet set = new ParsedSDPMsgSet(); + long lastIndex = seqOnChain; // NOTE: this is the first seq which need to send to blockchain + + for (SDPMsgWrapper msg : msgs) { // precondition: msgs was sorted + + if (SDPMsgWrapper.UNORDERED_SDP_MSG_SEQ == msg.getMsgSequence()) { + set.getUpload().add(msg); + continue; + } + + if (seqOnChain == -1) { + // NOTE: Always use the sequence number of session on blockchain + seqOnChain = getSDPMsgSeqOnChain(product, blockchainId, msgs.get(0)); + lastIndex = seqOnChain; // NOTE: this is the first seq which need to send to blockchain + + log.info( + "session ( sender_blockchain: {}-{}, sender: {}, receiver_blockchain: {}-{}, receiver: {} ) seq on chain is {}", + msg.getSenderBlockchainProduct(), + msg.getSenderBlockchainDomain(), + msg.getMsgSender(), + msg.getReceiverBlockchainProduct(), + msg.getReceiverBlockchainDomain(), + msg.getMsgReceiver(), + seqOnChain + ); + } + + if (msg.getMsgSequence() < seqOnChain) { + log.info( + "ordered sdp msg ( sender_blockchain: {}-{}, sender: {}, receiver_blockchain: {}-{}, receiver: {} ) seq is expired: seq on chain is {} and seq in msg is {}", + msg.getSenderBlockchainProduct(), + msg.getSenderBlockchainDomain(), + msg.getMsgSender(), + msg.getReceiverBlockchainProduct(), + msg.getReceiverBlockchainDomain(), + msg.getMsgReceiver(), + seqOnChain, + msg.getMsgSequence() + ); + set.getExpired().add(msg); + continue; + } + + if (msg.getMsgSequence() == lastIndex) { // collect consecutive msgs + log.info( + "put ordered sdp msg with seq {} into upload list for session ( sender_blockchain: {}-{}, sender: {}, receiver_blockchain: {}-{}, receiver: {} )", + msg.getMsgSequence(), + msg.getSenderBlockchainProduct(), + msg.getSenderBlockchainDomain(), + msg.getMsgSender(), + msg.getReceiverBlockchainProduct(), + msg.getReceiverBlockchainDomain(), + msg.getMsgReceiver() + ); + set.getUpload().add(msg); + lastIndex++; + continue; + } + + log.warn( + "unhandled ordered msg seq {} for session ( sender_blockchain: {}-{}, sender: {}, receiver_blockchain: {}-{}, receiver: {} )", + msg.getMsgSequence(), + msg.getSenderBlockchainProduct(), + msg.getSenderBlockchainDomain(), + msg.getMsgSender(), + msg.getReceiverBlockchainProduct(), + msg.getReceiverBlockchainDomain(), + msg.getMsgReceiver() + ); + } + return set; + } + + private void sortSDPMsgList(List msgs) { + ListUtil.sort( + msgs, + Comparator.comparingInt(SDPMsgWrapper::getMsgSequence) + ); + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/confirm/AMConfirmService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/confirm/AMConfirmService.java new file mode 100644 index 0000000..ed09061 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/confirm/AMConfirmService.java @@ -0,0 +1,95 @@ +package com.alipay.antchain.bridge.relayer.core.service.confirm; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import javax.annotation.Resource; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgCommitResult; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.AbstractBlockchainClient; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.HeteroBlockchainClient; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class AMConfirmService { + + @Value("${relayer.service.confirm.batch_size:64}") + private int confirmBatchSize; + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource(name = "confirmServiceThreadsPool") + private ExecutorService confirmServiceThreadsPool; + + @Resource + private BlockchainClientPool blockchainClientPool; + + public void process(String product, String blockchainId) { + AbstractBlockchainClient client = blockchainClientPool.getClient(product, blockchainId); + if (ObjectUtil.isNull(client)) { + log.info("waiting for hetero-client to start for blockchain {} - {}", product, blockchainId); + return; + } + + HeteroBlockchainClient heteroBlockchainClient = (HeteroBlockchainClient) client; + List sdpMsgWrappers = crossChainMessageRepository.peekSDPMessages( + product, + blockchainId, + SDPMsgProcessStateEnum.TX_PENDING, + confirmBatchSize + ); + if (sdpMsgWrappers.size() == 0) { + return; + } + + List> futureList = new ArrayList<>(); + sdpMsgWrappers.forEach( + sdpMsgWrapper -> futureList.add( + confirmServiceThreadsPool.submit( + () -> heteroBlockchainClient.queryCommittedTxReceipt(sdpMsgWrapper.getTxHash()) + ) + ) + ); + + List commitResults = new ArrayList<>(); + futureList.forEach( + future -> { + CrossChainMessageReceipt receipt; + try { + receipt = future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException( + String.format("failed to query cross-chain receipt for ( product: %s, bid: %s )", product, blockchainId), + e + ); + } + if (receipt.isConfirmed()) { + commitResults.add( + new SDPMsgCommitResult( + product, + blockchainId, + receipt.getTxhash(), + receipt.isSuccessful(), + receipt.getErrorMsg(), + System.currentTimeMillis() + ) + ); + } + } + ); + + crossChainMessageRepository.updateSDPMessageResults(commitResults); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/DeployService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/DeployService.java new file mode 100644 index 0000000..2ed3e11 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/DeployService.java @@ -0,0 +1,91 @@ +/* + * 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.relayer.core.service.deploy; + +import java.util.concurrent.locks.Lock; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.core.service.deploy.task.AbstractAsyncTaskExecutor; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +public class DeployService { + + public static String ROW_LOCK_NAME = "DEPLOY_SERVICE_TASK_LOCK_"; + + @Resource + private IBlockchainManager blockchainManager; + + @Resource(name = "amServiceAsyncTaskExecutor") + private AbstractAsyncTaskExecutor amServiceAsyncTaskExecutor; + + @Resource + private TransactionTemplate transactionTemplate; + + @Resource + private RedissonClient redisson; + + public void process(String product, String blockchainId){ + BlockchainMeta blockchainMeta = blockchainManager.getBlockchainMeta(product, blockchainId); + + // 判断是否有am服务部署任务需要执行 + if (amServiceAsyncTaskExecutor.preCheck(blockchainMeta)) { + try { + log.info("do deploy am service task {}-{}", blockchainMeta.getProduct(), blockchainMeta.getBlockchainId()); + processDeployAMService(blockchainMeta); + } catch (Exception e) { + log.error("processDeployAMService failed for blockchain {}", blockchainMeta.getMetaKey(), e); + } + } + } + + public void processDeployAMService(BlockchainMeta blockchainMeta) { + Lock lock = getDeployServiceLock(blockchainMeta.getProduct(), blockchainMeta.getBlockchainId()); + if (!lock.tryLock()) { + log.info("deploy service get lock failed: {}-{} ", blockchainMeta.getProduct(), blockchainMeta.getBlockchainId()); + return; + } + try { + // 真正执行任务时,加排它锁,确保不会有多并发线程安全问题 + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { + // 执行任务 + amServiceAsyncTaskExecutor.doAsyncTask(blockchainMeta); + } + }); + } finally { + lock.unlock(); + } + } + + private String getRowLockName(String product, String blockchainId) { + return ROW_LOCK_NAME + product + "_" + blockchainId; + } + + private Lock getDeployServiceLock(String product, String blockchainId) { + return redisson.getLock(getRowLockName(product, blockchainId)); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AMServiceAsyncTaskExecutor.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AMServiceAsyncTaskExecutor.java new file mode 100644 index 0000000..0f42341 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AMServiceAsyncTaskExecutor.java @@ -0,0 +1,66 @@ +package com.alipay.antchain.bridge.relayer.core.service.deploy.task; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.OnChainServiceStatusEnum; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component("amServiceAsyncTaskExecutor") +@Slf4j +public class AMServiceAsyncTaskExecutor extends AbstractAsyncTaskExecutor { + + @Override + public boolean preCheck(BlockchainMeta blockchainMeta) { + if (ObjectUtil.isNull(blockchainMeta)) { + return false; + } + if ( + !blockchainMeta.isRunning() + || ObjectUtil.isNull(blockchainMeta.getProperties().getAmServiceStatus()) + ) { + return false; + } + + // 如果状态是am合约已部署,不需要执行 + // 其他状态需要继续推进 + return OnChainServiceStatusEnum.DEPLOY_FINISHED != blockchainMeta.getProperties().getAmServiceStatus(); + } + + @Override + public boolean doAsyncTask(BlockchainMeta blockchainMeta) { + + // 如果是init状态,则部署合约 + if (OnChainServiceStatusEnum.INIT == blockchainMeta.getProperties().getAmServiceStatus()) { + return processInitStatus(blockchainMeta); + } + + return false; + } + + /** + * [部署am合约] --> [保存am合约地址] --> [修改状态为finish_deploy_am_contract] --> [落库] + */ + public boolean processInitStatus(BlockchainMeta blockchainMeta) { + + log.info("processInitStatus {}-{}", blockchainMeta.getProduct(), blockchainMeta.getBlockchainId()); + try { + getBlockchainManager().deployAMClientContract( + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ); + + blockchainMeta.getProperties().setAmServiceStatus(OnChainServiceStatusEnum.DEPLOY_FINISHED); + getBlockchainManager().updateBlockchainProperty( + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId(), + BlockchainMeta.BlockchainProperties.AM_SERVICE_STATUS, + blockchainMeta.getProperties().getAmServiceStatus().name() + ); + } catch (Exception e) { + log.error("failed to deploy AuthMessage contract for {}", blockchainMeta.getMetaKey(), e); + return false; + } + return true; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AbstractAsyncTaskExecutor.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AbstractAsyncTaskExecutor.java new file mode 100644 index 0000000..ba5599e --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/deploy/task/AbstractAsyncTaskExecutor.java @@ -0,0 +1,30 @@ +package com.alipay.antchain.bridge.relayer.core.service.deploy.task; + +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import lombok.Getter; + +@Getter +public abstract class AbstractAsyncTaskExecutor { + + @Resource + private IBlockchainManager blockchainManager; + + /** + * 预检查该状态是否需要继续推进 + * + * @param blockchainMeta@return + */ + public abstract boolean preCheck(BlockchainMeta blockchainMeta); + + /** + * 状态推进 + * + * @param blockchainMeta + * @return + */ + public abstract boolean doAsyncTask(BlockchainMeta blockchainMeta); +} + diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/AuthenticMessageProcess.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/AuthenticMessageProcess.java new file mode 100644 index 0000000..ad09fa7 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/AuthenticMessageProcess.java @@ -0,0 +1,362 @@ +package com.alipay.antchain.bridge.relayer.core.service.process; + +import java.util.Base64; +import javax.annotation.Resource; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.commons.core.sdp.AbstractSDPMessage; +import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageFactory; +import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.RelayerNodeSyncStateEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.UpperProtocolTypeBeyondAMEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.*; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.core.manager.gov.IGovernManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.types.network.IRelayerClientPool; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.impl.BlockchainIdleDCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * AM消息处理器 + *

    + * 根据不同的上层AM协议,处理流程不一样 + *

    + * 如果是MSG消息,则需要将取UDAG数据后,将消息拆分为一条发送消息落库 + */ +@Component +@Slf4j +public class AuthenticMessageProcess { + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private IGovernManager governManager; + + @Resource + private IRelayerNetworkManager relayerNetworkManager; + + @Resource + private BlockchainIdleDCache blockchainIdleDCache; + + @Resource + private IRelayerClientPool relayerClientPool; + + @Value("${relayer.service.process.sdp.acl_on:true}") + private boolean sdpACLOn; + + public boolean doProcess(AuthMsgWrapper amMsgWrapper) { + + log.info( + "process auth msg : (src_domain: {}, id: {}, if_remote: {})", + amMsgWrapper.getDomain(), + amMsgWrapper.getAuthMsgId(), + amMsgWrapper.isNetworkAM() + ); + + if ( + amMsgWrapper.getProcessState() == AuthMsgProcessStateEnum.PROVED + || amMsgWrapper.getProcessState() == AuthMsgProcessStateEnum.REJECTED + ) { + log.error("auth msg repeat process : {}", amMsgWrapper.getAuthMsgId()); + return true; + } else if (amMsgWrapper.getProcessState() != AuthMsgProcessStateEnum.PENDING) { + log.error("auth msg process state error : {}-{}", amMsgWrapper.getAuthMsgId(), amMsgWrapper.getProcessState()); + return false; + } + + try { + if (!amMsgWrapper.isNetworkAM()) { + processLocalAM(amMsgWrapper); + } else { + processRemoteAM(amMsgWrapper); + } + + // 只有am_proof状态,需要解析协议栈处理 + if (amMsgWrapper.getProcessState() == AuthMsgProcessStateEnum.PROVED) { + log.info( + "process high layer protocol {} of am message : (src_domain: {}, id: {}, if_remote: {})", + UpperProtocolTypeBeyondAMEnum.parseFromValue(amMsgWrapper.getAuthMessage().getUpperProtocol()).name(), + amMsgWrapper.getDomain(), + amMsgWrapper.getAuthMsgId(), + amMsgWrapper.isNetworkAM() + ); + // 处理上层协议 + if (amMsgWrapper.getAuthMessage().getUpperProtocol() == UpperProtocolTypeBeyondAMEnum.SDP.getCode()) { + processSDPMsg(amMsgWrapper); + } else { + throw new RuntimeException("unsupported am upper protocol type: " + amMsgWrapper.getProtocolType()); + } + } + + return crossChainMessageRepository.updateAuthMessage(amMsgWrapper); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_CORE_PROCESS_AUTH_MSG_PROCESS_FAILED, + e, + "process auth msg : (src_domain: {}, id: {}, if_remote: {})", + amMsgWrapper.getDomain(), + amMsgWrapper.getAuthMsgId(), + amMsgWrapper.isNetworkAM() + ); + } + + } + + private void processLocalAM(AuthMsgWrapper authMsgWrapper) { + + // 填充区块链信息 + if (!StrUtil.isAllNotEmpty(authMsgWrapper.getProduct(), authMsgWrapper.getBlockchainId())) { + DomainCertWrapper domainCertWrapper = blockchainManager.getDomainCert(authMsgWrapper.getDomain()); + if (ObjectUtil.isNull(domainCertWrapper)) { + throw new RuntimeException("domain cert not exist: " + authMsgWrapper.getDomain()); + } + + authMsgWrapper.setProduct(domainCertWrapper.getBlockchainProduct()); + authMsgWrapper.setBlockchainId(domainCertWrapper.getBlockchainId()); + } + + if (StrUtil.isEmpty(authMsgWrapper.getAmClientContractAddress())) { + // 填充合约信息 + BlockchainMeta blockchainMeta = blockchainManager.getBlockchainMeta( + authMsgWrapper.getProduct(), + authMsgWrapper.getBlockchainId() + ); + authMsgWrapper.setAmClientContractAddress( + blockchainMeta.getProperties().getAmClientContractAddress() + ); + } + + authMsgWrapper.setProcessState(AuthMsgProcessStateEnum.PROVED); + } + + private void processRemoteAM(AuthMsgWrapper authMsgWrapper) { + + authMsgWrapper.setProcessState(AuthMsgProcessStateEnum.PROVED); + } + + private void processSDPMsg(AuthMsgWrapper authMsgWrapper) { + + SDPMsgWrapper sdpMsgWrapper = parseSDPMsgFrom(authMsgWrapper); + + // 接收者在本地 + if (StrUtil.isNotEmpty(sdpMsgWrapper.getReceiverBlockchainId())) { + this.blockchainIdleDCache.setLastAMProcessTime( + sdpMsgWrapper.getReceiverBlockchainProduct(), + sdpMsgWrapper.getReceiverBlockchainId() + ); + processLocalP2PMsg(sdpMsgWrapper); + return; + } + + processRemoteP2PMsg(sdpMsgWrapper); + } + + public SDPMsgWrapper parseSDPMsgFrom(AuthMsgWrapper authMsgWrapper) { + + SDPMsgWrapper sdpMsgWrapper = new SDPMsgWrapper(); + + sdpMsgWrapper.setSdpMessage( + (AbstractSDPMessage) SDPMessageFactory.createSDPMessage( + authMsgWrapper.getAuthMessage().getPayload() + ) + ); + + sdpMsgWrapper.setAuthMsgWrapper(authMsgWrapper); + + if (StrUtil.isEmpty(sdpMsgWrapper.getReceiverBlockchainDomain())) { + log.error( + "receiver domain is empty from am (src_domain: {}, id: {}, if_remote: {})", + authMsgWrapper.getDomain(), + authMsgWrapper.getAuthMsgId(), + authMsgWrapper.isNetworkAM() + ); + sdpMsgWrapper.setProcessState(SDPMsgProcessStateEnum.MSG_ILLEGAL); + sdpMsgWrapper.setTxFailReason("Empty receiver domain"); + return sdpMsgWrapper; + } + + if (blockchainManager.hasBlockchain(sdpMsgWrapper.getReceiverBlockchainDomain())) { + BlockchainMeta receiverBlockchainMeta = blockchainManager.getBlockchainMetaByDomain( + sdpMsgWrapper.getReceiverBlockchainDomain() + ); + if (ObjectUtil.isNull(receiverBlockchainMeta)) { + log.error( + "receiver blockchain not exist for domain {} from am (src_domain: {}, id: {}, if_remote: {})", + sdpMsgWrapper.getReceiverBlockchainDomain(), + authMsgWrapper.getDomain(), + authMsgWrapper.getAuthMsgId(), + authMsgWrapper.isNetworkAM() + ); + sdpMsgWrapper.setProcessState(SDPMsgProcessStateEnum.MSG_ILLEGAL); + sdpMsgWrapper.setTxFailReason("Blockchain supposed existed but not"); + return sdpMsgWrapper; + } + sdpMsgWrapper.setReceiverBlockchainId(receiverBlockchainMeta.getBlockchainId()); + sdpMsgWrapper.setReceiverBlockchainProduct(receiverBlockchainMeta.getProduct()); + sdpMsgWrapper.setReceiverAMClientContract(receiverBlockchainMeta.getProperties().getAmClientContractAddress()); + } + + sdpMsgWrapper.setProcessState(SDPMsgProcessStateEnum.PENDING); + + log.info( + "parse auth msg to sdp msg : ( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} )", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + authMsgWrapper.getAuthMsgId() + ); + + return sdpMsgWrapper; + + } + + private String findRemoteRelayer(SDPMsgWrapper sdpMsgWrapper) { + RelayerNetwork.Item relayerNetworkItem = relayerNetworkManager.findNetworkItemByDomainName(sdpMsgWrapper.getReceiverBlockchainDomain()); + // Network也没有该domain,就拒绝请求 + if (ObjectUtil.isNull(relayerNetworkItem)) { + log.info("can't find receiver domain {} in all network from local data", sdpMsgWrapper.getReceiverBlockchainDomain()); + return null; + } + if (relayerNetworkItem.getSyncState() != RelayerNodeSyncStateEnum.SYNC) { + log.warn("receiver domain {} router existed but not on state SYNC.", sdpMsgWrapper.getReceiverBlockchainDomain()); + return null; + } + return relayerNetworkItem.getNodeId(); + } + + private void processRemoteP2PMsg(SDPMsgWrapper sdpMsgWrapper) { + String relayerNodeId = findRemoteRelayer(sdpMsgWrapper); + try { + if (ObjectUtil.isNull(relayerNodeId)) { + // TODO: call BCDNS for domain router + } + + if (ObjectUtil.isNull(relayerNodeId)) { + // Even try BCDNS but not found + sdpMsgWrapper.setProcessState(SDPMsgProcessStateEnum.MSG_REJECTED); + sdpMsgWrapper.setTxFailReason("Unknown receiver domain"); + + crossChainMessageRepository.putSDPMessage(sdpMsgWrapper); + } + + relayerClientPool.getRelayerClient(relayerNetworkManager.getRelayerNode(relayerNodeId)) + .amRequest( + sdpMsgWrapper.getSenderBlockchainDomain(), + Base64.getEncoder().encodeToString(sdpMsgWrapper.getAuthMsgWrapper().getAuthMessage().encode()), + "", + new String(sdpMsgWrapper.getAuthMsgWrapper().getRawLedgerInfo()) + ); + } catch (Exception e) { + log.error( + "failed to send message " + + "( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} ) " + + "to remote relayer {}", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId(), + relayerNodeId, + e + ); + return; + } + + log.info( + "successful to send message " + + "( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} ) " + + "to remote relayer {}", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId(), + relayerNodeId + ); + } + + private void processLocalP2PMsg(SDPMsgWrapper sdpMsgWrapper) { + switch (sdpMsgWrapper.getProcessState()) { + case PENDING: + // 检查ACL规则,若规则不满足,则状态置为am_msg_rejected + checkCrossChainACLRule(sdpMsgWrapper); + if (sdpMsgWrapper.getProcessState() == SDPMsgProcessStateEnum.MSG_REJECTED) { + log.warn( + "sdp msg ( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} ) is rejected by ACL", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId() + ); + } + break; + case MSG_ILLEGAL: + log.error( + "process illegal sdp msg ( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} ) on receiving locally", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId() + ); + break; + } + + crossChainMessageRepository.putSDPMessage(sdpMsgWrapper); + log.info( + "successful to process sdp msg ( version: {}, from_blockchain: {}, sender: {}, receiver_blockchain: {}, receiver: {}, seq: {}, am_id: {} ) locally", + sdpMsgWrapper.getVersion(), + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver(), + sdpMsgWrapper.getMsgSequence(), + sdpMsgWrapper.getAuthMsgWrapper().getAuthMsgId() + ); + } + + private void checkCrossChainACLRule(SDPMsgWrapper sdpMsgWrapper) { + + if (!sdpACLOn || sdpMsgWrapper.isBlockchainSelfCall()) { + return; + } + + if ( + !governManager.verifyCrossChainMsgACL( + sdpMsgWrapper.getSenderBlockchainDomain(), + sdpMsgWrapper.getMsgSender(), + sdpMsgWrapper.getReceiverBlockchainDomain(), + sdpMsgWrapper.getMsgReceiver() + ) + ) { + sdpMsgWrapper.setProcessState(SDPMsgProcessStateEnum.MSG_REJECTED); + sdpMsgWrapper.setTxFailReason("msg rejected by ACL"); + } + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/ProcessService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/ProcessService.java new file mode 100644 index 0000000..d44ff4f --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/process/ProcessService.java @@ -0,0 +1,143 @@ +package com.alipay.antchain.bridge.relayer.core.service.process; + +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import com.alipay.antchain.bridge.relayer.dal.repository.impl.BlockchainIdleDCache; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +@Getter +public class ProcessService { + + @Resource + private AuthenticMessageProcess authenticMessageProcess; + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private BlockchainIdleDCache blockchainIdleDCache; + + @Resource + private TransactionTemplate transactionTemplate; + + @Resource(name = "processServiceThreadsPool") + private ExecutorService processServiceThreadsPool; + + @Value("${relayer.service.process.ccmsg.batch_size:64}") + private int ccmsgBatchSize; + + /** + * 执行指定区块的分布式调度任务 + * + * @param blockchainProduct + * @param blockchainId + */ + public void process(String blockchainProduct, String blockchainId) { + + log.debug("process service run with blockchain {}", blockchainId); + + List authMsgWrapperList = ListUtil.toList(); + + String domainName = blockchainManager.getBlockchainDomain(blockchainProduct, blockchainId); + + if (this.blockchainIdleDCache.ifAMProcessIdle(blockchainProduct, blockchainId)) { + log.info("am process : blockchain is idle {}-{}.", blockchainProduct, blockchainId); + } else if (StrUtil.isNotEmpty(domainName)) { + authMsgWrapperList = crossChainMessageRepository.peekAuthMessages( + domainName, + AuthMsgProcessStateEnum.PENDING, + ccmsgBatchSize + ); + } + + if (authMsgWrapperList.size() > 0) { + log.info("peek {} auth msg from pool: {}-{}", authMsgWrapperList.size(), blockchainProduct, blockchainId); + } else { + this.blockchainIdleDCache.setLastEmptyAMPoolTime(blockchainProduct, blockchainId); + log.debug("{}-{} for auth msg is idle", blockchainProduct, blockchainId); + } + + // 使用线程池并发执行 + List futures = authMsgWrapperList.stream().map( + authMsgWrapper -> processServiceThreadsPool.submit( + wrapAMTask(authMsgWrapper.getAuthMsgId()) + ) + ).collect(Collectors.toList()); + + // 等待执行完成 + do { + for (Future future : ListUtil.reverse(ListUtil.toList(futures))) { + try { + future.get(30 * 1000L, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("worker interrupted exception for blockchain {}-{}.", blockchainProduct, blockchainId, e); + } catch (ExecutionException e) { + log.error("worker execution fail for blockchain {}-{}.", blockchainProduct, blockchainId, e); + } catch (TimeoutException e) { + log.warn("worker query timeout exception for blockchain {}-{}.", blockchainProduct, blockchainId, e); + } finally { + if (future.isDone()) { + futures.remove(future); + } + } + } + } while (!futures.isEmpty()); + } + + private Runnable wrapAMTask(long amId) { + return () -> transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + + AuthMsgWrapper am = crossChainMessageRepository.getAuthMessage(amId, true); + if (ObjectUtil.isNull(am)) { + log.error("none auth message found for auth id {}", amId); + return; + } + + try { + if (!authenticMessageProcess.doProcess(am)) { + throw new RuntimeException( + StrUtil.format("failed to process auth message for auth id {} for unknown reason", amId) + ); + } + + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_CORE_PROCESS_PROCESS_CCMSG_FAILED, + e, + "failed to process auth message for auth id {}", + amId + ); + } + } + } + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/ReceiverService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/ReceiverService.java new file mode 100644 index 0000000..2a68404 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/ReceiverService.java @@ -0,0 +1,104 @@ +package com.alipay.antchain.bridge.relayer.core.service.receiver; + +import java.util.Base64; +import java.util.List; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.commons.core.am.AuthMessageFactory; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgCommitResult; +import com.alipay.antchain.bridge.relayer.core.service.receiver.handler.AsyncReceiveHandler; +import com.alipay.antchain.bridge.relayer.core.service.receiver.handler.SyncReceiveHandler; +import com.alipay.antchain.bridge.relayer.dal.repository.impl.BlockchainIdleDCache; +import org.springframework.stereotype.Service; + +/** + * OracleService核心引擎的接收者,用于接收来自链上、链外的服务请求。 + *

    + * 该引擎设置有同步处理器、异步处理器,根据需求选择。 + */ +@Service +public class ReceiverService { + + /** + * 同步receiver处理器 + */ + @Resource + private SyncReceiveHandler syncReceiveHandler; + + /** + * 异步receiver处理器 + */ + @Resource + private AsyncReceiveHandler asyncReceiveHandler; + + @Resource + private BlockchainIdleDCache blockchainIdleDCache; + + /** + * 链外请求receive接口 + * + * @param authMsg + * @param authMsg + * @return + */ + public void receiveOffChainAMRequest(String domainName, String authMsg, String udagProof, String ledgerInfo) { + + AuthMsgWrapper authMsgWrapper = new AuthMsgWrapper(); + authMsgWrapper.setDomain(domainName); + authMsgWrapper.setLedgerInfo(ledgerInfo); + authMsgWrapper.setAuthMessage( + AuthMessageFactory.createAuthMessage( + Base64.getDecoder().decode(authMsg) + ) + ); + + try { + syncReceiveHandler.receiveOffChainAMRequest(authMsgWrapper, udagProof); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_REMOTE_AM_PROCESS_FAILED, + e, + "failed to process remote am request from blockchain {}", + domainName + ); + } + } + + /** + * 接收am消息的接口 + * + * @param authMsgWrappers + * @return + */ + public void receiveAM(List authMsgWrappers) { + asyncReceiveHandler.receiveAuthMessages(authMsgWrappers); + if (!authMsgWrappers.isEmpty()) { + blockchainIdleDCache.setLastAMReceiveTime( + authMsgWrappers.get(0).getProduct(), + authMsgWrappers.get(0).getBlockchainId() + ); + } + } + + /** + * receive am client eceipt接口 + * + * @param commitResults + * @return + */ + public boolean receiveAMClientReceipts(List commitResults) { + + if (!commitResults.isEmpty()) { + blockchainIdleDCache.setLastAMResponseTime( + commitResults.get(0).getReceiveProduct(), + commitResults.get(0).getReceiveBlockchainId() + ); + } + return asyncReceiveHandler.receiveAMClientReceipt(commitResults); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/AsyncReceiveHandler.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/AsyncReceiveHandler.java new file mode 100644 index 0000000..b21329e --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/AsyncReceiveHandler.java @@ -0,0 +1,83 @@ +package com.alipay.antchain.bridge.relayer.core.service.receiver.handler; + +import java.util.List; +import javax.annotation.Resource; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgProcessStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.SDPMsgCommitResult; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionTemplate; + +@Component +@Slf4j +public class AsyncReceiveHandler { + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + @Resource + private TransactionTemplate transactionTemplate; + + public void receiveAuthMessages(List authMsgWrappers) { + + for (AuthMsgWrapper am : authMsgWrappers) { + log.info("receive a AuthenticMessage from {} with upper protocol {}", am.getDomain(), am.getProtocolType()); + am.setProcessState(AuthMsgProcessStateEnum.PENDING); + } + + int rowsNum = crossChainMessageRepository.putAuthMessages(authMsgWrappers); + if (authMsgWrappers.size() != rowsNum) { + throw new RuntimeException( + StrUtil.format( + "failed to save auth messages: rows number {} inserted not equal to list size {}", + rowsNum, authMsgWrappers.size() + ) + ); + } + log.info("[asyncReceiver] put am_pending AuthenticMessage to pool success"); + } + + public boolean receiveAMClientReceipt(List commitResults) { + + // 处理空值 + if (commitResults.isEmpty()) { + return true; + } + + List results = transactionTemplate.execute( + status -> crossChainMessageRepository.updateSDPMessageResults(commitResults) + ); + + if (ObjectUtil.isEmpty(results)) { + return false; + } + + for (int i = 0; i < results.size(); i++) { + if (0 == results.get(i)) { + // sql变更行数为0,表示tx hash在DB不存在,可能有多种原因导致,可以跳过,打个warn + log.warn( + "sdp msg to blockchain {} processed failed: ( tx: {}, fail_reason: {} )", + commitResults.get(i).getReceiveBlockchainId(), + commitResults.get(i).getTxHash(), + commitResults.get(i).getFailReason() + ); + } else { + log.info( + "sdp msg to blockchain {}-{} processed success: ( tx: {}, committed: {}, confirm: {}, )", + commitResults.get(i).getReceiveProduct(), + commitResults.get(i).getReceiveBlockchainId(), + commitResults.get(i).getTxHash(), + commitResults.get(i).isCommitSuccess(), + commitResults.get(i).isConfirmed() + ); + } + } + + return true; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/SyncReceiveHandler.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/SyncReceiveHandler.java new file mode 100644 index 0000000..c370ed2 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/service/receiver/handler/SyncReceiveHandler.java @@ -0,0 +1,65 @@ +package com.alipay.antchain.bridge.relayer.core.service.receiver.handler; + +import javax.annotation.Resource; + +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.core.service.process.ProcessService; +import com.alipay.antchain.bridge.relayer.dal.repository.ICrossChainMessageRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Component +@Slf4j +public class SyncReceiveHandler { + + @Resource + private ProcessService processService; + + @Resource + private TransactionTemplate transactionTemplate; + + @Resource + private ICrossChainMessageRepository crossChainMessageRepository; + + public void receiveOffChainAMRequest(AuthMsgWrapper authMsg, String ptcProof) { + + log.info("receive a off-chain am request from blockchain {}", authMsg.getDomain()); + + authMsg.setNetworkAM(true); + + try { + transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + authMsg.setAuthMsgId( + crossChainMessageRepository.putAuthMessageWithIdReturned(authMsg) + ); + if ( + !processService.getAuthenticMessageProcess().doProcess(authMsg) + ) { + throw new RuntimeException(StrUtil.format("process off-chain am request from blockchain {} failed", authMsg.getDomain())); + } + log.info("process off-chain am request from blockchain {} success", authMsg.getDomain()); + } + } + ); + } catch (AntChainBridgeRelayerException e) { + throw e; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.SERVICE_MULTI_ANCHOR_PROCESS_REMOTE_AM_PROCESS_FAILED, + e, + "failed to process remote am request from blockchain {}", + authMsg.getDomain() + ); + } + } + +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlock.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlock.java new file mode 100644 index 0000000..d26d9cb --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlock.java @@ -0,0 +1,27 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public abstract class AbstractBlock { + + @JSONField + private String product; + + @JSONField + private String blockchainId; + + @JSONField + private long height; + + public abstract byte[] encode(); + + public abstract void decode(byte[] data); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockHeader.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockHeader.java new file mode 100644 index 0000000..c1f80ed --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockHeader.java @@ -0,0 +1,23 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AbstractBlockHeader { + + private String product; + + private String blockchainId; + + private long height; + + private byte[] blockHeader; + + private byte[] ext; +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java new file mode 100644 index 0000000..4b39c9d --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractBlockchainClient.java @@ -0,0 +1,67 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +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.ISDPMsgClientContract; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public abstract class AbstractBlockchainClient { + + private BlockchainMeta blockchainMeta; + + private String domain; + + public abstract boolean start(); + + public abstract boolean shutdown(); + + public abstract boolean ifHasDeployedAMClientContract(); + + public abstract long getLastBlockHeight(); + + public abstract AbstractBlock getEssentialHeaderByHeight(long height); + + public abstract IAMClientContract getAMClientContract(); + + public abstract ISDPMsgClientContract getSDPMsgClientContract(); + + public abstract CrossChainMessageReceipt queryCommittedTxReceipt(String txhash); + + public abstract AbstractBBCContext queryBBCContext(); + + @Getter + @Setter + public static class SendResponseResult { + private String txId; + private boolean confirmed; + private boolean success; + private String errorCode; + private String errorMessage; + + public SendResponseResult(String txId, boolean success, String errorCode, String errorMessage) { + this.txId = txId; + this.success = success; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + public SendResponseResult(String txId, boolean confirmed, boolean success, String errorCode, String errorMessage) { + this.txId = txId; + this.confirmed = confirmed; + this.success = success; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + public SendResponseResult(String txId, boolean success, int errorCode, String errorMessage) { + this(txId, success, String.valueOf(errorCode), errorMessage); + } + } +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractTransactionWithProof.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractTransactionWithProof.java new file mode 100644 index 0000000..dbd5186 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/AbstractTransactionWithProof.java @@ -0,0 +1,25 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AbstractTransactionWithProof { + + private String product; + + private String blockchainId; + + private long blockHeight; + + private String txHash; + + private byte[] tx; + + private byte[] ext; +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainAnchorProcess.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainAnchorProcess.java new file mode 100644 index 0000000..db0179d --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainAnchorProcess.java @@ -0,0 +1,85 @@ +/* + * 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.relayer.core.types.blockchain; + +import java.util.Date; +import java.util.Map; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import com.alipay.antchain.bridge.relayer.commons.model.AnchorProcessHeights; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BlockchainAnchorProcess { + + private static final String ANCHOR_PROCESS_LATEST_HEIGHT = "polling"; + + private static final String ANCHOR_PROCESS_SPV_HEIGHT = "notify_CONTRACT_SYSTEM"; + + private static final String ANCHOR_PROCESS_DATA_REQ_HEIGHT = "notify_CONTRACT_ORACLE"; + + private static final String ANCHOR_PROCESS_MSG_HEIGHT = "notify_CONTRACT_AM_CLIENT"; + + public static BlockchainAnchorProcess convertFrom(AnchorProcessHeights heights) { + BlockchainAnchorProcess process = new BlockchainAnchorProcess(); + + for (Map.Entry entry : heights.getProcessHeights().entrySet()) { + TaskBlockHeight taskBlockHeight = new TaskBlockHeight( + entry.getValue(), + DateUtil.format( + new Date(heights.getModifiedTimeMap().get(entry.getKey())), + DatePattern.NORM_DATETIME_PATTERN + ) + ); + switch (entry.getKey()) { + case ANCHOR_PROCESS_LATEST_HEIGHT: + process.setLatestBlockHeight(taskBlockHeight); + break; + case ANCHOR_PROCESS_SPV_HEIGHT: + process.setSpvTaskBlockHeight(taskBlockHeight); + break; + case ANCHOR_PROCESS_DATA_REQ_HEIGHT: + process.setDataReqTaskBlockHeight(taskBlockHeight); + break; + case ANCHOR_PROCESS_MSG_HEIGHT: + process.setCrosschainTaskBlockHeight(taskBlockHeight); + break; + } + } + return process; + } + + @Getter + @Setter + @AllArgsConstructor + public static class TaskBlockHeight { + private long height; + private String gmtModified; + } + + private TaskBlockHeight latestBlockHeight; + + private TaskBlockHeight spvTaskBlockHeight; + + private TaskBlockHeight dataReqTaskBlockHeight; + + private TaskBlockHeight crosschainTaskBlockHeight; +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainClientPool.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainClientPool.java new file mode 100644 index 0000000..5306b9f --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/BlockchainClientPool.java @@ -0,0 +1,126 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import java.util.List; +import java.util.Map; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.IBBCPluginManager; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; +import lombok.Getter; +import lombok.Synchronized; +import org.springframework.stereotype.Component; + +@Component +@Getter +public class BlockchainClientPool { + + @Resource + private IBlockchainRepository blockchainRepository; + + @Resource + private IBBCPluginManager bbcPluginManager; + + private final Map clients = MapUtil.newConcurrentHashMap(); + + public AbstractBlockchainClient createClient(String blockchainProduct, String blockchainId) { + + String key = blockchainProduct + "_" + blockchainId; + + if (clients.containsKey(key)) { + return clients.get(key); + } + + BlockchainMeta blockchainMeta = blockchainRepository.getBlockchainMeta(blockchainProduct, blockchainId); + if (ObjectUtil.isNull(blockchainMeta)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_ERROR, + "none meta found for blockchain {}-{}", + blockchainProduct, blockchainId + ); + } + + return createClient(blockchainMeta); + } + + @Synchronized + public AbstractBlockchainClient createClient(BlockchainMeta chainMeta) { + String clientKey = chainMeta.getMetaKey(); + if (clients.containsKey(clientKey)) { + AbstractBlockchainClient client = clients.get(clientKey); + client.setBlockchainMeta(chainMeta); + return client; + } + + String domain = blockchainRepository.getBlockchainDomain( + chainMeta.getProduct(), + chainMeta.getBlockchainId() + ); + if (StrUtil.isEmpty(domain)) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_CLIENT_INIT_ERROR, + "none domain found for {}", + clientKey + ); + } + + IBBCServiceClient bbcClient = bbcPluginManager.createBBCClient( + chainMeta.getProperties().getPluginServerId(), + chainMeta.getProduct(), + domain + ); + HeteroBlockchainClient heteroBcClient = new HeteroBlockchainClient( + bbcClient, + chainMeta + ); + if (!heteroBcClient.start()) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.CORE_BLOCKCHAIN_CLIENT_INIT_ERROR, + "Blockchain client start failed for product {} and domain {}", + heteroBcClient.getBlockchainMeta().getProduct(), + heteroBcClient.getDomain() + ); + } + clients.put(clientKey, heteroBcClient); + + return heteroBcClient; + } + + public AbstractBlockchainClient getClient(String product, String blockchainId) { + String key = BlockchainMeta.createMetaKey(product, blockchainId); + if (clients.containsKey(key)) { + return clients.get(key); + } + return null; + } + + public synchronized List getAllClient() { + return ListUtil.toList(clients.keySet()); + } + + @Synchronized + public boolean hasClient(String blockchainProduct, String blockchainId) { + String key = blockchainProduct + "_" + blockchainId; + return clients.containsKey(key); + } + + @Synchronized + public void shutdownClient(String blockchainProduct, String blockchainId) { + + String key = blockchainProduct + "_" + blockchainId; + + if (!clients.containsKey(key)) { + return; + } + + clients.get(key).shutdown(); + clients.remove(key); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java new file mode 100644 index 0000000..bfd308c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeteroBlockchainClient.java @@ -0,0 +1,169 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import java.util.List; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext; +import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext; +import com.alipay.antchain.bridge.commons.bbc.syscontract.ContractStatusEnum; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.AMClientContractHeteroBlockchainImpl; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.IAMClientContract; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.ISDPMsgClientContract; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.SDPMsgClientHeteroBlockchainImpl; +import com.alipay.antchain.bridge.relayer.core.types.pluginserver.IBBCServiceClient; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HeteroBlockchainClient extends AbstractBlockchainClient { + + private final IBBCServiceClient bbcClient; + + private final IAMClientContract amClientContract; + + private final ISDPMsgClientContract sdpMsgClient; + + public HeteroBlockchainClient(IBBCServiceClient bbcClient, BlockchainMeta blockchainMeta) { + super(blockchainMeta, bbcClient.getDomain()); + this.bbcClient = bbcClient; + this.amClientContract = new AMClientContractHeteroBlockchainImpl(bbcClient); + this.sdpMsgClient = new SDPMsgClientHeteroBlockchainImpl(bbcClient); + } + + @Override + public boolean start() { + + DefaultBBCContext bbcContext = getBlockchainMeta().getProperties().getBbcContext(); + if (ObjectUtil.isNull(bbcContext)) { + log.error( + "bbcContext is null for ( plugin_server: {}, product: {}, domain: {} )", + getBlockchainMeta().getProperties().getPluginServerId(), + bbcClient.getProduct(), + bbcClient.getDomain() + ); + return false; + } + try { + this.bbcClient.startup(bbcContext); + } catch (Exception e) { + log.error( + "failed to start heterogeneous blockchain client ( plugin_server: {}, product: {}, domain: {} )", + getBlockchainMeta().getProperties().getPluginServerId(), + bbcClient.getProduct(), + bbcClient.getDomain(), + e + ); + return false; + } + + return true; + } + + @Override + public boolean shutdown() { + try { + this.bbcClient.shutdown(); + } catch (Exception e) { + log.error( + "failed to shutdown heterogeneous blockchain client ( plugin_server: {}, product: {}, domain: {} )", + getBlockchainMeta().getProperties().getPluginServerId(), + this.bbcClient.getProduct(), + this.bbcClient.getDomain(), + e + ); + return false; + } + + return true; + } + + @Override + public boolean ifHasDeployedAMClientContract() { + if (checkIfHasAMDeployedLocally()) { + return true; + } + return checkIfHasAMDeployedRemotely(); + } + + private boolean checkContractsStatus(AbstractBBCContext bbcContext) { + if (ObjectUtil.isNull(bbcContext.getAuthMessageContract()) || ObjectUtil.isNull(bbcContext.getSdpContract())) { + log.info( + "local bbc context for ( product: {}, domain: {} ) is incomplete", + getBlockchainMeta().getProduct(), getDomain() + ); + return false; + } + + boolean ifAMPrepared = ContractStatusEnum.CONTRACT_READY == bbcContext.getAuthMessageContract().getStatus(); + if (!ifAMPrepared) { + log.info( + "AM contract of heterogeneous blockchain client ( product: {} , domain: {} ) is {} but ready", + getBlockchainMeta().getProduct(), getDomain(), bbcContext.getAuthMessageContract().getStatus() + ); + return false; + } + boolean ifSDPPrepared = ContractStatusEnum.CONTRACT_READY == bbcContext.getSdpContract().getStatus(); + if (!ifSDPPrepared) { + log.info( + "SDP contract of heterogeneous blockchain client ( product: {} , domain: {} ) is not ready", + getBlockchainMeta().getProduct(), getDomain() + ); + return false; + } + return true; + } + + private boolean checkIfHasAMDeployedLocally() { + return checkContractsStatus( + getBlockchainMeta().getProperties().getBbcContext() + ); + } + + private boolean checkIfHasAMDeployedRemotely() { + AbstractBBCContext bbcContext = this.bbcClient.getContext(); + if (ObjectUtil.isNull(bbcContext)) { + log.error("get null bbc context for {}-{}", getBlockchainMeta().getProduct(), getDomain()); + return false; + } + getBlockchainMeta().getProperties().setBbcContext((DefaultBBCContext) bbcContext); + return checkContractsStatus(bbcContext); + } + + @Override + public long getLastBlockHeight() { + return this.bbcClient.queryLatestHeight(); + } + + @Override + public AbstractBlock getEssentialHeaderByHeight(long height) { + List messages = this.bbcClient.readCrossChainMessagesByHeight(height); + return new HeterogeneousBlock( + getBlockchainMeta().getProduct(), + getDomain(), + getBlockchainMeta().getBlockchainId(), + height, + messages + ); + } + + @Override + public IAMClientContract getAMClientContract() { + return this.amClientContract; + } + + @Override + public ISDPMsgClientContract getSDPMsgClientContract() { + return this.sdpMsgClient; + } + + @Override + public CrossChainMessageReceipt queryCommittedTxReceipt(String txhash) { + return this.bbcClient.readCrossChainMessageReceipt(txhash); + } + + public AbstractBBCContext queryBBCContext() { + return this.bbcClient.getContext(); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeterogeneousBlock.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeterogeneousBlock.java new file mode 100644 index 0000000..be572e9 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/blockchain/HeterogeneousBlock.java @@ -0,0 +1,103 @@ +package com.alipay.antchain.bridge.relayer.core.types.blockchain; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage; +import com.alipay.antchain.bridge.relayer.commons.model.AuthMsgWrapper; +import com.alipay.antchain.bridge.relayer.commons.model.UniformCrosschainPacketContext; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Getter +@Slf4j +@NoArgsConstructor +public class HeterogeneousBlock extends AbstractBlock { + + @JSONField(serialize = false, deserialize = false) + private List uniformCrosschainPacketContexts; + + @JSONField + private List crossChainMessages; + + @JSONField + private String domain; + + public HeterogeneousBlock(String product, String domain, String blockchainId, Long height, List crossChainMessages) { + super( + product, + blockchainId, + height + ); + this.crossChainMessages = crossChainMessages; + this.domain = domain; + } + + @Override + public byte[] encode() { + return JSON.toJSONBytes(this); + } + + @Override + public void decode(byte[] data) { + BeanUtil.copyProperties(JSON.parseObject(data, HeterogeneousBlock.class), this); + } + + public List toAuthMsgWrappers() { + return this.crossChainMessages.stream() + .filter(crossChainMessage -> crossChainMessage.getType() == CrossChainMessage.CrossChainMessageType.AUTH_MSG) + .map( + crossChainMessage -> { + + AuthMsgWrapper wrapper = AuthMsgWrapper.buildFrom( + getProduct(), + getBlockchainId(), + domain, + crossChainMessage + ); + + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_BLOCK_HEIGHT, + Long.toString(getHeight()) + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_BLOCK_HASH, + HexUtil.encodeHexStr(crossChainMessage.getProvableData().getBlockHash()) + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_BLOCK_TIMESTAMP, + String.valueOf(crossChainMessage.getProvableData().getTimestamp()) + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_CAPTURE_TIMESTAMP, + String.valueOf(System.currentTimeMillis()) + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_SENDER_GAS_USED, + "0" + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_HINTS, + StrUtil.EMPTY + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_TX_ID, + HexUtil.encodeHexStr(crossChainMessage.getProvableData().getTxHash()) + ); + wrapper.addLedgerInfo( + AuthMsgWrapper.AM_HETEROGENEOUS_RANDOM_UUID, + UUID.randomUUID().toString() + ); + + return wrapper; + } + ).collect(Collectors.toList()); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/BaseRelayerClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/BaseRelayerClient.java new file mode 100644 index 0000000..d8b88ab --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/BaseRelayerClient.java @@ -0,0 +1,206 @@ +/* + * 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.relayer.core.types.network; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainContent; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainInfo; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.types.network.request.*; +import com.alipay.antchain.bridge.relayer.core.types.network.response.HandshakeRespPayload; +import com.alipay.antchain.bridge.relayer.core.types.network.response.RelayerResponse; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * Endpoint Client基类 + */ +@Getter +@Setter +@Slf4j +public abstract class BaseRelayerClient implements RelayerClient { + + private RelayerNodeInfo remoteNodeInfo; + + private IRelayerCredentialManager relayerCredentialManager; + + private String defaultNetworkId; + + public BaseRelayerClient( + RelayerNodeInfo remoteNodeInfo, + IRelayerCredentialManager relayerCredentialManager, + String defaultNetworkId + ) { + this.remoteNodeInfo = remoteNodeInfo; + this.relayerCredentialManager = relayerCredentialManager; + this.defaultNetworkId = defaultNetworkId; + } + + /** + * 发送请求 + * + * @param relayerRequest + * @return + */ + public abstract RelayerResponse sendRequest(RelayerRequest relayerRequest); + + public abstract void startup(); + + public abstract boolean shutdown(); + + @Override + public RelayerNodeInfo getRelayerNodeInfo() { + RelayerRequest request = new GetRelayerNodeInfoRelayerRequest(); + relayerCredentialManager.signRelayerRequest(request); + + RelayerResponse response = validateRelayerResponse(sendRequest(request)); + if (ObjectUtil.isNull(response) || !response.isSuccess()) { + throw new RuntimeException( + StrUtil.format( + "failed to getRelayerNodeInfo: {} - {}", + response.getResponseCode(), response.getResponseMessage() + ) + ); + } + + return RelayerNodeInfo.decode(Base64.decode(response.getResponsePayload())); + } + + @Override + public RelayerBlockchainInfo getRelayerBlockchainInfo(String domainToQuery) { + RelayerRequest request = new GetRelayerBlockchainInfoRelayerRequest(domainToQuery); + relayerCredentialManager.signRelayerRequest(request); + + RelayerResponse response = validateRelayerResponse(sendRequest(request)); + if (ObjectUtil.isNull(response) || !response.isSuccess()) { + throw new RuntimeException( + StrUtil.format( + "getRelayerBlockchainInfo for domain {} failed: {} - {}", + domainToQuery, + response.getResponseCode(), + response.getResponseMessage() + ) + ); + } + + return RelayerBlockchainInfo.decode(response.getResponsePayload()); + } + + @Override + public RelayerBlockchainContent getRelayerBlockchainContent() { + RelayerRequest request = new GetRelayerBlockchainContentRelayerRequest(); + relayerCredentialManager.signRelayerRequest(request); + + RelayerResponse response = validateRelayerResponse(sendRequest(request)); + if (ObjectUtil.isNull(response) || !response.isSuccess()) { + throw new RuntimeException( + StrUtil.format( + "getRelayerBlockchainContent from relayer {} failed: {} - {}", + remoteNodeInfo.getNodeId(), + response.getResponseCode(), + response.getResponseMessage() + ) + ); + } + + return RelayerBlockchainContent.decodeFromJson(response.getResponsePayload()); + } + + @Override + public void amRequest(String domainName, String authMsg, String udagProof, String ledgerInfo) { + RelayerRequest request = new AMRelayerRequest( + udagProof, + authMsg, + domainName, + ledgerInfo + ); + relayerCredentialManager.signRelayerRequest(request); + + RelayerResponse response = validateRelayerResponse(sendRequest(request)); + if (ObjectUtil.isNull(response)) { + throw new RuntimeException( + StrUtil.format( + "am request from domain {} failed: empty response found", + domainName + ) + ); + } else if (!response.isSuccess()) { + throw new RuntimeException( + StrUtil.format("am request from domain {} failed: (code: {}, msg: {})", + domainName, response.getResponseCode(), response.getResponseMessage() + ) + ); + } + } + + @Override + public RelayerNodeInfo handshake(RelayerNodeInfo senderNodeInfo, String networkId) { + RelayerRequest request = new HandshakeRelayerRequest( + senderNodeInfo, + defaultNetworkId + ); + + RelayerResponse response = validateRelayerResponse(sendRequest(request)); + if (ObjectUtil.isNull(response)) { + throw new RuntimeException( + StrUtil.format( + "handshake with relayer {} failed: response is empty", + senderNodeInfo.getNodeId() + ) + ); + } else if (!response.isSuccess()) { + throw new RuntimeException( + String.format("handshake with relayer {} failed: (code: %d, msg: %s)", + response.getResponseCode(), response.getResponseMessage() + ) + ); + } + + HandshakeRespPayload handshakeRespPayload = HandshakeRespPayload.decodeFromJson(response.getResponsePayload()); + RelayerNodeInfo remoteNodeInfo = RelayerNodeInfo.decode( + Base64.decode(handshakeRespPayload.getRemoteNodeInfo()) + ); + + remoteNodeInfo.getProperties().getProperties().put( + "network_id", + ObjectUtil.defaultIfEmpty( + handshakeRespPayload.getRemoteNetworkId(), + defaultNetworkId + ) + ); + + log.debug("handshake with relayer {} success with response: {}", this.remoteNodeInfo.getNodeId(), response.getResponsePayload()); + + return remoteNodeInfo; + } + + private RelayerResponse validateRelayerResponse(RelayerResponse relayerResponse) { + if (relayerCredentialManager.validateRelayerResponse(relayerResponse)) { + throw new RuntimeException( + StrUtil.format( + "response from relayer {} sig is invalid", + relayerResponse.calcRelayerNodeId() + ) + ); + } + return relayerResponse; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/IRelayerClientPool.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/IRelayerClientPool.java new file mode 100644 index 0000000..95e7a78 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/IRelayerClientPool.java @@ -0,0 +1,24 @@ +/* + * 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.relayer.core.types.network; + +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; + +public interface IRelayerClientPool { + + RelayerClient getRelayerClient(RelayerNodeInfo remoteRelayerNodeInfo); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClient.java new file mode 100644 index 0000000..ae87210 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClient.java @@ -0,0 +1,50 @@ +package com.alipay.antchain.bridge.relayer.core.types.network; + +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainContent; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainInfo; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; + +/** + * 实现节点endpoint客通讯客户端、服务端 + * + * @author honglin.qhl + */ +public interface RelayerClient { + + /** + * 获取Relayer基本信息 + * + * @return + */ + RelayerNodeInfo getRelayerNodeInfo(); + + /** + * 获取支持指定domain的区块链信息,包括oracle等信任根 + * + * @param supportedDomain + * @return + */ + RelayerBlockchainInfo getRelayerBlockchainInfo(String supportedDomain); + + /** + * + * @return + */ + RelayerBlockchainContent getRelayerBlockchainContent(); + + /** + * 发送AM请求 + * + * @param authMsg + * @param udagProof + * @return + */ + void amRequest(String domainName, String authMsg, String udagProof, String ledgerInfo); + + /** + * + * @param nodeInfo + * @return + */ + RelayerNodeInfo handshake(RelayerNodeInfo nodeInfo, String networkId); +} \ No newline at end of file diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClientPool.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClientPool.java new file mode 100644 index 0000000..89e4d4f --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/RelayerClientPool.java @@ -0,0 +1,62 @@ +package com.alipay.antchain.bridge.relayer.core.types.network; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.types.network.ws.WsSslFactory; +import com.alipay.antchain.bridge.relayer.core.types.network.ws.client.WSRelayerClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class RelayerClientPool implements IRelayerClientPool { + + @Resource + private IRelayerCredentialManager relayerCredentialManager; + + @Resource(name = "wsRelayerClientThreadsPool") + private ExecutorService wsRelayerClientThreadsPool; + + @Value("#{systemConfigRepository.defaultNetworkId}") + private String defaultNetworkId; + + @Resource + private WsSslFactory wsSslFactory; + + private final Map clientMap = MapUtil.newConcurrentHashMap(); + + public RelayerClient getRelayerClient(RelayerNodeInfo remoteRelayerNodeInfo) { + + try { + if (!clientMap.containsKey(remoteRelayerNodeInfo.getNodeId())) { + WSRelayerClient client = new WSRelayerClient( + remoteRelayerNodeInfo, + relayerCredentialManager, + defaultNetworkId, + wsRelayerClientThreadsPool, + wsSslFactory.getSslContext().getSocketFactory() + ); + client.startup(); + clientMap.put(remoteRelayerNodeInfo.getNodeId(), client); + } + } catch (Exception e) { + throw new RuntimeException( + StrUtil.format("failed to create relayer client for {}: ", remoteRelayerNodeInfo.getNodeId()), + e + ); + } + + + return clientMap.get(remoteRelayerNodeInfo.getNodeId()); + } + + public void addRelayerClient(String nodeId, RelayerClient client) { + clientMap.put(nodeId, client); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/exception/RejectRequestException.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/exception/RejectRequestException.java new file mode 100644 index 0000000..102ece2 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/exception/RejectRequestException.java @@ -0,0 +1,48 @@ +/* + * 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.relayer.core.types.network.exception; + +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import lombok.Getter; + +@Getter +public class RejectRequestException extends AntChainBridgeRelayerException { + + private final int errorCode; + + private final String errorMsg; + + public RejectRequestException(int errorCode, String errorMsg) { + super( + RelayerErrorCodeEnum.SERVER_REQUEST_FROM_RELAYER_REJECT, + "request reject: " + errorMsg + ); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public RejectRequestException(String message, int errorCode, String errorMsg) { + super( + RelayerErrorCodeEnum.SERVER_REQUEST_FROM_RELAYER_REJECT, + StrUtil.format("request reject: {} - {}", errorMsg, message) + ); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/AMRelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/AMRelayerRequest.java new file mode 100644 index 0000000..fd24750 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/AMRelayerRequest.java @@ -0,0 +1,65 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AMRelayerRequest extends RelayerRequest { + + public static AMRelayerRequest createFrom(RelayerRequest relayerRequest) { + AMRelayerRequest request = JSON.parseObject(relayerRequest.getRequestPayload(), AMRelayerRequest.class); + BeanUtil.copyProperties(relayerRequest, request); + return request; + } + + @JSONField + private String udagProof; + + @JSONField + private String authMsg; + + @JSONField + private String domainName; + + @JSONField + private String ledgerInfo; + + public AMRelayerRequest( + String udagProof, + String authMsg, + String domainName, + String ledgerInfo + ) { + super( + RelayerRequestType.AM_REQUEST + ); + this.udagProof = udagProof; + this.authMsg = authMsg; + this.domainName = domainName; + this.ledgerInfo = ledgerInfo; + + setRequestPayload( + JSON.toJSONBytes(this) + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainContentRelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainContentRelayerRequest.java new file mode 100644 index 0000000..e71c4df --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainContentRelayerRequest.java @@ -0,0 +1,38 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetRelayerBlockchainContentRelayerRequest extends RelayerRequest { + + public static GetRelayerBlockchainContentRelayerRequest createFrom(RelayerRequest relayerRequest) { + return BeanUtil.copyProperties(relayerRequest, GetRelayerBlockchainContentRelayerRequest.class); + } + + public GetRelayerBlockchainContentRelayerRequest() { + super( + RelayerRequestType.GET_RELAYER_BLOCKCHAIN_CONTENT + ); + setRequestPayload(StrUtil.EMPTY.getBytes()); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainInfoRelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainInfoRelayerRequest.java new file mode 100644 index 0000000..69e660c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerBlockchainInfoRelayerRequest.java @@ -0,0 +1,48 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.bean.BeanUtil; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class GetRelayerBlockchainInfoRelayerRequest extends RelayerRequest { + + private String domainToQuery; + + public static GetRelayerBlockchainInfoRelayerRequest createFrom(RelayerRequest relayerRequest) { + GetRelayerBlockchainInfoRelayerRequest request = BeanUtil.copyProperties( + relayerRequest, + GetRelayerBlockchainInfoRelayerRequest.class + ); + request.setDomainToQuery(new String(relayerRequest.getRequestPayload())); + return request; + } + + public GetRelayerBlockchainInfoRelayerRequest( + String domainToQuery + ) { + super( + RelayerRequestType.GET_RELAYER_BLOCKCHAIN_INFO + ); + this.setRequestPayload(domainToQuery.getBytes()); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerNodeInfoRelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerNodeInfoRelayerRequest.java new file mode 100644 index 0000000..fbfb0f2 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/GetRelayerNodeInfoRelayerRequest.java @@ -0,0 +1,33 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetRelayerNodeInfoRelayerRequest extends RelayerRequest { + + public GetRelayerNodeInfoRelayerRequest() { + super( + RelayerRequestType.GET_RELAYER_NODE_INFO + ); + setRequestPayload(StrUtil.EMPTY.getBytes()); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/HandshakeRelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/HandshakeRelayerRequest.java new file mode 100644 index 0000000..05ce37d --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/HandshakeRelayerRequest.java @@ -0,0 +1,77 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.map.MapUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldNameConstants; + +@Getter +@Setter +@NoArgsConstructor +@FieldNameConstants +public class HandshakeRelayerRequest extends RelayerRequest { + + public static HandshakeRelayerRequest createFrom(RelayerRequest relayerRequest) { + HandshakeRelayerRequest request = BeanUtil.copyProperties( + relayerRequest, + HandshakeRelayerRequest.class + ); + + JSONObject jsonObject = JSON.parseObject(new String(relayerRequest.getRequestPayload())); + request.setNetworkId(jsonObject.getString(Fields.networkId)); + request.setSenderNodeInfo(RelayerNodeInfo.decode(jsonObject.getBytes(Fields.senderNodeInfo))); + return request; + } + + public static byte[] createHandshakePayload( + String networkId, + RelayerNodeInfo relayerNodeInfo + ) { + return JSON.toJSONBytes( + MapUtil.builder() + .put(Fields.networkId, networkId) + .put(Fields.senderNodeInfo, relayerNodeInfo.encodeWithProperties()) + .build() + ); + } + + private String networkId; + + private RelayerNodeInfo senderNodeInfo; + + public HandshakeRelayerRequest( + RelayerNodeInfo senderNodeInfo, + String networkId + ) { + super( + RelayerRequestType.HANDSHAKE + ); + + this.networkId = networkId; + this.senderNodeInfo = senderNodeInfo; + setRequestPayload( + createHandshakePayload(networkId, senderNodeInfo) + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequest.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequest.java new file mode 100644 index 0000000..9c9480b --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequest.java @@ -0,0 +1,129 @@ +/* + * 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.relayer.core.types.network.request; + +import java.security.PublicKey; +import java.security.Signature; + +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.commons.bcdns.utils.ObjectIdentityUtil; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVTypeEnum; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVUtils; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.annotation.TLVField; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * Relayer请求 + */ +@Getter +@Setter +@Slf4j +@NoArgsConstructor +public class RelayerRequest { + + public static final short TLV_TYPE_RELAYER_REQUEST_TYPE = 0; + + public static final short TLV_TYPE_RELAYER_REQUEST_NODE_ID = 1; + + public static final short TLV_TYPE_RELAYER_REQUEST_RELAYER_CERT = 2; + + public static final short TLV_TYPE_RELAYER_REQUEST_PAYLOAD = 3; + + public static final short TLV_TYPE_RELAYER_REQUEST_SIG_ALGO = 4; + + public static final short TLV_TYPE_RELAYER_REQUEST_SIGNATURE = 5; + + public static RelayerRequest decode(byte[] rawData, Class requestClass) { + return TLVUtils.decode(rawData, requestClass); + } + + public static RelayerRequest decode(byte[] rawData) { + return TLVUtils.decode(rawData, RelayerRequest.class); + } + + public RelayerRequest( + RelayerRequestType relayerRequestType + ) { + this.requestType = relayerRequestType; + } + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_TYPE, type = TLVTypeEnum.UINT8) + private RelayerRequestType requestType; + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_NODE_ID, type = TLVTypeEnum.STRING, order = TLV_TYPE_RELAYER_REQUEST_NODE_ID) + private String nodeId; + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_RELAYER_CERT, type = TLVTypeEnum.BYTES, order = TLV_TYPE_RELAYER_REQUEST_RELAYER_CERT) + private AbstractCrossChainCertificate senderRelayerCertificate; + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_PAYLOAD, type = TLVTypeEnum.BYTES, order = TLV_TYPE_RELAYER_REQUEST_PAYLOAD) + private byte[] requestPayload; + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_SIG_ALGO, type = TLVTypeEnum.STRING, order = TLV_TYPE_RELAYER_REQUEST_SIG_ALGO) + private String sigAlgo; + + @TLVField(tag = TLV_TYPE_RELAYER_REQUEST_SIGNATURE, type = TLVTypeEnum.BYTES, order = TLV_TYPE_RELAYER_REQUEST_SIGNATURE) + private byte[] signature; + + public byte[] rawEncode() { + return TLVUtils.encode(this, TLV_TYPE_RELAYER_REQUEST_SIGNATURE); + } + + public byte[] encode() { + return TLVUtils.encode(this); + } + + /** + * 验签 + * + * @return + */ + public boolean verify() { + + try { + RelayerCredentialSubject relayerCredentialSubject = RelayerCredentialSubject.decode( + senderRelayerCertificate.getCredentialSubject() + ); + PublicKey publicKey = ObjectIdentityUtil.getPublicKeyFromSubject( + relayerCredentialSubject.getApplicant(), + relayerCredentialSubject.getSubjectInfo() + ); + + Signature verifier = Signature.getInstance(sigAlgo); + verifier.initVerify(publicKey); + verifier.update(rawEncode()); + + return verifier.verify(signature); + + } catch (Exception e) { + throw new RuntimeException("failed to verify request sig", e); + } + } + + public void setRequestTypeCode(String requestType) { + this.requestType = RelayerRequestType.parseFromValue(requestType); + } + + public String calcRelayerNodeId() { + return RelayerNodeInfo.calculateNodeId(senderRelayerCertificate); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequestType.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequestType.java new file mode 100644 index 0000000..451ba56 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/request/RelayerRequestType.java @@ -0,0 +1,118 @@ +/* + * 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.relayer.core.types.network.request; + +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Relayer请求类型 + */ +@Getter +@AllArgsConstructor +public enum RelayerRequestType { + + // relayer之间的请求 + GET_RELAYER_NODE_INFO("getRelayerNodeInfo"), + + GET_RELAYER_BLOCKCHAIN_INFO("getBlockchainInfo"), + + AM_REQUEST("amRequest"), + + /** + * 建立可信连接,进行握手 + */ + HANDSHAKE("handshake"), + + /** + * 获取指定的域名信息 + */ + GET_RELAYER_FOR_DOMAIN("getRelayerForDomain"), + + /** + * 注册域名 + */ + REGISTER_DOMAIN("registerDomain"), + + /** + * 更新指定域名 + */ + UPDATE_DOMAIN("updateDomain"), + + /** + * 让relayer配置中心删除对应域名 + */ + DELETE_DOMAIN("deleteDomain"), + + GET_RELAYER_BLOCKCHAIN_CONTENT("getRelayerBlockChainContent"); + + private final String code; + + public static RelayerRequestType parseFromValue(String value) { + if (StrUtil.equals(value, GET_RELAYER_NODE_INFO.code)) { + return GET_RELAYER_NODE_INFO; + } else if (StrUtil.equals(value, GET_RELAYER_BLOCKCHAIN_INFO.code)) { + return GET_RELAYER_BLOCKCHAIN_INFO; + } else if (StrUtil.equals(value, AM_REQUEST.code)) { + return AM_REQUEST; + } else if (StrUtil.equals(value, HANDSHAKE.code)) { + return HANDSHAKE; + } else if (StrUtil.equals(value, GET_RELAYER_FOR_DOMAIN.code)) { + return GET_RELAYER_FOR_DOMAIN; + } else if (StrUtil.equals(value, REGISTER_DOMAIN.code)) { + return REGISTER_DOMAIN; + } else if (StrUtil.equals(value, UPDATE_DOMAIN.code)) { + return UPDATE_DOMAIN; + } else if (StrUtil.equals(value, DELETE_DOMAIN.code)) { + return DELETE_DOMAIN; + } else if (StrUtil.equals(value, GET_RELAYER_BLOCKCHAIN_CONTENT.code)) { + return GET_RELAYER_BLOCKCHAIN_CONTENT; + } + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.UNKNOWN_INTERNAL_ERROR, + "Invalid value for relayer request type: " + value + ); + } + + public static RelayerRequestType valueOf(Byte value) { + switch (value) { + case 0: + return GET_RELAYER_NODE_INFO; + case 1: + return GET_RELAYER_BLOCKCHAIN_INFO; + case 2: + return AM_REQUEST; + case 3: + return HANDSHAKE; + case 4: + return GET_RELAYER_FOR_DOMAIN; + case 5: + return REGISTER_DOMAIN; + case 6: + return UPDATE_DOMAIN; + case 7: + return DELETE_DOMAIN; + case 8: + return GET_RELAYER_BLOCKCHAIN_CONTENT; + default: + return null; + } + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/HandshakeRespPayload.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/HandshakeRespPayload.java new file mode 100644 index 0000000..3593a9b --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/HandshakeRespPayload.java @@ -0,0 +1,41 @@ +/* + * 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.relayer.core.types.network.response; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class HandshakeRespPayload implements IResponsePayload { + public static HandshakeRespPayload decodeFromJson(String json) { + return JSON.parseObject(json, HandshakeRespPayload.class); + } + + @JSONField(name = "network_id") + private String remoteNetworkId; + + @JSONField(name = "remote_node_info") + private String remoteNodeInfo; + + @Override + public String encode() { + return JSON.toJSONString(this); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/IResponsePayload.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/IResponsePayload.java new file mode 100644 index 0000000..94bab61 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/IResponsePayload.java @@ -0,0 +1,22 @@ +/* + * 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.relayer.core.types.network.response; + +public interface IResponsePayload { + + String encode(); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/RelayerResponse.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/RelayerResponse.java new file mode 100644 index 0000000..a551cef --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/response/RelayerResponse.java @@ -0,0 +1,187 @@ +/* + * 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.relayer.core.types.network.response; + +import java.security.PublicKey; +import java.security.Signature; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.RelayerCredentialSubject; +import com.alipay.antchain.bridge.commons.bcdns.utils.ObjectIdentityUtil; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVTypeEnum; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.TLVUtils; +import com.alipay.antchain.bridge.commons.utils.codec.tlv.annotation.TLVField; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * Relaye请求响应 + */ +@Getter +@Setter +@Slf4j +public class RelayerResponse { + + public static final int SUCCESS = 0; + + public static final int FAILED = -1; + + public static final short TLV_TYPE_RELAYER_RESPONSE_CODE = 0; + + public static final short TLV_TYPE_RELAYER_RESPONSE_MSG = 1; + + public static final short TLV_TYPE_RELAYER_RESPONSE_PAYLOAD = 2; + + public static final short TLV_TYPE_RELAYER_RESPONSE_REMOTE_RELAYER_CERT = 3; + + public static final short TLV_TYPE_RELAYER_RESPONSE_REMOTE_SIG_ALGO = 4; + + public static final short TLV_TYPE_RELAYER_RESPONSE_SIG = 5; + + public static RelayerResponse createSuccessResponse( + IResponsePayload payload, + IRelayerCredentialManager relayerCredentialManager + ) { + return createResponse( + SUCCESS, + "", + payload, + relayerCredentialManager + ); + } + + public static RelayerResponse createFailureResponse( + String errorMsg, + IResponsePayload payload, + IRelayerCredentialManager relayerCredentialManager + ) { + return createResponse( + FAILED, + errorMsg, + payload, + relayerCredentialManager + ); + } + + public static RelayerResponse createFailureResponse( + String errorMsg, + IRelayerCredentialManager relayerCredentialManager + ) { + return createResponse( + FAILED, + errorMsg, + null, + relayerCredentialManager + ); + } + + public static RelayerResponse createResponse( + int errorCode, + String message, + IResponsePayload payload, + IRelayerCredentialManager relayerCredentialManager + ) { + RelayerResponse relayerResponse = new RelayerResponse(); + relayerResponse.setResponseCode(errorCode); + relayerResponse.setResponseMessage(message); + relayerResponse.setResponsePayload(ObjectUtil.isNull(payload) ? "" : payload.encode()); + relayerCredentialManager.signRelayerResponse(relayerResponse); + + return relayerResponse; + } + + public static RelayerResponse decode(byte[] rawData) { + return TLVUtils.decode(rawData, RelayerResponse.class); + } + + @TLVField(tag = TLV_TYPE_RELAYER_RESPONSE_CODE, type = TLVTypeEnum.UINT8) + private int responseCode; + + @TLVField(tag = TLV_TYPE_RELAYER_RESPONSE_MSG, type = TLVTypeEnum.STRING, order = TLV_TYPE_RELAYER_RESPONSE_MSG) + private String responseMessage; + + @TLVField(tag = TLV_TYPE_RELAYER_RESPONSE_PAYLOAD, type = TLVTypeEnum.STRING, order = TLV_TYPE_RELAYER_RESPONSE_PAYLOAD) + private String responsePayload; + + @TLVField( + tag = TLV_TYPE_RELAYER_RESPONSE_REMOTE_RELAYER_CERT, + type = TLVTypeEnum.BYTES, + order = TLV_TYPE_RELAYER_RESPONSE_REMOTE_RELAYER_CERT + ) + private AbstractCrossChainCertificate remoteRelayerCertificate; + + @TLVField( + tag = TLV_TYPE_RELAYER_RESPONSE_REMOTE_SIG_ALGO, + type = TLVTypeEnum.STRING, + order = TLV_TYPE_RELAYER_RESPONSE_REMOTE_SIG_ALGO + ) + private String sigAlgo; + + @TLVField( + tag = TLV_TYPE_RELAYER_RESPONSE_SIG, + type = TLVTypeEnum.BYTES, + order = TLV_TYPE_RELAYER_RESPONSE_SIG + ) + private byte[] signature; + + public byte[] rawEncode() { + return TLVUtils.encode(this, TLV_TYPE_RELAYER_RESPONSE_SIG); + } + + public byte[] encode() { + return TLVUtils.encode(this); + } + + /** + * 验签 + * + * @return + */ + public boolean verify() { + + try { + RelayerCredentialSubject relayerCredentialSubject = RelayerCredentialSubject.decode( + remoteRelayerCertificate.getCredentialSubject() + ); + PublicKey publicKey = ObjectIdentityUtil.getPublicKeyFromSubject( + relayerCredentialSubject.getApplicant(), + relayerCredentialSubject.getSubjectInfo() + ); + + Signature verifier = Signature.getInstance(sigAlgo); + verifier.initVerify(publicKey); + verifier.update(rawEncode()); + + return verifier.verify(signature); + + } catch (Exception e) { + throw new RuntimeException("failed to verify response sig", e); + } + } + + public boolean isSuccess() { + return responseCode == SUCCESS; + } + + public String calcRelayerNodeId() { + return RelayerNodeInfo.calculateNodeId(remoteRelayerCertificate); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsRelayerNodePeerEndpoint.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsRelayerNodePeerEndpoint.java new file mode 100644 index 0000000..df0fd3f --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsRelayerNodePeerEndpoint.java @@ -0,0 +1,38 @@ +/* + * 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.relayer.core.types.network.ws; + +//@Endpoint +//@Slf4j +public class WsRelayerNodePeerEndpoint { + +// @PayloadRoot(localPart = "request", namespace = "http://ws.offchainapi.oracle.mychain.alipay.com/") +// @ResponsePayload +// public RequestResponse request(@RequestPayload Request request) { +// log.info("request!!! {}", request.getRelayerRequest()); +// RequestResponse response = new RequestResponse(); +// response.setReturn("test"); +// return response; +// } +// +// @PayloadRoot(localPart = "WSRelayerServerAPImplService", namespace = "http://ws.offchainapi.oracle.mychain.alipay.com/") +// @ResponsePayload +// public String request(@RequestPayload String relayerRequest) { +// log.info("request!!! {}", relayerRequest); +// return relayerRequest; +// } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsSslFactory.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsSslFactory.java new file mode 100644 index 0000000..37f7813 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/WsSslFactory.java @@ -0,0 +1,77 @@ +/* + * 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.relayer.core.types.network.ws; + +import java.io.ByteArrayInputStream; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.net.SSLUtil; +import cn.hutool.crypto.PemUtil; +import io.grpc.util.CertificateUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class WsSslFactory { + + @Value("${relayer.network.node.tls.private_key_path}") + private String privateKeyPath; + + @Value("${relayer.network.node.tls.trust_ca_path}") + private String trustCaPath; + + public SSLContext getSslContext() throws Exception { + PrivateKey privateKey = PemUtil.readPemPrivateKey( + new ByteArrayInputStream(FileUtil.readBytes(privateKeyPath)) + ); + Certificate[] trustCertificates = CertificateUtils.getX509Certificates( + new ByteArrayInputStream(FileUtil.readBytes(trustCaPath)) + ); + + char[] keyStorePassword = new char[0]; + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + int count = 0; + for (Certificate cert : trustCertificates) { + keyStore.setCertificateEntry("cert" + count, cert); + count++; + } + keyStore.setKeyEntry("key", privateKey, keyStorePassword, trustCertificates); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm() + ); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm() + ); + + trustManagerFactory.init(keyStore); + keyManagerFactory.init(keyStore, keyStorePassword); + + return SSLUtil.createSSLContext( + "TLSv1.2", + keyManagerFactory.getKeyManagers(), + trustManagerFactory.getTrustManagers() + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerClient.java new file mode 100644 index 0000000..6a04ae1 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerClient.java @@ -0,0 +1,126 @@ +/* + * 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.relayer.core.types.network.ws.client; + +import java.util.concurrent.ExecutorService; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.xml.ws.BindingProvider; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.types.network.BaseRelayerClient; +import com.alipay.antchain.bridge.relayer.core.types.network.request.RelayerRequest; +import com.alipay.antchain.bridge.relayer.core.types.network.response.RelayerResponse; +import com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated.WSRelayerServerAPImpl; + +public class WSRelayerClient extends BaseRelayerClient { + + private static final String HOSTNAME_VERIFIER = "com.sun.xml.internal.ws.transport.https.client.hostname.verifier"; + + private static final String SSL_SOCKET_FACTORY = "com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory"; + + private WSRelayerServerAPImpl wsEndpointServer; + + private final ExecutorService workers; + + private final SSLSocketFactory sslSocketFactory; + + public WSRelayerClient( + RelayerNodeInfo remoteNodeInfo, + IRelayerCredentialManager relayerCredentialManager, + String defaultNetworkId, + ExecutorService workers, + SSLSocketFactory sslSocketFactory + ) { + super(remoteNodeInfo, relayerCredentialManager, defaultNetworkId); + this.workers = workers; + this.sslSocketFactory = sslSocketFactory; + } + + @Override + public void startup() { + + HostnameVerifier hostnameVerifier = (s, sslSession) -> true; + + HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); + HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); + + if (getRemoteNodeInfo().getEndpoints().size() == 0) { + throw new RuntimeException( + String.format( + "failed to start WSRelayerServerAPImplService: zero size endpoints for relayer (node_id: %s)", + getRemoteNodeInfo().getNodeId() + ) + ); + } + + boolean isRunningOnTLS = false; + WSRelayerServerAPImplServiceWithHost serverService = null; + for (int idx = 0; idx < getRemoteNodeInfo().getEndpoints().size(); ++idx) { + try { + String url = getRemoteNodeInfo().getEndpoints().get(idx); + if (!url.startsWith("http")) { + url += "https://"; + } + serverService = new WSRelayerServerAPImplServiceWithHost(url); + isRunningOnTLS = url.startsWith("https"); + break; + } catch (Exception e) { + if (idx == getRemoteNodeInfo().getEndpoints().size() - 1) { + throw new RuntimeException("failed to start WSRelayerServerAPImplService. ", e); + } + } + } + if (ObjectUtil.isNull(serverService)) { + throw new RuntimeException("null webservice client made for relayer " + getRemoteNodeInfo().getNodeId()); + } + + serverService.setExecutor(workers); + wsEndpointServer = serverService.getWSRelayerServerAPImplPort(); + + BindingProvider bindingProvider = (BindingProvider) wsEndpointServer; + + if (isRunningOnTLS) { + bindingProvider.getRequestContext().put(HOSTNAME_VERIFIER, hostnameVerifier); + bindingProvider.getRequestContext().put(SSL_SOCKET_FACTORY, sslSocketFactory); + bindingProvider.getRequestContext().put( + BindingProvider.ENDPOINT_ADDRESS_PROPERTY, + serverService.getWSDLDocumentLocation().toString() + ); + } + } + + @Override + public boolean shutdown() { + return true; + } + + @Override + public RelayerResponse sendRequest(RelayerRequest relayerRequest) { + return RelayerResponse.decode( + Base64.decode( + wsEndpointServer.request( + Base64.encode(relayerRequest.encode()) + ) + ) + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerServerAPImplServiceWithHost.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerServerAPImplServiceWithHost.java new file mode 100644 index 0000000..dc81c6c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/WSRelayerServerAPImplServiceWithHost.java @@ -0,0 +1,111 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client; + +import java.net.MalformedURLException; +import java.net.URL; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import javax.xml.ws.WebEndpoint; +import javax.xml.ws.WebServiceClient; +import javax.xml.ws.WebServiceException; +import javax.xml.ws.WebServiceFeature; + +import com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated.WSRelayerServerAPImpl; + +/** + * THIS IS NOT A GENERATED CLASS!!! + *

    + * Add some features to {@link com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated.WSRelayerServerAPImplService} + * for supporting define the host to connect, + * and we get this class {@code WSRelayerServerAPImplServiceWithHost} + *

    + */ +@WebServiceClient(name = "WSRelayerServerAPImplService", + targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", + wsdlLocation = "http://127.0.0.1:8082/WSEndpointServer?wsdl") +public class WSRelayerServerAPImplServiceWithHost + extends Service { + + private final static URL WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION; + private final static WebServiceException WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION; + private final static QName WSRELAYERSERVERAPIMPLSERVICE_QNAME = new QName( + "http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplService"); + + static { + URL url = null; + WebServiceException e = null; + try { + url = new URL("http://127.0.0.1:8082/WSEndpointServer?wsdl"); + } catch (MalformedURLException ex) { + e = new WebServiceException(ex); + } + WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION = url; + WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION = e; + } + + public WSRelayerServerAPImplServiceWithHost() { + super(__getWsdlLocation(), WSRELAYERSERVERAPIMPLSERVICE_QNAME); + } + + public WSRelayerServerAPImplServiceWithHost(WebServiceFeature... features) { + super(__getWsdlLocation(), WSRELAYERSERVERAPIMPLSERVICE_QNAME, features); + } + + public WSRelayerServerAPImplServiceWithHost(String host) { + + super(__getWsdlLocation(host), WSRELAYERSERVERAPIMPLSERVICE_QNAME); + } + + public WSRelayerServerAPImplServiceWithHost(URL wsdlLocation) { + super(wsdlLocation, WSRELAYERSERVERAPIMPLSERVICE_QNAME); + } + + public WSRelayerServerAPImplServiceWithHost(URL wsdlLocation, WebServiceFeature... features) { + super(wsdlLocation, WSRELAYERSERVERAPIMPLSERVICE_QNAME, features); + } + + public WSRelayerServerAPImplServiceWithHost(URL wsdlLocation, QName serviceName) { + super(wsdlLocation, serviceName); + } + + public WSRelayerServerAPImplServiceWithHost(URL wsdlLocation, QName serviceName, WebServiceFeature... features) { + super(wsdlLocation, serviceName, features); + } + + /** + * @return returns WSRelayerServerAPImpl + */ + @WebEndpoint(name = "WSRelayerServerAPImplPort") + public WSRelayerServerAPImpl getWSRelayerServerAPImplPort() { + return super.getPort(new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplPort"), + WSRelayerServerAPImpl.class); + } + + /** + * @param features A list of {@link WebServiceFeature} to configure on the proxy. Supported features not in the + * features parameter will have their default values. + * @return returns WSRelayerServerAPImpl + */ + @WebEndpoint(name = "WSRelayerServerAPImplPort") + public WSRelayerServerAPImpl getWSRelayerServerAPImplPort(WebServiceFeature... features) { + return super.getPort(new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplPort"), + WSRelayerServerAPImpl.class, features); + } + + private static URL __getWsdlLocation() { + if (WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION != null) { + throw WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION; + } + return WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION; + } + + private static URL __getWsdlLocation(String host) { + try { + return new URL(host + "/WSEndpointServer?wsdl"); + } catch (MalformedURLException e) { + throw new WebServiceException(e); + } + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/ObjectFactory.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/ObjectFactory.java new file mode 100644 index 0000000..d211ef4 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/ObjectFactory.java @@ -0,0 +1,79 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated package. + *

    An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _Request_QNAME = new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "request"); + private final static QName _RequestResponse_QNAME = new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "requestResponse"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link Request } + * + */ + public Request createRequest() { + return new Request(); + } + + /** + * Create an instance of {@link RequestResponse } + * + */ + public RequestResponse createRequestResponse() { + return new RequestResponse(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link Request }{@code >} + * + * @param value + * Java instance representing xml element's value. + * @return + * the new instance of {@link JAXBElement }{@code <}{@link Request }{@code >} + */ + @XmlElementDecl(namespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", name = "request") + public JAXBElement createRequest(Request value) { + return new JAXBElement(_Request_QNAME, Request.class, null, value); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link RequestResponse }{@code >} + * + * @param value + * Java instance representing xml element's value. + * @return + * the new instance of {@link JAXBElement }{@code <}{@link RequestResponse }{@code >} + */ + @XmlElementDecl(namespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", name = "requestResponse") + public JAXBElement createRequestResponse(RequestResponse value) { + return new JAXBElement(_RequestResponse_QNAME, RequestResponse.class, null, value); + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java new file mode 100644 index 0000000..e017cae --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/Request.java @@ -0,0 +1,60 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; + + +/** + *

    request complex type的 Java 类。 + * + *

    以下模式片段指定包含在此类中的预期内容。 + * + *

    + * <complexType name="request">
    + *   <complexContent>
    + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
    + *       <sequence>
    + *         <element name="relayerRequest" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
    + *       </sequence>
    + *     </restriction>
    + *   </complexContent>
    + * </complexType>
    + * 
    + * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "request", propOrder = { + "relayerRequest" +}) +public class Request { + + protected String relayerRequest; + + /** + * 获取relayerRequest属性的值。 + * + * @return + * possible object is + * {@link String } + * + */ + public String getRelayerRequest() { + return relayerRequest; + } + + /** + * 设置relayerRequest属性的值。 + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setRelayerRequest(String value) { + this.relayerRequest = value; + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java new file mode 100644 index 0000000..f1e53e0 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/RequestResponse.java @@ -0,0 +1,62 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

    requestResponse complex type的 Java 类。 + * + *

    以下模式片段指定包含在此类中的预期内容。 + * + *

    + * <complexType name="requestResponse">
    + *   <complexContent>
    + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
    + *       <sequence>
    + *         <element name="return" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
    + *       </sequence>
    + *     </restriction>
    + *   </complexContent>
    + * </complexType>
    + * 
    + * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "requestResponse", propOrder = { + "_return" +}) +public class RequestResponse { + + @XmlElement(name = "return") + protected String _return; + + /** + * 获取return属性的值。 + * + * @return + * possible object is + * {@link String } + * + */ + public String getReturn() { + return _return; + } + + /** + * 设置return属性的值。 + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setReturn(String value) { + this._return = value; + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImpl.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImpl.java new file mode 100644 index 0000000..b45212c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImpl.java @@ -0,0 +1,42 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebResult; +import javax.jws.WebService; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.ws.Action; +import javax.xml.ws.RequestWrapper; +import javax.xml.ws.ResponseWrapper; + + +/** + * This class was generated by the JAX-WS RI. + * JAX-WS RI 2.3.2 + * Generated source version: 2.2 + * + */ +@WebService(name = "WSRelayerServerAPImpl", targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/") +@XmlSeeAlso({ + ObjectFactory.class +}) +public interface WSRelayerServerAPImpl { + + + /** + * + * @param relayerRequest + * @return + * returns java.lang.String + */ + @WebMethod + @WebResult(targetNamespace = "") + @RequestWrapper(localName = "request", targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", className = "com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated.Request") + @ResponseWrapper(localName = "requestResponse", targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", className = "com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated.RequestResponse") + @Action(input = "http://ws.offchainapi.oracle.mychain.alipay.com/WSRelayerServerAPImpl/requestRequest", output = "http://ws.offchainapi.oracle.mychain.alipay.com/WSRelayerServerAPImpl/requestResponse") + public String request( + @WebParam(name = "relayerRequest", targetNamespace = "") + String relayerRequest); + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImplService.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImplService.java new file mode 100644 index 0000000..ee39881 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/WSRelayerServerAPImplService.java @@ -0,0 +1,94 @@ + +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; + +import java.net.MalformedURLException; +import java.net.URL; +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import javax.xml.ws.WebEndpoint; +import javax.xml.ws.WebServiceClient; +import javax.xml.ws.WebServiceException; +import javax.xml.ws.WebServiceFeature; + + +/** + * This class was generated by the JAX-WS RI. + * JAX-WS RI 2.3.2 + * Generated source version: 2.2 + * + */ +@WebServiceClient(name = "WSRelayerServerAPImplService", targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/", wsdlLocation = "http://127.0.0.1:8082/WSEndpointServer?wsdl") +public class WSRelayerServerAPImplService + extends Service +{ + + private final static URL WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION; + private final static WebServiceException WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION; + private final static QName WSRELAYERSERVERAPIMPLSERVICE_QNAME = new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplService"); + + static { + URL url = null; + WebServiceException e = null; + try { + url = new URL("http://127.0.0.1:8082/WSEndpointServer?wsdl"); + } catch (MalformedURLException ex) { + e = new WebServiceException(ex); + } + WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION = url; + WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION = e; + } + + public WSRelayerServerAPImplService() { + super(__getWsdlLocation(), WSRELAYERSERVERAPIMPLSERVICE_QNAME); + } + + public WSRelayerServerAPImplService(WebServiceFeature... features) { + super(__getWsdlLocation(), WSRELAYERSERVERAPIMPLSERVICE_QNAME, features); + } + + public WSRelayerServerAPImplService(URL wsdlLocation) { + super(wsdlLocation, WSRELAYERSERVERAPIMPLSERVICE_QNAME); + } + + public WSRelayerServerAPImplService(URL wsdlLocation, WebServiceFeature... features) { + super(wsdlLocation, WSRELAYERSERVERAPIMPLSERVICE_QNAME, features); + } + + public WSRelayerServerAPImplService(URL wsdlLocation, QName serviceName) { + super(wsdlLocation, serviceName); + } + + public WSRelayerServerAPImplService(URL wsdlLocation, QName serviceName, WebServiceFeature... features) { + super(wsdlLocation, serviceName, features); + } + + /** + * + * @return + * returns WSRelayerServerAPImpl + */ + @WebEndpoint(name = "WSRelayerServerAPImplPort") + public WSRelayerServerAPImpl getWSRelayerServerAPImplPort() { + return super.getPort(new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplPort"), WSRelayerServerAPImpl.class); + } + + /** + * + * @param features + * A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy. Supported features not in the features parameter will have their default values. + * @return + * returns WSRelayerServerAPImpl + */ + @WebEndpoint(name = "WSRelayerServerAPImplPort") + public WSRelayerServerAPImpl getWSRelayerServerAPImplPort(WebServiceFeature... features) { + return super.getPort(new QName("http://ws.offchainapi.oracle.mychain.alipay.com/", "WSRelayerServerAPImplPort"), WSRelayerServerAPImpl.class, features); + } + + private static URL __getWsdlLocation() { + if (WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION!= null) { + throw WSRELAYERSERVERAPIMPLSERVICE_EXCEPTION; + } + return WSRELAYERSERVERAPIMPLSERVICE_WSDL_LOCATION; + } + +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/package-info.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/package-info.java new file mode 100644 index 0000000..755259a --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/network/ws/client/generated/package-info.java @@ -0,0 +1,2 @@ +@javax.xml.bind.annotation.XmlSchema(namespace = "http://ws.offchainapi.oracle.mychain.alipay.com/") +package com.alipay.antchain.bridge.relayer.core.types.network.ws.client.generated; diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java new file mode 100644 index 0000000..1419e7c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcBBCServiceClient.java @@ -0,0 +1,356 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver; + +import java.util.List; +import java.util.stream.Collectors; + +import com.alibaba.fastjson.JSON; +import com.alipay.antchain.bridge.commons.bbc.AbstractBBCContext; +import com.alipay.antchain.bridge.commons.bbc.DefaultBBCContext; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +import com.alipay.antchain.bridge.pluginserver.service.*; +import com.alipay.antchain.bridge.relayer.core.utils.PluginServerUtils; +import com.google.protobuf.ByteString; + +public class GRpcBBCServiceClient implements IBBCServiceClient { + + private String psId; + + private final String product; + + private final String domain; + + private CrossChainServiceGrpc.CrossChainServiceBlockingStub blockingStub; + + private AbstractBBCContext bbcContext; + + public GRpcBBCServiceClient(String psId, String product, String domain, CrossChainServiceGrpc.CrossChainServiceBlockingStub blockingStub) { + this.psId = psId; + this.product = product; + this.domain = domain; + this.blockingStub = blockingStub; + } + + @Override + public String getProduct() { + return this.product; + } + + @Override + public String getDomain() { + return this.domain; + } + + @Override + public void startup(AbstractBBCContext abstractBBCContext) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setStartUpReq( + StartUpRequest.newBuilder() + .setRawContext(ByteString.copyFrom(JSON.toJSONBytes(abstractBBCContext))) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] startup request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg())); + } + + this.bbcContext = abstractBBCContext; + } + + @Override + public void shutdown() { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setShutdownReq(ShutdownRequest.getDefaultInstance()) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] shutdown request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg())); + } + } + + @Override + public AbstractBBCContext getContext() { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setGetContextReq(GetContextRequest.getDefaultInstance()) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] getContext request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg())); + } + AbstractBBCContext bbcContext = new DefaultBBCContext(); + bbcContext.decodeFromBytes(response.getBbcResp().getGetContextResp().getRawContext().toByteArray()); + this.bbcContext = bbcContext; + + return bbcContext; + } + + @Override + public CrossChainMessageReceipt readCrossChainMessageReceipt(String txhash) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setReadCrossChainMessageReceiptReq( + ReadCrossChainMessageReceiptRequest.newBuilder().setTxhash(txhash) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] isCrossChainMessageConfirmed request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + return PluginServerUtils.convertFromGRpcCrossChainMessageReceipt( + response.getBbcResp().getReadCrossChainMessageReceiptResp().getReceipt() + ); + } + + @Override + public List readCrossChainMessagesByHeight(long height) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setReadCrossChainMessagesByHeightReq( + ReadCrossChainMessagesByHeightRequest.newBuilder() + .setHeight(height) + ).build() + ); + if (response.getCode() != 0) { + try { + handleErrorCode(response); + } catch (Exception e) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] readCrossChainMessagesByHeight request failed :", + this.domain, this.product), e + ); + } + } + return response.getBbcResp().getReadCrossChainMessagesByHeightResp().getMessageListList().stream() + .map(PluginServerUtils::convertFromGRpcCrossChainMessage) + .collect(Collectors.toList()); + } + + @Override + public long querySDPMessageSeq(String senderDomain, String fromAddress, String receiverDomain, String toAddress) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setQuerySDPMessageSeqReq( + QuerySDPMessageSeqRequest.newBuilder() + .setSenderDomain(senderDomain) + .setFromAddress(fromAddress) + .setReceiverDomain(receiverDomain) + .setToAddress(toAddress) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] querySDPMessageSeq request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + return response.getBbcResp().getQuerySDPMsgSeqResp().getSequence(); + } + + @Override + public void setupAuthMessageContract() { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setSetupAuthMessageContractReq(SetupAuthMessageContractRequest.getDefaultInstance()) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setupAuthMessageContract request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + } + + @Override + public void setupSDPMessageContract() { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setSetupSDPMessageContractReq(SetupSDPMessageContractRequest.getDefaultInstance()) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setupSDPMessageContract 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( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setSetProtocolReq( + SetProtocolRequest.newBuilder() + .setProtocolType(protocolType) + .setProtocolAddress(protocolAddress) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setProtocol request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + } + + @Override + public CrossChainMessageReceipt relayAuthMessage(byte[] rawMessage) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setRelayAuthMessageReq( + RelayAuthMessageRequest.newBuilder() + .setRawMessage(ByteString.copyFrom(rawMessage)) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] relayAuthMessage request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + + return PluginServerUtils.convertFromGRpcCrossChainMessageReceipt(response.getBbcResp().getRelayAuthMessageResponse().getReceipt()); + } + + @Override + public void setAmContract(String contractAddress) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setSetAmContractReq(SetAmContractRequest.newBuilder().setContractAddress(contractAddress)) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setAmContract request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + } + + @Override + public void setLocalDomain(String domain) { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setSetLocalDomainReq(SetLocalDomainRequest.newBuilder().setDomain(domain)) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] setLocalDomain request failed for plugin server %s: %s", + this.domain, this.product, this.psId, response.getErrorMsg()) + ); + } + } + + @Override + public Long queryLatestHeight() { + Response response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setQueryLatestHeightReq( + QueryLatestHeightRequest.getDefaultInstance() + ).build() + ); + if (response.getCode() != 0) { + try { + handleErrorCode(response); + } catch (Exception e) { + throw new RuntimeException( + String.format("[GRpcBBCServiceClient (domain: %s, product: %s)] queryLatestHeight request failed :", + this.domain, this.product), e + ); + } + } + return response.getBbcResp().getQueryLatestHeightResponse().getHeight(); + } + + @Override + public boolean hasTPBTA(String s) { + return false; + } + + @Override + public byte[] getTPBTA(String s) { + return new byte[0]; + } + + @Override + public boolean ifBlockchainProductSupported(String s) { + return false; + } + + @Override + public void addTPBTA(String s, byte[] bytes) { + + } + + @Override + public void approveProtocol(String s) { + + } + + @Override + public void disapproveProtocol(String s) { + + } + + @Override + public void setOwner(String s) { + + } + + private void handleErrorCode(Response response) { + if (response.getCode() == 217) { + response = this.blockingStub.bbcCall( + CallBBCRequest.newBuilder() + .setProduct(this.getProduct()) + .setDomain(this.getDomain()) + .setStartUpReq( + StartUpRequest.newBuilder() + .setRawContext(ByteString.copyFrom(JSON.toJSONBytes(this.bbcContext))) + ).build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("restart request failed for plugin server %s: %s", + this.psId, response.getErrorMsg())); + } + return; + } + throw new RuntimeException( + String.format("error code %d for plugin server %s: %s", response.getCode(), this.psId, response.getErrorMsg()) + ); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcPluginServerClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcPluginServerClient.java new file mode 100644 index 0000000..04c574c --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/GRpcPluginServerClient.java @@ -0,0 +1,81 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.pluginserver.service.*; +import com.alipay.antchain.bridge.relayer.commons.model.PluginServerInfo; +import lombok.Synchronized; + +public class GRpcPluginServerClient implements IPluginServerClient { + + private final String psId; + + private CrossChainServiceGrpc.CrossChainServiceBlockingStub serviceStub; + + private final AtomicInteger errorCount = new AtomicInteger(0); + + private final int errorLimitForHeartbeat; + + public GRpcPluginServerClient( + String psId, + CrossChainServiceGrpc.CrossChainServiceBlockingStub serviceStub, + int errorLimitForHeartbeat + ) { + this.psId = psId; + this.serviceStub = serviceStub; + this.errorLimitForHeartbeat = errorLimitForHeartbeat; + } + + @Override + @Synchronized + public PluginServerInfo heartbeat() { + Response response = this.serviceStub.heartbeat(Empty.getDefaultInstance()); + if ( + (ObjectUtil.isNull(response) || response.getCode() != 0) + && this.errorCount.getAndAdd(1) > this.errorLimitForHeartbeat + ) { + throw new RuntimeException(String.format("heartbeat failed out of limit %s for plugin server %s: %s", + this.errorCount.get(), this.psId, response.getErrorMsg())); + } + + if (!response.hasHeartbeatResp()) { + throw new RuntimeException(String.format("heartbeat response not found for plugin server %s", this.psId)); + } + this.errorCount.set(0); + return new PluginServerInfo( + response.getHeartbeatResp().getProductsList(), + response.getHeartbeatResp().getDomainsList() + ); + } + + @Override + public Map ifProductSupport(List products) { + Response response = this.serviceStub.ifProductSupport( + IfProductSupportRequest.newBuilder() + .addAllProducts(products) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("ifProductSupport request failed for plugin server %s: %s", + this.psId, response.getErrorMsg())); + } + return response.getIfProductSupportResp().getResultsMap(); + } + + @Override + public Map ifDomainAlive(List domains) { + Response response = this.serviceStub.ifDomainAlive( + IfDomainAliveRequest.newBuilder() + .addAllDomains(domains) + .build() + ); + if (response.getCode() != 0) { + throw new RuntimeException(String.format("ifDomainAlive request failed for plugin server %s: %s", + this.psId, response.getErrorMsg())); + } + return response.getIfDomainAliveResp().getResultsMap(); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IBBCServiceClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IBBCServiceClient.java new file mode 100644 index 0000000..58124cd --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IBBCServiceClient.java @@ -0,0 +1,26 @@ +/* + * 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.relayer.core.types.pluginserver; + +import com.alipay.antchain.bridge.plugins.spi.bbc.IBBCService; + +public interface IBBCServiceClient extends IBBCService { + + String getProduct(); + + String getDomain(); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IPluginServerClient.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IPluginServerClient.java new file mode 100644 index 0000000..cb57726 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/IPluginServerClient.java @@ -0,0 +1,31 @@ +/* + * 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.relayer.core.types.pluginserver; + +import java.util.List; +import java.util.Map; + +import com.alipay.antchain.bridge.relayer.commons.model.PluginServerInfo; + +public interface IPluginServerClient { + + PluginServerInfo heartbeat(); + + Map ifProductSupport(List products); + + Map ifDomainAlive(List domains); +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/PluginServerMetaInfo.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/PluginServerMetaInfo.java new file mode 100644 index 0000000..b3ab9a6 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/PluginServerMetaInfo.java @@ -0,0 +1,15 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class PluginServerMetaInfo { + + private String address; + + private String properties; +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerConnectionFailException.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerConnectionFailException.java new file mode 100644 index 0000000..d2049e1 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerConnectionFailException.java @@ -0,0 +1,11 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver.exception; + +public class PluginServerConnectionFailException extends PluginServerManagerException { + public PluginServerConnectionFailException(String message, Throwable cause) { + super(message, cause); + } + + public PluginServerConnectionFailException(String message) { + super(message); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerManagerException.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerManagerException.java new file mode 100644 index 0000000..c253b80 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerManagerException.java @@ -0,0 +1,14 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver.exception; + +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; + +public class PluginServerManagerException extends AntChainBridgeRelayerException { + public PluginServerManagerException(String message, Throwable cause) { + super(RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, message, cause); + } + + public PluginServerManagerException(String message) { + super(RelayerErrorCodeEnum.CORE_PLUGIN_SERVER_ERROR, message); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerRegistrationFailException.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerRegistrationFailException.java new file mode 100644 index 0000000..a9bbd24 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/types/pluginserver/exception/PluginServerRegistrationFailException.java @@ -0,0 +1,11 @@ +package com.alipay.antchain.bridge.relayer.core.types.pluginserver.exception; + +public class PluginServerRegistrationFailException extends PluginServerManagerException { + public PluginServerRegistrationFailException(String message, Throwable cause) { + super(message, cause); + } + + public PluginServerRegistrationFailException(String message) { + super(message); + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/PluginServerUtils.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/PluginServerUtils.java new file mode 100644 index 0000000..8dc1248 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/PluginServerUtils.java @@ -0,0 +1,56 @@ +package com.alipay.antchain.bridge.relayer.core.utils; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessage; +import com.alipay.antchain.bridge.commons.core.base.CrossChainMessageReceipt; +import sun.security.util.DerValue; +import sun.security.util.HostnameChecker; +import sun.security.x509.X500Name; + +public class PluginServerUtils { + + public static String getPluginServerCertX509CommonName(String x509Cert) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(x509Cert.getBytes())); + DerValue derValue = HostnameChecker.getSubjectX500Name(c) + .findMostSpecificAttribute(X500Name.commonName_oid); + return derValue.getAsString(); + } catch (Exception e) { + throw new RuntimeException("failed to get common name from x509 cert of pluginserver", e); + } + } + + public static com.alipay.antchain.bridge.pluginserver.service.CrossChainMessageReceipt convertFromCommonReceipt(CrossChainMessageReceipt receipt) { + return com.alipay.antchain.bridge.pluginserver.service.CrossChainMessageReceipt.newBuilder() + .setTxhash(receipt.getTxhash()) + .setConfirmed(receipt.isConfirmed()) + .build(); + } + + public static CrossChainMessage convertFromGRpcCrossChainMessage(com.alipay.antchain.bridge.pluginserver.service.CrossChainMessage crossChainMessage) { + return CrossChainMessage.createCrossChainMessage( + CrossChainMessage.CrossChainMessageType.parseFromValue(crossChainMessage.getType().getNumber()), + crossChainMessage.getProvableData().getHeight(), + crossChainMessage.getProvableData().getTimestamp(), + crossChainMessage.getProvableData().getBlockHash().toByteArray(), + crossChainMessage.getMessage().toByteArray(), + crossChainMessage.getProvableData().getLedgerData().toByteArray(), + crossChainMessage.getProvableData().getProof().toByteArray(), + crossChainMessage.getProvableData().getTxHash().toByteArray() + ); + } + + public 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()); + + return receipt; + } +} diff --git a/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/ProcessUtils.java b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/ProcessUtils.java new file mode 100644 index 0000000..f890a38 --- /dev/null +++ b/r-core/src/main/java/com/alipay/antchain/bridge/relayer/core/utils/ProcessUtils.java @@ -0,0 +1,22 @@ +/* + * 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.relayer.core.utils; + +public class ProcessUtils { + + +} diff --git a/r-core/src/main/proto/admingrpc.proto b/r-core/src/main/proto/admingrpc.proto new file mode 100644 index 0000000..6057aa3 --- /dev/null +++ b/r-core/src/main/proto/admingrpc.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.alipay.mychain.oracle.servicemanager.grpcserver.grpc.admin"; +option java_outer_classname = "OSManagerGrpcServerOuter"; +option objc_class_prefix = "OSManagerGrpcServer"; + +package osmanagergrpcserver; + +/** +* 管控接口对象 +*/ +// 管控请求 +message AdminRequest { + // commandNamespace + string commandNamespace = 1; + // 管理命令 + string command = 2; + // 参数 + repeated string args = 3; +} + +// 管控结果 +message AdminResponse { + // 是否执行 + bool success = 1; + // 执行结果 + string result = 2; + // error msg + string errorMsg = 3; +} + +// The greeting service definition. +service AdministratorService { + + // 管控请求 + rpc adminRequest (AdminRequest) returns (AdminResponse) { + }; + +} diff --git a/r-core/src/main/proto/pluginserver.proto b/r-core/src/main/proto/pluginserver.proto new file mode 100644 index 0000000..121e796 --- /dev/null +++ b/r-core/src/main/proto/pluginserver.proto @@ -0,0 +1,237 @@ +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; + } +} + +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 SetProtocolRequest { + string protocolAddress = 1; + string protocolType = 2; +} + +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; +} + +// basic messages. +// same as project `antchain-bridge-commons` +message CrossChainMessageReceipt { + string txhash = 1; + bool confirmed = 2; + bool successful = 3; + string errorMsg = 4; +} + +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; + } +} + +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 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; +} \ No newline at end of file diff --git a/r-core/src/main/resources/relayer_node_peer.wsdl b/r-core/src/main/resources/relayer_node_peer.wsdl new file mode 100644 index 0000000..a13b36e --- /dev/null +++ b/r-core/src/main/resources/relayer_node_peer.wsdl @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/r-core/src/main/resources/relayer_node_peer.xsd b/r-core/src/main/resources/relayer_node_peer.xsd new file mode 100644 index 0000000..5a26f0a --- /dev/null +++ b/r-core/src/main/resources/relayer_node_peer.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/AnchorProcessEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/AnchorProcessEntity.java index 11cdbb6..4870aea 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/AnchorProcessEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/AnchorProcessEntity.java @@ -20,12 +20,14 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @TableName("anchor_process") @AllArgsConstructor +@NoArgsConstructor public class AnchorProcessEntity extends BaseEntity { @TableField("blockchain_product") diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/BlockchainEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/BlockchainEntity.java index b524456..dd5c3cd 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/BlockchainEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/BlockchainEntity.java @@ -37,7 +37,7 @@ public class BlockchainEntity extends BaseEntity { private String alias; @TableField("description") - private String desc; + private String description; @TableField("properties") private byte[] properties; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/CrossChainMsgACLEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/CrossChainMsgACLEntity.java index 431e58d..869e87b 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/CrossChainMsgACLEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/CrossChainMsgACLEntity.java @@ -48,5 +48,5 @@ public class CrossChainMsgACLEntity extends BaseEntity { private String grantIdHex; @TableField("is_deleted") - private int isDeleted; + private Integer isDeleted; } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainCertEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainCertEntity.java index f50ed90..2adc71c 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainCertEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainCertEntity.java @@ -35,11 +35,14 @@ public class DomainCertEntity extends BaseEntity { @TableField("instance") private String blockchainId; - @TableField("cert_pk_hash") - private String certPkHash; + @TableField("subject_oid") + private byte[] subjectOid; - @TableField("issuer_pk_hash") - private String issuerPkHash; + @TableField("issuer_oid") + private byte[] issuerOid; + + @TableField("domain_space") + private String domainSpace; @TableField("domain_cert") private byte[] domainCert; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainSpaceCertEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainSpaceCertEntity.java index 5f32ca5..ac2c9fb 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainSpaceCertEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/DomainSpaceCertEntity.java @@ -29,6 +29,9 @@ public class DomainSpaceCertEntity extends BaseEntity { @TableField("domain_space") private String domainSpace; + @TableField("parent_space") + private String parentSpace; + @TableField("description") private String desc; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/RelayerNodeEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/RelayerNodeEntity.java index a6acbcd..ca26b49 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/RelayerNodeEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/RelayerNodeEntity.java @@ -28,8 +28,11 @@ public class RelayerNodeEntity extends BaseEntity { @TableField("node_id") private String nodeId; - @TableField("node_public_key") - private String nodePublicKey; + @TableField("node_crosschain_cert") + private String nodeCrossChainCert; + + @TableField("node_sig_algo") + private String nodeSigAlgo; @TableField("domains") private String domains; @@ -37,6 +40,9 @@ public class RelayerNodeEntity extends BaseEntity { @TableField("endpoints") private String endpoints; + @TableField("blockchain_content") + private String blockchainContent; + @TableField("properties") private byte[] properties; } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgArchiveEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgArchiveEntity.java index 53ae6a1..97c7730 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgArchiveEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgArchiveEntity.java @@ -27,13 +27,13 @@ @TableName("sdp_msg_archive") public class SDPMsgArchiveEntity extends BaseEntity { @TableField("auth_msg_id") - private int authMsgId; + private Integer authMsgId; @TableField("version") - private int version; + private Integer version; @TableField("atomic") - private boolean atomic; + private Boolean atomic; @TableField("sender_blockchain_product") private String senderBlockchainProduct; @@ -75,7 +75,7 @@ public class SDPMsgArchiveEntity extends BaseEntity { private String txHash; @TableField("tx_success") - private int txSuccess; + private Boolean txSuccess; @TableField("tx_fail_reason") private String txFailReason; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgPoolEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgPoolEntity.java index ea26b61..6d263c9 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgPoolEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/SDPMsgPoolEntity.java @@ -27,13 +27,13 @@ @TableName("sdp_msg_pool") public class SDPMsgPoolEntity extends BaseEntity { @TableField("auth_msg_id") - private int authMsgId; + private Integer authMsgId; @TableField("version") - private int version; + private Integer version; @TableField("atomic") - private boolean atomic; + private Boolean atomic; @TableField("sender_blockchain_product") private String senderBlockchainProduct; @@ -75,7 +75,7 @@ public class SDPMsgPoolEntity extends BaseEntity { private String txHash; @TableField("tx_success") - private boolean txSuccess; + private Boolean txSuccess; @TableField("tx_fail_reason") private String txFailReason; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/UCPPoolEntity.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/UCPPoolEntity.java index 9aed387..104a59b 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/UCPPoolEntity.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/entities/UCPPoolEntity.java @@ -37,7 +37,7 @@ public class UCPPoolEntity extends BaseEntity { private String blockchainId; @TableField("version") - private int version; + private Integer version; @TableField("src_domain") private String srcDomain; @@ -49,13 +49,13 @@ public class UCPPoolEntity extends BaseEntity { private String txHash; @TableField("ledger_time") - private long ledgerTime; + private Long ledgerTime; @TableField("udag_path") private String udagPath; @TableField("protocol_type") - private int protocolType; + private Integer protocolType; @TableField("raw_message") private byte[] rawMessage; @@ -67,7 +67,7 @@ public class UCPPoolEntity extends BaseEntity { private byte[] tpProof; @TableField("from_network") - private boolean fromNetwork; + private Boolean fromNetwork; @TableField("relayer_id") private String relayerId; diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/AuthMsgPoolMapper.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/AuthMsgPoolMapper.java index 5bd19e8..6406eb8 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/AuthMsgPoolMapper.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/AuthMsgPoolMapper.java @@ -25,7 +25,7 @@ public interface AuthMsgPoolMapper extends BaseMapper { - void saveAuthMessages(List authMsgWrappers); + int saveAuthMessages(List authMsgWrappers); long lastInsertId(); diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/BlockchainMapper.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/BlockchainMapper.java index 82e9cf6..a758efa 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/BlockchainMapper.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/mapper/BlockchainMapper.java @@ -18,8 +18,11 @@ import com.alipay.antchain.bridge.relayer.dal.entities.BlockchainEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; public interface BlockchainMapper extends BaseMapper { void insertBlockchain(String product, String blockchainId, String alias, String description, byte[] properties); + + BlockchainEntity queryBlockchainByDomain(@Param("domain") String domain); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBCDNSRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBCDNSRepository.java new file mode 100644 index 0000000..e77c320 --- /dev/null +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBCDNSRepository.java @@ -0,0 +1,26 @@ +/* + * 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.relayer.dal.repository; + +import com.alipay.antchain.bridge.relayer.commons.model.DomainSpaceCertWrapper; + +public interface IBCDNSRepository { + + boolean hasDomainSpaceCert(String domainSpace); + + void saveDomainSpaceCert(DomainSpaceCertWrapper domainSpaceCertWrapper); +} diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBlockchainRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBlockchainRepository.java index 5f1a3fd..f27eaf9 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBlockchainRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IBlockchainRepository.java @@ -18,12 +18,17 @@ import java.util.List; +import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.AnchorProcessHeights; import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; public interface IBlockchainRepository { Long getAnchorProcessHeight(String product, String blockchainId, String heightType); + AnchorProcessHeights getAnchorProcessHeights(String product, String blockchainId); + void setAnchorProcessHeight(String product, String blockchainId, String heightType, Long height); void saveBlockchainMeta(BlockchainMeta blockchainMeta); @@ -32,5 +37,21 @@ public interface IBlockchainRepository { List getAllBlockchainMeta(); + List getBlockchainMetaByState(BlockchainStateEnum state); + + BlockchainMeta getBlockchainMetaByDomain(String domain); + + boolean hasBlockchain(String domain); + + List getBlockchainMetaByPluginServerId(String pluginServerId); + BlockchainMeta getBlockchainMeta(String product, String blockchainId); + + boolean hasBlockchain(String product, String blockchainId); + + String getBlockchainDomain(String product, String blockchainId); + + List getBlockchainDomainsByState(BlockchainStateEnum state); + + DomainCertWrapper getDomainCert(String domain); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ICrossChainMessageRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ICrossChainMessageRepository.java index 8f82d4c..c7e410b 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ICrossChainMessageRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ICrossChainMessageRepository.java @@ -17,6 +17,7 @@ package com.alipay.antchain.bridge.relayer.dal.repository; import java.util.List; +import java.util.concurrent.locks.Lock; import com.alipay.antchain.bridge.relayer.commons.constant.AuthMsgProcessStateEnum; import com.alipay.antchain.bridge.relayer.commons.constant.SDPMsgProcessStateEnum; @@ -33,6 +34,8 @@ public interface ICrossChainMessageRepository { long putAuthMessageWithIdReturned(AuthMsgWrapper authMsgWrapper); + int putAuthMessages(List authMsgWrappers); + void putSDPMessage(SDPMsgWrapper sdpMsgWrapper); boolean updateAuthMessage(AuthMsgWrapper authMsgWrapper); @@ -53,6 +56,8 @@ public interface ICrossChainMessageRepository { List peekSDPMessages(String receiverBlockchainProduct, String receiverBlockchainId, SDPMsgProcessStateEnum processState, int limit); + List peekTxFinishedSDPMessageIds(String receiverBlockchainProduct, String receiverBlockchainId, int limit); + long countSDPMessagesByState(String receiverBlockchainProduct, String receiverBlockchainId, SDPMsgProcessStateEnum processState); int archiveAuthMessages(List authMsgIds); @@ -62,4 +67,6 @@ public interface ICrossChainMessageRepository { int deleteAuthMessages(List authMsgIds); int deleteSDPMessages(List ids); + + Lock getSessionLock(String session); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IPluginServerRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IPluginServerRepository.java index ddcd400..9b6201c 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IPluginServerRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IPluginServerRepository.java @@ -17,6 +17,7 @@ package com.alipay.antchain.bridge.relayer.dal.repository; import java.util.List; +import java.util.concurrent.locks.Lock; import com.alipay.antchain.bridge.relayer.commons.constant.PluginServerStateEnum; import com.alipay.antchain.bridge.relayer.commons.model.PluginServerDO; @@ -41,4 +42,6 @@ public interface IPluginServerRepository { List getDomainsServingOfPluginServer(String psId); PluginServerInfo getPluginServerInfo(String psId); + + Lock getHeartbeatLock(String psId); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IRelayerNetworkRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IRelayerNetworkRepository.java index 23a23e1..495ceab 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IRelayerNetworkRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/IRelayerNetworkRepository.java @@ -32,16 +32,22 @@ public interface IRelayerNetworkRepository { RelayerNetwork.Item getNetworkItem(String networkId, String domain, String nodeId); + RelayerNetwork.Item getNetworkItem(String domain); + void addNetworkItem(String networkId, String domain, String nodeId, RelayerNodeSyncStateEnum syncState); boolean updateNetworkItem(String networkId, String domain, String nodeId, RelayerNodeSyncStateEnum syncState); Map getNetworkItems(String networkId); + boolean hasNetworkItem(String networkId, String domain, String nodeId); + List getAllNetworks(); RelayerNetwork getRelayerNetwork(String networkId); + RelayerNetwork getRelayerNetworkByDomain(String domain); + String getRelayerNodeIdForDomain(String domain); void addRelayerNode(RelayerNodeInfo nodeInfo); @@ -52,5 +58,7 @@ public interface IRelayerNetworkRepository { RelayerNodeInfo getRelayerNode(String nodeId); + boolean hasRelayerNode(String nodeId); + List getAllRelayerHealthInfo(); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ISystemConfigRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ISystemConfigRepository.java index e66e0e6..286c85f 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ISystemConfigRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/ISystemConfigRepository.java @@ -16,6 +16,7 @@ package com.alipay.antchain.bridge.relayer.dal.repository; +import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; @@ -30,4 +31,8 @@ public interface ISystemConfigRepository { void setSystemConfig(String key, String value); Lock getDistributedLockForDeployTask(String product, String blockchainId); + + List getLocalEndpoints(); + + String getDefaultNetworkId(); } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BCDNSRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BCDNSRepository.java new file mode 100644 index 0000000..81f3395 --- /dev/null +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BCDNSRepository.java @@ -0,0 +1,58 @@ +/* + * 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.relayer.dal.repository.impl; + +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.DomainSpaceCertWrapper; +import com.alipay.antchain.bridge.relayer.dal.entities.DomainSpaceCertEntity; +import com.alipay.antchain.bridge.relayer.dal.mapper.DomainSpaceCertMapper; +import com.alipay.antchain.bridge.relayer.dal.repository.IBCDNSRepository; +import com.alipay.antchain.bridge.relayer.dal.utils.ConvertUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Component; + +@Component +public class BCDNSRepository implements IBCDNSRepository { + + @Resource + private DomainSpaceCertMapper domainSpaceCertMapper; + + @Override + public boolean hasDomainSpaceCert(String domainSpace) { + return domainSpaceCertMapper.exists( + new LambdaQueryWrapper() + .eq(DomainSpaceCertEntity::getDomainSpace, domainSpace) + ); + } + + @Override + public void saveDomainSpaceCert(DomainSpaceCertWrapper domainSpaceCertWrapper) { + try { + domainSpaceCertMapper.insert(ConvertUtil.convertFromDomainSpaceCertWrapper(domainSpaceCertWrapper)); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_DOMAIN_SPACE_ERROR, + e, + "failed to insert domain space certificate for space {}", + domainSpaceCertWrapper.getDomainSpace() + ); + } + } +} diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainIdleDCache.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainIdleDCache.java new file mode 100644 index 0000000..869b03e --- /dev/null +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainIdleDCache.java @@ -0,0 +1,194 @@ +package com.alipay.antchain.bridge.relayer.dal.repository.impl; + +import java.util.Map; +import javax.annotation.Resource; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RBucket; +import org.redisson.api.RedissonClient; +import org.redisson.client.codec.StringCodec; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 区块链空闲状态分布式缓存 + * + *
    + *     该状态用于优化,当区块链空闲没有请求流量时,os相关的任务处理可以减低执行频率,减低系统负担。
    + *
    + *     该状态记录几个值
    + *
    + *     1. lastAMReceiveTime:最后一笔am消息的接收时间
    + *     2. lastAMProcessTime:最后一笔am消息的处理时间
    + *     3. lastAMResponseTime:最后一笔am消息提交回执的接收时间
    + *     4. lastOracleReceiveTime:最后一笔oracle请求的接收时间
    + *
    + *     1. lastEmptyAMPoolTime:最后一次am poll为空的时间
    + *     2. lastEmptyAMSendQueueTime:最后一次am send queue为空的时间
    + *     3. lastEmptyAMArchiveTime:am archive为空的时间
    + *     4. lastEmptyOraclePoolTime:最后一次oracle poll为空的时间
    + *     5. lastEmptyOracleCommitterTime:最后一次oracle poll committer为空的时间
    + *
    + *     由于并发任务有乱序问题,且避免加悲观锁,使用无锁方案,设定一个乐观的脏读区间,可以设置为3S
    + *
    + *     即,AM Process停止轮询pool表时,条件为 lastEmptyAMPoolTime - lastAMReceiveTime > 3S
    + * 
    + */ +@Component +@Slf4j +public class BlockchainIdleDCache { + + private static final String LAST_AM_RECEIVE_TIME = "last_am_receive_time"; + private static final String LAST_AM_RESPONSE_TIME = "last_am_response_time"; + private static final String LAST_AM_PROCESS_TIME = "last_am_process_time"; + private static final String LAST_ORACLE_RECEIVE_TIME = "last_oracle_receive_time"; + private static final String LAST_EMPTY_AM_POOL_TIME = "last_empty_am_pool_time"; + private static final String LAST_EMPTY_AM_SEND_QUEUE_TIME = "last_empty_am_send_queue_time"; + private static final String LAST_EMPTY_AM_ARCHIVE_TIME = "last_empty_am_archive_time"; + private static final String LAST_EMPTY_ORACLE_POOL_TIME = "last_empty_oracle_pool_time"; + private static final String LAST_EMPTY_ORACLE_COMMITTER_TIME = "last_empty_oracle_committer_time"; + + private static final int COUNT_LIMIT = 1 << 8; + + /** + * 对每个(func,product,blockchainID)记一个计数器,当整除COUNT_LIMIT的时候 + * 重置并无视空闲时间,执行任务。 + * 这是为了防止出现数据库有请求但是relayer处于空闲的情况。 + */ + private final Map counterMap = MapUtil.newConcurrentHashMap(); + + @Value("${relayer.blockchain.idle.time_limit:10000}") + private long idleTime; + + @Resource + private RedissonClient redisson; + + public void setLastAMReceiveTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_AM_RECEIVE_TIME, System.currentTimeMillis()); + } + + public void setLastAMResponseTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_AM_RESPONSE_TIME, System.currentTimeMillis()); + } + + public void setLastAMProcessTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_AM_PROCESS_TIME, System.currentTimeMillis()); + } + + public void setLastOracleReceiveTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_ORACLE_RECEIVE_TIME, System.currentTimeMillis()); + } + + public void setLastEmptyAMPoolTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_EMPTY_AM_POOL_TIME, System.currentTimeMillis()); + } + + public void setLastEmptyAMSendQueueTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_EMPTY_AM_SEND_QUEUE_TIME, System.currentTimeMillis()); + } + + public void setLastEmptyAMArchiveTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_EMPTY_AM_ARCHIVE_TIME, System.currentTimeMillis()); + } + + public void setLastEmptyOraclePoolTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_EMPTY_ORACLE_POOL_TIME, System.currentTimeMillis()); + } + + public void setLastEmptyOracleCommitterTime(String product, String blockchainId) { + setIdleState(product, blockchainId, LAST_EMPTY_ORACLE_COMMITTER_TIME, System.currentTimeMillis()); + } + + /** + * 从counterMap获取对应的数值,检查是否整除COUNT_LIMIT, + * 若整除,则返回false,代表本次不检查是否空闲,直接执行任务即可。 + * + * @param funcName 函数名字 + * @param product 链的框架 + * @param blockchainId 链ID + * @return 是否空闲 + */ + private boolean checkCounter(String funcName, String product, String blockchainId) { + String key = String.format("%s-%s:%s", funcName, product, blockchainId); + int cnt = counterMap.getOrDefault(key, 1) % COUNT_LIMIT; + counterMap.put(key, cnt + 1); + return cnt != 0; + } + + public boolean ifAMProcessIdle(String product, String blockchainId) { + + long lastAMReceiveTime = getIdleState(product, blockchainId, LAST_AM_RECEIVE_TIME); + long lastEmptyAMPoolTime = getIdleState(product, blockchainId, LAST_EMPTY_AM_POOL_TIME); + if (lastEmptyAMPoolTime - lastAMReceiveTime > idleTime) { + return checkCounter("ifAMProcessIdle", product, blockchainId); + } + return false; + } + + public boolean ifAMCommitterIdle(String product, String blockchainId) { + + long lastAMProcessTime = getIdleState(product, blockchainId, LAST_AM_PROCESS_TIME); + long lastEmptyAMSendQueueTime = getIdleState(product, blockchainId, LAST_EMPTY_AM_SEND_QUEUE_TIME); + + if(lastEmptyAMSendQueueTime - lastAMProcessTime > idleTime) { + return checkCounter("ifAMCommitterIdle", product, blockchainId); + } + return false; + } + + public boolean ifAMArchiveIdle(String product, String blockchainId) { + + long lastAMResponseTime = getIdleState(product, blockchainId, LAST_AM_RESPONSE_TIME); + long lastEmptyAMArchiveTime = getIdleState(product, blockchainId, LAST_EMPTY_AM_ARCHIVE_TIME); + + if(lastEmptyAMArchiveTime - lastAMResponseTime > idleTime) { + return checkCounter("ifAMArchiveIdle", product, blockchainId); + } + return false; + } + + public boolean ifOracleProcessIdle(String product, String blockchainId) { + + long lastOracleReceiveTime = getIdleState(product, blockchainId, LAST_ORACLE_RECEIVE_TIME); + long lastEmptyOraclePoolTime = getIdleState(product, blockchainId, LAST_EMPTY_ORACLE_POOL_TIME); + + if(lastEmptyOraclePoolTime - lastOracleReceiveTime > idleTime) { + return checkCounter("ifOracleProcessIdle", product, blockchainId); + } + return false; + } + + public boolean ifOracleCommitterIdle(String product, String blockchainId) { + + long lastOracleReceiveTime = getIdleState(product, blockchainId, LAST_ORACLE_RECEIVE_TIME); + long lastEmptyOracleCommitterTime = getIdleState(product, blockchainId, LAST_EMPTY_ORACLE_COMMITTER_TIME); + + if(lastEmptyOracleCommitterTime - lastOracleReceiveTime > idleTime) { + return checkCounter("ifOracleCommitterIdle", product, blockchainId); + } + return false; + } + + private long getIdleState(String product, String blockchainId, String type) { + + RBucket bucket = redisson.getBucket(genKey(product, blockchainId, type), StringCodec.INSTANCE); + String rawTime = bucket.get(); + if (StrUtil.isEmpty(rawTime)) { + return 0; + } + return Long.parseLong(rawTime); + } + + private void setIdleState(String product, String blockchainId, String type, long time) { + log.info("set idle state : {}-{}-{}-{} ", product, blockchainId, type, time); + redisson.getBucket(genKey(product, blockchainId, type), StringCodec.INSTANCE) + .set(Long.valueOf(time).toString()); + } + + public static String genKey(String product, String blockchainId, String type) { + return product + "^" + blockchainId + "^" + type; + } + +} diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainRepository.java index d3be909..5e9ab82 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/BlockchainRepository.java @@ -18,58 +18,136 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Resource; +import cn.hutool.cache.Cache; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.BlockchainStateEnum; import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; import com.alipay.antchain.bridge.relayer.commons.exception.RelayerErrorCodeEnum; import com.alipay.antchain.bridge.relayer.commons.model.AnchorProcessHeights; import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DomainCertWrapper; import com.alipay.antchain.bridge.relayer.dal.entities.AnchorProcessEntity; import com.alipay.antchain.bridge.relayer.dal.entities.BaseEntity; import com.alipay.antchain.bridge.relayer.dal.entities.BlockchainEntity; +import com.alipay.antchain.bridge.relayer.dal.entities.DomainCertEntity; +import com.alipay.antchain.bridge.relayer.dal.mapper.AnchorProcessMapper; +import com.alipay.antchain.bridge.relayer.dal.mapper.DomainCertMapper; import com.alipay.antchain.bridge.relayer.dal.repository.IBlockchainRepository; import com.alipay.antchain.bridge.relayer.dal.service.BlockchainService; -import com.alipay.antchain.bridge.relayer.dal.service.IAnchorProcessService; import com.alipay.antchain.bridge.relayer.dal.utils.ConvertUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.redisson.client.codec.ByteArrayCodec; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; @Component public class BlockchainRepository implements IBlockchainRepository { @Resource - private IAnchorProcessService anchorProcessService; + private AnchorProcessMapper anchorProcessMapper; @Resource private BlockchainService blockchainService; @Resource - private RedissonClient redissonClient; + private DomainCertMapper domainCertMapper; - @Value("${anchor.process.cache.heights.ttl:240000}") + @Resource + private RedissonClient redisson; + + @Value("${relayer.dal.blockchain.heights_cache.ttl:240000}") private long ttlForHeightsCache; - @Value("${anchor.process.cache.flush.period:0}") + @Value("${relayer.dal.blockchain.heights_cache.flush_period:3000}") private long flushPeriodForHeightsCache; + @Resource + private Cache domainCertWrapperCache; + + @Resource + private Cache blockchainMetaCache; + + @Resource(name = "blockchainIdToDomainCache") + private Cache blockchainIdToDomainCache; + + @Resource + private TransactionTemplate transactionTemplate; + @Override public Long getAnchorProcessHeight(String product, String blockchainId, String heightType) { - Long height = getHeightFromCache(product, blockchainId, heightType); - if (ObjectUtil.isNull(height) || ObjectUtil.equals(0L, height)) { - height = getAnchorProcessHeightFromDB(product, blockchainId, heightType); - setHeightToCache(product, blockchainId, heightType, height); + try { + Long height = getHeightFromCache(product, blockchainId, heightType); + if (ObjectUtil.isNull(height) || ObjectUtil.equals(0L, height)) { + height = getAnchorProcessHeightFromDB(product, blockchainId, heightType); + setHeightToCache(product, blockchainId, heightType, height); + } + return height; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_ANCHOR_HEIGHTS_ERROR, + e, + "failed to get heights for ( product: {}, blockchain id: {}, type: {} )", + product, blockchainId, heightType + ); + } + + } + + @Override + public AnchorProcessHeights getAnchorProcessHeights(String product, String blockchainId) { + try { + AnchorProcessHeights heights = getAnchorProcessHeightsFromCache(product, blockchainId); + if (ObjectUtil.isNull(heights)) { + List entities = anchorProcessMapper + .selectList( + new LambdaQueryWrapper() + .select( + ListUtil.toList( + AnchorProcessEntity::getTask, + AnchorProcessEntity::getBlockHeight, + BaseEntity::getGmtModified + ) + ).eq(AnchorProcessEntity::getProduct, product) + .eq(AnchorProcessEntity::getBlockchainId, blockchainId) + ); + if (ObjectUtil.isEmpty(entities)) { + return null; + } + heights = new AnchorProcessHeights(product, blockchainId); + for (AnchorProcessEntity entity : entities) { + heights.getProcessHeights().put(entity.getTask(), entity.getBlockHeight()); + heights.getModifiedTimeMap().put(entity.getTask(), entity.getGmtModified().getTime()); + } + + return heights; + } + + for (Map.Entry entry : heights.getProcessHeights().entrySet()) { + heights.getModifiedTimeMap().put(entry.getKey(), heights.getLastUpdateTime()); + } + return heights; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_ANCHOR_HEIGHTS_ERROR, + e, + "failed to get heights for ( product: {}, blockchain id: {} )", + product, blockchainId + ); } - return height; } @Override @@ -111,6 +189,7 @@ public void saveBlockchainMeta(BlockchainMeta blockchainMeta) { blockchainMeta.getDesc(), blockchainMeta.getProperties().encode() ); + blockchainMetaCache.put(blockchainMeta.getBlockchainId(), blockchainMeta); } catch (Exception e) { throw new AntChainBridgeRelayerException( RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, @@ -124,15 +203,21 @@ public void saveBlockchainMeta(BlockchainMeta blockchainMeta) { @Override public boolean updateBlockchainMeta(BlockchainMeta blockchainMeta) { - return blockchainService.getBaseMapper().update( - BlockchainEntity.builder() - .alias(blockchainMeta.getAlias()) - .desc(blockchainMeta.getDesc()) - .properties(blockchainMeta.getProperties().encode()) - .build(), - new LambdaUpdateWrapper() - .eq(BlockchainEntity::getBlockchainId, "test") - ) == 1; + if ( + blockchainService.getBaseMapper().update( + BlockchainEntity.builder() + .alias(blockchainMeta.getAlias()) + .description(blockchainMeta.getDesc()) + .properties(blockchainMeta.getProperties().encode()) + .build(), + new LambdaUpdateWrapper() + .eq(BlockchainEntity::getBlockchainId, blockchainMeta.getBlockchainId()) + ) == 1 + ) { + blockchainMetaCache.put(blockchainMeta.getBlockchainId(), blockchainMeta); + return true; + } + return false; } @Override @@ -142,8 +227,111 @@ public List getAllBlockchainMeta() { .collect(Collectors.toList()); } + @Override + public List getBlockchainMetaByState(BlockchainStateEnum state) { + try { + List blockchainEntities = blockchainService.lambdaQuery() + .like(BlockchainEntity::getProperties, state.getCode()) + .list(); + if (ObjectUtil.isEmpty(blockchainEntities)) { + return ListUtil.empty(); + } + return blockchainEntities.stream() + .map(ConvertUtil::convertFromBlockchainEntity) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + e, + "failed to query blockchains from DB with state {}", state.getCode() + ); + } + } + + @Override + public BlockchainMeta getBlockchainMetaByDomain(String domain) { + try { + if (blockchainMetaCache.containsKey(getDomainBlockchainMetaCacheKey(domain))) { + return blockchainMetaCache.get(getDomainBlockchainMetaCacheKey(domain)); + } + + BlockchainEntity blockchainEntity = blockchainService.getBaseMapper().queryBlockchainByDomain(domain); + if (ObjectUtil.isNull(blockchainEntity)) { + return null; + } + BlockchainMeta blockchainMeta = ConvertUtil.convertFromBlockchainEntity(blockchainEntity); + blockchainMetaCache.put(getDomainBlockchainMetaCacheKey(domain), blockchainMeta); + if (StrUtil.isAllNotEmpty(domain, blockchainEntity.getBlockchainId())) { + blockchainIdToDomainCache.put(blockchainEntity.getBlockchainId(), domain); + } + return blockchainMeta; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + e, + "failed to query blockchains from DB with domain {}", domain + ); + } + } + + @Override + public boolean hasBlockchain(String domain) { + String blockchainId; + if (domainCertWrapperCache.containsKey(domain)) { + blockchainId = domainCertWrapperCache.get(domain).getBlockchainId(); + } else { + DomainCertEntity domainCertEntity = domainCertMapper.selectOne( + new LambdaQueryWrapper() + .select(ListUtil.toList(DomainCertEntity::getBlockchainId)) + .eq(DomainCertEntity::getDomain, domain) + ); + if (ObjectUtil.isNull(domainCertEntity)) { + return false; + } + blockchainId = domainCertEntity.getBlockchainId(); + } + + if (StrUtil.isAllNotEmpty(domain, blockchainId)) { + blockchainIdToDomainCache.put(blockchainId, domain); + } + + if (blockchainMetaCache.containsKey(getDomainBlockchainMetaCacheKey(domain))) { + return true; + } + + return blockchainService.getBaseMapper().exists( + new LambdaQueryWrapper() + .eq(BlockchainEntity::getBlockchainId, blockchainId) + ); + } + + @Override + public List getBlockchainMetaByPluginServerId(String pluginServerId) { + try { + List blockchainEntities = blockchainService.lambdaQuery() + .like(BlockchainEntity::getProperties, pluginServerId) + .list(); + if (ObjectUtil.isEmpty(blockchainEntities)) { + return ListUtil.empty(); + } + return blockchainEntities.stream() + .map(ConvertUtil::convertFromBlockchainEntity) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + e, + "failed to query blockchains from DB with plugin server id {}", pluginServerId + ); + } + } + @Override public BlockchainMeta getBlockchainMeta(String product, String blockchainId) { + if (blockchainMetaCache.containsKey(blockchainId)) { + return blockchainMetaCache.get(blockchainId); + } + BlockchainEntity blockchainEntity = blockchainService.lambdaQuery() .eq(BlockchainEntity::getProduct, product) .eq(BlockchainEntity::getBlockchainId, blockchainId) @@ -152,14 +340,136 @@ public BlockchainMeta getBlockchainMeta(String product, String blockchainId) { BlockchainEntity::getProduct, BlockchainEntity::getBlockchainId, BlockchainEntity::getAlias, - BlockchainEntity::getDesc, + BlockchainEntity::getDescription, BlockchainEntity::getProperties ) ).one(); if (ObjectUtil.isNull(blockchainEntity)) { return null; } - return ConvertUtil.convertFromBlockchainEntity(blockchainEntity); + BlockchainMeta blockchainMeta = ConvertUtil.convertFromBlockchainEntity(blockchainEntity); + blockchainMetaCache.put(blockchainId, blockchainMeta); + return blockchainMeta; + } + + @Override + public boolean hasBlockchain(String product, String blockchainId) { + try { + if (blockchainMetaCache.containsKey(blockchainId)) { + return true; + } + + return blockchainService.exists( + new LambdaQueryWrapper() + .eq(BlockchainEntity::getProduct, product) + .eq(BlockchainEntity::getBlockchainId, blockchainId) + ); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + String.format( + "failed to query blockchain existence from DB for ( product: %s, blockchain id: %s )", + product, blockchainId + ), e + ); + } + } + + @Override + public String getBlockchainDomain(String product, String blockchainId) { + try { + if (blockchainIdToDomainCache.containsKey(blockchainId)) { + return blockchainIdToDomainCache.get(blockchainId); + } + DomainCertEntity entity = domainCertMapper.selectOne( + new LambdaQueryWrapper() + .eq(DomainCertEntity::getProduct, product) + .eq(DomainCertEntity::getBlockchainId, blockchainId) + ); + if (ObjectUtil.isNull(entity)) { + return ""; + } + + blockchainIdToDomainCache.put(blockchainId, entity.getDomain()); + + return entity.getDomain(); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + String.format( + "failed to query blockchain existence from DB for ( product: %s, blockchain id: %s )", + product, blockchainId + ), e + ); + } + } + + @Override + @Transactional + public List getBlockchainDomainsByState(BlockchainStateEnum state) { + try { + List blockchainEntities = blockchainService.lambdaQuery() + .select(ListUtil.toList(BlockchainEntity::getBlockchainId)) + .like(BlockchainEntity::getProperties, state.getCode()) + .list(); + if (ObjectUtil.isEmpty(blockchainEntities)) { + return ListUtil.empty(); + } + + List domainCertEntities = domainCertMapper.selectList( + new LambdaQueryWrapper() + .select(ListUtil.toList(DomainCertEntity::getDomain)) + .in( + DomainCertEntity::getBlockchainId, + blockchainEntities.stream() + .map(BlockchainEntity::getBlockchainId) + .collect(Collectors.toList()) + ) + ); + if (ObjectUtil.isEmpty(domainCertEntities)) { + return ListUtil.empty(); + } + + return domainCertEntities.stream() + .map(DomainCertEntity::getDomain) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + e, + "failed to query blockchain domain list from DB for state {}", + state.getCode() + ); + } + } + + @Override + public DomainCertWrapper getDomainCert(String domain) { + try { + if (domainCertWrapperCache.containsKey(domain)) { + return domainCertWrapperCache.get(domain); + } + + DomainCertEntity entity = domainCertMapper.selectOne( + new LambdaQueryWrapper() + .eq(DomainCertEntity::getDomain, domain) + ); + if (ObjectUtil.isNull(entity)) { + return null; + } + + DomainCertWrapper domainCertWrapper = ConvertUtil.convertFromDomainCertEntity(entity); + domainCertWrapperCache.put(domain, domainCertWrapper); + + return domainCertWrapper; + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_BLOCKCHAIN_ERROR, + e, + "failed to query domain cert from DB for domain {}", + domain + ); + } } private void flushAnchorProcessHeights(AnchorProcessHeights heights) { @@ -169,27 +479,53 @@ private void flushAnchorProcessHeights(AnchorProcessHeights heights) { } private void flushHeight(String product, String blockchainId, String heightType, Long height) { - if ( - !anchorProcessService.lambdaUpdate() - .set(AnchorProcessEntity::getBlockHeight, height) - .set(BaseEntity::getGmtModified, new Date()) - .eq(AnchorProcessEntity::getProduct, product) - .eq(AnchorProcessEntity::getBlockchainId, blockchainId) - .eq(AnchorProcessEntity::getTask, heightType) - .update() - ) { - throw new RuntimeException(String.format("update ( height type: %s, value: %d ) failed", heightType, height)); - } + AnchorProcessEntity entity = new AnchorProcessEntity(); + entity.setBlockHeight(height); + transactionTemplate.execute( + new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + if ( + anchorProcessMapper.exists( + new LambdaQueryWrapper() + .eq(AnchorProcessEntity::getProduct, product) + .eq(AnchorProcessEntity::getBlockchainId, blockchainId) + .eq(AnchorProcessEntity::getTask, heightType) + ) + ) { + if ( + anchorProcessMapper.update( + entity, + new LambdaUpdateWrapper() + .eq(AnchorProcessEntity::getProduct, product) + .eq(AnchorProcessEntity::getBlockchainId, blockchainId) + .eq(AnchorProcessEntity::getTask, heightType) + ) != 1 + ) { + throw new RuntimeException(String.format("update ( height type: %s, value: %d ) failed", heightType, height)); + } + return; + } + + entity.setBlockchainId(blockchainId); + entity.setProduct(product); + entity.setTask(heightType); + if (anchorProcessMapper.insert(entity) != 1) { + throw new RuntimeException(String.format("insert ( height type: %s, value: %d ) failed", heightType, height)); + } + } + } + ); } private Long getAnchorProcessHeightFromDB(String product, String blockchainId, String heightType) { - return anchorProcessService.lambdaQuery() - .select(true, ListUtil.of(AnchorProcessEntity::getBlockHeight)) - .eq(AnchorProcessEntity::getProduct, product) - .eq(AnchorProcessEntity::getBlockchainId, blockchainId) - .eq(AnchorProcessEntity::getTask, heightType) - .oneOpt() - .map(AnchorProcessEntity::getBlockHeight).orElse(0L); + return anchorProcessMapper.selectOne( + new LambdaQueryWrapper() + .select(ListUtil.toList(AnchorProcessEntity::getBlockHeight)) + .eq(AnchorProcessEntity::getProduct, product) + .eq(AnchorProcessEntity::getBlockchainId, blockchainId) + .eq(AnchorProcessEntity::getTask, heightType) + ).getBlockHeight(); } private Long getHeightFromCache(String product, String blockchainId, String heightKey) { @@ -201,7 +537,7 @@ private Long getHeightFromCache(String product, String blockchainId, String heig } private AnchorProcessHeights getAnchorProcessHeightsFromCache(String product, String blockchainId) { - RBucket bucket = redissonClient.getBucket(AnchorProcessHeights.getKey(product, blockchainId), ByteArrayCodec.INSTANCE); + RBucket bucket = redisson.getBucket(AnchorProcessHeights.getKey(product, blockchainId), ByteArrayCodec.INSTANCE); byte[] rawHeights = bucket.get(); if (ObjectUtil.isEmpty(rawHeights)) { return null; @@ -219,7 +555,11 @@ private void setHeightToCache(String product, String blockchainId, String height } private void setAnchorProcessHeightsToCache(AnchorProcessHeights heights) { - redissonClient.getBucket(AnchorProcessHeights.getKey(heights.getProduct(), heights.getBlockchainId()), ByteArrayCodec.INSTANCE) + redisson.getBucket(AnchorProcessHeights.getKey(heights.getProduct(), heights.getBlockchainId()), ByteArrayCodec.INSTANCE) .set(heights.encode(), Duration.of(ttlForHeightsCache, ChronoUnit.MILLIS)); } + + private String getDomainBlockchainMetaCacheKey(String domain) { + return "%domain%" + domain; + } } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMessageRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMessageRepository.java index dfe3227..9dd6237 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMessageRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMessageRepository.java @@ -17,9 +17,11 @@ package com.alipay.antchain.bridge.relayer.dal.repository.impl; import java.util.List; +import java.util.concurrent.locks.Lock; import java.util.stream.Collectors; import javax.annotation.Resource; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.ObjectUtil; @@ -44,12 +46,15 @@ import com.alipay.antchain.bridge.relayer.dal.utils.ConvertUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.redisson.api.RedissonClient; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @Component public class CrossChainMessageRepository implements ICrossChainMessageRepository { + private static final String CCMSG_SESSION_LOCK = "CCMSG_SESSION_LOCK:"; + @Resource private UCPPoolMapper ucpPoolMapper; @@ -62,6 +67,9 @@ public class CrossChainMessageRepository implements ICrossChainMessageRepository @Resource private AuthMsgArchiveMapper authMsgArchiveMapper; + @Resource + private RedissonClient redisson; + @Transactional @Override public long putAuthMessageWithIdReturned(AuthMsgWrapper authMsgWrapper) { @@ -79,6 +87,21 @@ public long putAuthMessageWithIdReturned(AuthMsgWrapper authMsgWrapper) { } } + @Override + public int putAuthMessages(List authMsgWrappers) { + try { + return authMsgPoolMapper.saveAuthMessages(authMsgWrappers); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_CROSSCHAIN_MSG_ERROR, + String.format( + "failed to put multiple am for chain %s", + authMsgWrappers.isEmpty() ? "empty list" : authMsgWrappers.get(0).getDomain() + ), e + ); + } + } + @Override public void putSDPMessage(SDPMsgWrapper sdpMsgWrapper) { try { @@ -223,7 +246,8 @@ public List updateSDPMessageResults(List results) { throw new RuntimeException( StrUtil.format( "failed to update sdp message with txhash {} from ( product: {} , blockchain_id: {} )", - result.getTxHash(), result.getReceiveProduct(), result.getReceiveBlockchainId() + result.getTxHash(), result.getReceiveProduct(), result.getReceiveBlockchainId(), + e ) ); } @@ -336,6 +360,39 @@ public List peekSDPMessages(String receiverBlockchainProduct, Str } } + @Override + public List peekTxFinishedSDPMessageIds(String receiverBlockchainProduct, String receiverBlockchainId, int limit) { + try { + List entities = sdpMsgPoolMapper.selectList( + new LambdaQueryWrapper() + .select(ListUtil.toList(BaseEntity::getId, SDPMsgPoolEntity::getAuthMsgId)) + .eq(SDPMsgPoolEntity::getReceiverBlockchainProduct, receiverBlockchainProduct) + .eq(SDPMsgPoolEntity::getReceiverBlockchainId, receiverBlockchainId) + .and( + wrapper -> wrapper.eq(SDPMsgPoolEntity::getProcessState, SDPMsgProcessStateEnum.TX_SUCCESS) + .or( + wrapper1 -> wrapper1.eq(SDPMsgPoolEntity::getProcessState, SDPMsgProcessStateEnum.TX_FAILED) + ) + ).last("limit " + limit) + ); + if (ObjectUtil.isEmpty(entities)) { + return ListUtil.empty(); + } + + return entities.stream() + .map(sdpMsgPoolEntity -> BeanUtil.copyProperties(sdpMsgPoolEntity, SDPMsgWrapper.class)) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_CROSSCHAIN_MSG_ERROR, + StrUtil.format( + "failed to peek tx_pending sdp messages ids for chain (product: {}, blockchain_id: {})", + receiverBlockchainProduct, receiverBlockchainId + ), e + ); + } + } + @Override public long countSDPMessagesByState(String receiverBlockchainProduct, String receiverBlockchainId, SDPMsgProcessStateEnum processState) { try { @@ -415,4 +472,13 @@ public int deleteSDPMessages(List ids) { ); } } + + @Override + public Lock getSessionLock(String session) { + return redisson.getLock(getCCMsgSessionLock(session)); + } + + private String getCCMsgSessionLock(String session) { + return String.format("%s%s", CCMSG_SESSION_LOCK, session); + } } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMsgACLRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMsgACLRepository.java index 145af33..67b84c8 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMsgACLRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/CrossChainMsgACLRepository.java @@ -93,9 +93,9 @@ public boolean checkItem(CrossChainMsgACLItem item) { new LambdaQueryWrapper() .select(ListUtil.of(CrossChainMsgACLEntity::getIsDeleted)) .eq(CrossChainMsgACLEntity::getOwnerDomain, item.getOwnerDomain()) - .eq(CrossChainMsgACLEntity::getOwnerIdHex, item.getOwnerIdentityHex()) + .eq(CrossChainMsgACLEntity::getOwnerIdHex, item.getOwnerIdentityHex().toLowerCase()) .eq(CrossChainMsgACLEntity::getGrantDomain, item.getGrantDomain()) - .eq(CrossChainMsgACLEntity::getGrantIdHex, item.getGrantIdentityHex()) + .eq(CrossChainMsgACLEntity::getGrantIdHex, item.getGrantIdentityHex().toLowerCase()) .or( wrapper -> wrapper.eq(CrossChainMsgACLEntity::getOwnerDomain, item.getOwnerDomain()) .eq(CrossChainMsgACLEntity::getGrantDomain, item.getGrantDomain()) diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/PluginServerRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/PluginServerRepository.java index 709c208..e800c08 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/PluginServerRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/PluginServerRepository.java @@ -17,6 +17,7 @@ package com.alipay.antchain.bridge.relayer.dal.repository.impl; import java.util.List; +import java.util.concurrent.locks.Lock; import javax.annotation.Resource; @@ -34,14 +35,20 @@ import com.alipay.antchain.bridge.relayer.dal.utils.ConvertUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.redisson.api.RedissonClient; import org.springframework.stereotype.Component; @Component public class PluginServerRepository implements IPluginServerRepository { + private static final String PLUGIN_SERVER_HEARTBEAT_LOCK_PREFIX = "plugin_server_heartbeat_lock-"; + @Resource private PluginServerObjectsMapper pluginServerObjectsMapper; + @Resource + private RedissonClient redisson; + @Override public void insertNewPluginServer(PluginServerDO pluginServerDO) { try { @@ -226,4 +233,13 @@ public PluginServerInfo getPluginServerInfo(String psId) { ); } } + + @Override + public Lock getHeartbeatLock(String psId) { + return redisson.getLock(getHeartbeatLockKey(psId)); + } + + private static String getHeartbeatLockKey(String psId) { + return String.format("%s%s", PLUGIN_SERVER_HEARTBEAT_LOCK_PREFIX, psId); + } } diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/RelayerNetworkRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/RelayerNetworkRepository.java index b2533c8..39ab93a 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/RelayerNetworkRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/RelayerNetworkRepository.java @@ -120,6 +120,24 @@ public RelayerNetwork.Item getNetworkItem(String networkId, String domain, Strin } } + @Override + public RelayerNetwork.Item getNetworkItem(String domain) { + try { + return ConvertUtil.convertFromRelayerNetworkEntity( + relayerNetworkMapper.selectOne( + new LambdaQueryWrapper() + .eq(RelayerNetworkEntity::getDomain, domain) + ) + ); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_RELAYER_NETWORK_ERROR, + StrUtil.format("failed to get network item by domain: {} ", domain), + e + ); + } + } + @Override public void addNetworkItem(String networkId, String domain, String nodeId, RelayerNodeSyncStateEnum syncState) { try { @@ -189,6 +207,25 @@ public Map getNetworkItems(String networkId) { } } + @Override + public boolean hasNetworkItem(String networkId, String domain, String nodeId) { + try { + return relayerNetworkMapper.exists( + new LambdaQueryWrapper() + .eq(RelayerNetworkEntity::getNetworkId, networkId) + .eq(RelayerNetworkEntity::getDomain, domain) + .eq(RelayerNetworkEntity::getNodeId, nodeId) + ); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_RELAYER_NETWORK_ERROR, + e, + "failed to check if network item ( network_id: {}, domain: {}, node_id: {}) exists", + networkId, domain, nodeId + ); + } + } + @Override public List getAllNetworks() { try { @@ -251,11 +288,49 @@ public RelayerNetwork getRelayerNetwork(String networkId) { } } + @Override + @Transactional + public RelayerNetwork getRelayerNetworkByDomain(String domain) { + try { + RelayerNetworkEntity entity = relayerNetworkMapper.selectOne( + new LambdaQueryWrapper() + .select(ListUtil.toList(RelayerNetworkEntity::getNetworkId)) + .eq(RelayerNetworkEntity::getDomain, domain) + ); + if (ObjectUtil.isNull(entity) || StrUtil.isEmpty(entity.getNetworkId())) { + return null; + } + + List entities = relayerNetworkMapper.selectList( + new LambdaQueryWrapper() + .eq(RelayerNetworkEntity::getNetworkId, entity.getNetworkId()) + ); + if (ObjectUtil.isEmpty(entities)) { + return null; + } + + RelayerNetwork network = new RelayerNetwork(entity.getNetworkId()); + entities.forEach( + e -> network.addItem(e.getDomain(), e.getNodeId(), e.getSyncState()) + ); + + return network; + + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_RELAYER_NETWORK_ERROR, + "failed to get network by domain " + domain, + e + ); + } + } + @Override public String getRelayerNodeIdForDomain(String domain) { try { RelayerNetworkEntity entity = relayerNetworkMapper.selectOne( new LambdaQueryWrapper() + .select(ListUtil.toList(RelayerNetworkEntity::getNodeId)) .eq(RelayerNetworkEntity::getDomain, domain) ); if (ObjectUtil.isNull(entity)) { @@ -324,6 +399,22 @@ public void updateRelayerNodeProperty(String nodeId, String key, String value) { } } + @Override + public boolean hasRelayerNode(String nodeId) { + try { + return relayerNodeMapper.exists( + new LambdaQueryWrapper() + .eq(RelayerNodeEntity::getNodeId, nodeId) + ); + } catch (Exception e) { + throw new AntChainBridgeRelayerException( + RelayerErrorCodeEnum.DAL_RELAYER_NODE_ERROR, + "failed to check if relayer node exist: " + nodeId, + e + ); + } + } + @Override public RelayerNodeInfo getRelayerNode(String nodeId) { try { diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/ScheduleRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/ScheduleRepository.java index 54ede16..6f2c8d6 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/ScheduleRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/ScheduleRepository.java @@ -47,7 +47,7 @@ public class ScheduleRepository implements IScheduleRepository { private static final String SCHEDULE_LOCK_KEY = "RELAYER_SCHEDULE_LOCK"; @Resource - private RedissonClient redissonClient; + private RedissonClient redisson; @Resource private DTActiveNodeMapper dtActiveNodeMapper; @@ -57,7 +57,7 @@ public class ScheduleRepository implements IScheduleRepository { @Override public Lock getDispatchLock() { - return redissonClient.getLock(SCHEDULE_LOCK_KEY); + return redisson.getLock(SCHEDULE_LOCK_KEY); } @Override @@ -66,7 +66,11 @@ public void activate(String nodeId, String nodeIp) { try { if ( 1 != dtActiveNodeMapper.update( - DTActiveNodeEntity.builder().build(), + DTActiveNodeEntity.builder() + .nodeId(nodeId) + .nodeIp(nodeIp) + .state(DTActiveNodeStateEnum.ONLINE) + .build(), new LambdaUpdateWrapper() .eq(DTActiveNodeEntity::getNodeId, nodeId) ) @@ -160,7 +164,7 @@ public void batchUpdateDTTasks(List tasks) { task -> dtTaskMapper.update( DTTaskEntity.builder() .nodeId(task.getNodeId()) - .timeSlice(new Date(task.getTimeSlice())) + .timeSlice(new Date(task.getStartTime())) .build(), new LambdaUpdateWrapper() .eq(DTTaskEntity::getTaskType, task.getTaskType()) diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/SystemConfigRepository.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/SystemConfigRepository.java index 0cb3edf..732689e 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/SystemConfigRepository.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/repository/impl/SystemConfigRepository.java @@ -16,10 +16,12 @@ package com.alipay.antchain.bridge.relayer.dal.repository.impl; +import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import javax.annotation.Resource; +import cn.hutool.cache.Cache; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -32,20 +34,38 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class SystemConfigRepository implements ISystemConfigRepository { + /** + * relayer的endpoints信息 + */ + public static final String LOCAL_ENDPOINTS_KEY = "local_endpoints"; + + public static final String DEFAULT_RELAYER_NETWORK_ID_KEY = "default_network_id"; + @Resource - private RedissonClient redissonClient; + private RedissonClient redisson; @Resource private SystemConfigMapper systemConfigMapper; + @Value("${relayer.network.id:1}") + private String networkIdInConfig; + + @Resource(name = "systemConfigCache") + private Cache systemConfigCache; + @Override public String getSystemConfig(String key) { try { + if (systemConfigCache.containsKey(key)) { + return systemConfigCache.get(key); + } + SystemConfigEntity entity = systemConfigMapper.selectOne( new LambdaQueryWrapper() .select(ListUtil.of(SystemConfigEntity::getConfValue)) @@ -54,6 +74,7 @@ public String getSystemConfig(String key) { if (ObjectUtil.isNull(entity)) { return StrUtil.EMPTY; } + systemConfigCache.put(key, entity.getConfValue()); return entity.getConfValue(); } catch (Exception e) { throw new AntChainBridgeRelayerException( @@ -67,7 +88,7 @@ public String getSystemConfig(String key) { @Override public boolean hasSystemConfig(String key) { try { - return systemConfigMapper.exists( + return systemConfigCache.containsKey(key) || systemConfigMapper.exists( new LambdaQueryWrapper() .eq(SystemConfigEntity::getConfKey, key) ); @@ -83,12 +104,15 @@ public boolean hasSystemConfig(String key) { @Override public void setSystemConfig(Map configs) { try { - configs.forEach((key, value) -> systemConfigMapper.insert( - SystemConfigEntity.builder() - .confKey(key) - .confValue(value) - .build() - )); + configs.forEach((key, value) -> { + systemConfigMapper.insert( + SystemConfigEntity.builder() + .confKey(key) + .confValue(value) + .build() + ); + systemConfigCache.put(key, value); + }); } catch (Exception e) { throw new AntChainBridgeRelayerException( RelayerErrorCodeEnum.DAL_SYSTEM_CONFIG_ERROR, @@ -117,6 +141,7 @@ public void setSystemConfig(String key, String value) { .build() ); } + systemConfigCache.put(key, value); } catch (Exception e) { throw new AntChainBridgeRelayerException( RelayerErrorCodeEnum.DAL_SYSTEM_CONFIG_ERROR, @@ -128,7 +153,21 @@ public void setSystemConfig(String key, String value) { @Override public Lock getDistributedLockForDeployTask(String product, String blockchainId) { - return redissonClient.getLock(getDeployLockKey(product, blockchainId)); + return redisson.getLock(getDeployLockKey(product, blockchainId)); + } + + @Override + public List getLocalEndpoints() { + String val = getSystemConfig(LOCAL_ENDPOINTS_KEY); + if (StrUtil.isEmpty(val)) { + return ListUtil.empty(); + } + return StrUtil.split(val, "^"); + } + + @Override + public String getDefaultNetworkId() { + return StrUtil.emptyToDefault(getSystemConfig(DEFAULT_RELAYER_NETWORK_ID_KEY), networkIdInConfig); } private String getDeployLockKey(String product, String blockchainId) { diff --git a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/utils/ConvertUtil.java b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/utils/ConvertUtil.java index eee529e..d67b070 100644 --- a/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/utils/ConvertUtil.java +++ b/r-dal/src/main/java/com/alipay/antchain/bridge/relayer/dal/utils/ConvertUtil.java @@ -16,13 +16,21 @@ package com.alipay.antchain.bridge.relayer.dal.utils; +import java.io.IOException; import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; +import com.alipay.antchain.bridge.commons.bcdns.AbstractCrossChainCertificate; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateFactory; +import com.alipay.antchain.bridge.commons.bcdns.CrossChainCertificateTypeEnum; +import com.alipay.antchain.bridge.commons.bcdns.DomainNameCredentialSubject; import com.alipay.antchain.bridge.commons.core.am.*; import com.alipay.antchain.bridge.commons.core.base.*; import com.alipay.antchain.bridge.commons.core.sdp.SDPMessageV1; @@ -49,7 +57,7 @@ public static BlockchainEntity convertFromBlockchainMeta(BlockchainMeta blockcha .blockchainId(blockchainMeta.getBlockchainId()) .product(blockchainMeta.getProduct()) .properties(blockchainMeta.getProperties().encode()) - .desc(blockchainMeta.getDesc()) + .description(blockchainMeta.getDesc()) .alias(blockchainMeta.getAlias()) .build(); } @@ -59,7 +67,7 @@ public static BlockchainMeta convertFromBlockchainEntity(BlockchainEntity blockc blockchainEntity.getProduct(), blockchainEntity.getBlockchainId(), blockchainEntity.getAlias(), - blockchainEntity.getDesc(), + blockchainEntity.getDescription(), blockchainEntity.getProperties() ); } @@ -167,7 +175,7 @@ public static UniformCrosschainPacketContext convertFromUCPPoolEntity(UCPPoolEnt context.setBlockchainId(ucpPoolEntity.getBlockchainId()); context.setUdagPath(ucpPoolEntity.getUdagPath()); context.setProcessState(ucpPoolEntity.getProcessState()); - context.setFromNetwork(ucpPoolEntity.isFromNetwork()); + context.setFromNetwork(ucpPoolEntity.getFromNetwork()); context.setRelayerId(ucpPoolEntity.getRelayerId()); return context; @@ -181,7 +189,7 @@ public static SDPMsgWrapper convertFromSDPMsgPoolEntity(SDPMsgPoolEntity sdpMsgP wrapper.setReceiverAMClientContract(sdpMsgPoolEntity.getReceiverAMClientContract()); wrapper.setProcessState(sdpMsgPoolEntity.getProcessState()); wrapper.setTxHash(sdpMsgPoolEntity.getTxHash()); - wrapper.setTxSuccess(sdpMsgPoolEntity.isTxSuccess()); + wrapper.setTxSuccess(sdpMsgPoolEntity.getTxSuccess()); wrapper.setTxFailReason(sdpMsgPoolEntity.getTxFailReason()); if (sdpMsgPoolEntity.getVersion() == SDPMessageV1.MY_VERSION) { @@ -195,7 +203,7 @@ public static SDPMsgWrapper convertFromSDPMsgPoolEntity(SDPMsgPoolEntity sdpMsgP message.setSequence(sdpMsgPoolEntity.getMsgSequence().intValue()); message.setTargetDomain(new CrossChainDomain(sdpMsgPoolEntity.getReceiverDomainName())); message.setTargetIdentity(new CrossChainIdentity(HexUtil.decodeHex(sdpMsgPoolEntity.getReceiverId()))); - message.setAtomic(sdpMsgPoolEntity.isAtomic()); + message.setAtomic(sdpMsgPoolEntity.getAtomic()); wrapper.setSdpMessage(message); } else { throw new RuntimeException("Invalid version of sdp message: " + sdpMsgPoolEntity.getVersion()); @@ -256,27 +264,43 @@ public static RelayerNetwork.Item convertFromRelayerNetworkEntity(RelayerNetwork } public static RelayerNodeInfo convertFromRelayerNodeEntity(RelayerNodeEntity entity) { - RelayerNodeInfo nodeInfo = new RelayerNodeInfo(); - - nodeInfo.setNodeId(entity.getNodeId()); - nodeInfo.setNodePublicKey(entity.getNodePublicKey()); - nodeInfo.setDomains(StrUtil.split(entity.getDomains(), "^")); - nodeInfo.setEndpoints(StrUtil.split(entity.getEndpoints(), "^")); - nodeInfo.setProperties(RelayerNodeInfo.RelayerNodeProperties.decodeFromJson(new String(entity.getProperties()))); - + RelayerNodeInfo nodeInfo = new RelayerNodeInfo( + entity.getNodeId(), + CrossChainCertificateFactory.createCrossChainCertificate( + Base64.decode(entity.getNodeCrossChainCert()) + ), + entity.getNodeSigAlgo(), + StrUtil.split(entity.getEndpoints(), "^"), + StrUtil.split(entity.getDomains(), "^") + ); + if (ObjectUtil.isNotEmpty(entity.getBlockchainContent())) { + nodeInfo.setRelayerBlockchainContent( + RelayerBlockchainContent.decodeFromJson(entity.getBlockchainContent()) + ); + } + nodeInfo.setProperties( + RelayerNodeInfo.RelayerNodeProperties.decodeFromJson( + new String(entity.getProperties()) + ) + ); return nodeInfo; } - public static RelayerNodeEntity convertFromRelayerNodeInfo(RelayerNodeInfo nodeInfo) { + public static RelayerNodeEntity convertFromRelayerNodeInfo(RelayerNodeInfo nodeInfo) throws IOException { RelayerNodeEntity entity = new RelayerNodeEntity(); entity.setNodeId(nodeInfo.getNodeId()); entity.setDomains( nodeInfo.getDomains().stream().reduce((s1, s2) -> StrUtil.join("^", s1, s2)).orElse("") ); - entity.setNodePublicKey(nodeInfo.getNodePublicKey()); + entity.setNodeCrossChainCert(Base64.encode(nodeInfo.getRelayerCrossChainCertificate().encode())); entity.setEndpoints( nodeInfo.getEndpoints().stream().reduce((s1, s2) -> StrUtil.join("^", s1, s2)).orElse("") ); + + entity.setBlockchainContent( + ObjectUtil.isNull(nodeInfo.getRelayerBlockchainContent()) ? + StrUtil.EMPTY : nodeInfo.getRelayerBlockchainContent().encodeToJson() + ); entity.setProperties(nodeInfo.marshalProperties().getBytes()); return entity; @@ -297,7 +321,7 @@ public static DistributedTask convertFromDTTaskEntity(DTTaskEntity entity) { distributedTask.setTaskType(entity.getTaskType()); distributedTask.setBlockchainId(entity.getBlockchainId()); distributedTask.setBlockchainProduct(entity.getProduct()); - distributedTask.setTimeSlice(entity.getTimeSlice().getTime()); + distributedTask.setStartTime(entity.getTimeSlice().getTime()); distributedTask.setExt(entity.getExt()); return distributedTask; } @@ -308,7 +332,7 @@ public static DTTaskEntity convertFromDistributedTask(DistributedTask task) { entity.setBlockchainId(task.getBlockchainId()); entity.setProduct(task.getBlockchainProduct()); entity.setNodeId(task.getNodeId()); - entity.setTimeSlice(new Date(task.getTimeSlice())); + entity.setTimeSlice(new Date(task.getStartTime())); entity.setExt(task.getExt()); return entity; } @@ -327,11 +351,11 @@ public static CrossChainMsgACLEntity convertFromCrossChainMsgACLItem(CrossChainM entity.setOwnerDomain(item.getOwnerDomain()); entity.setOwnerId(item.getOwnerIdentity()); - entity.setOwnerIdHex(item.getOwnerIdentityHex()); + entity.setOwnerIdHex(item.getOwnerIdentityHex().toLowerCase()); entity.setGrantDomain(item.getGrantDomain()); entity.setGrantId(item.getGrantIdentity()); - entity.setGrantIdHex(item.getGrantIdentityHex()); + entity.setGrantIdHex(item.getGrantIdentityHex().toLowerCase()); entity.setIsDeleted(item.getIsDeleted()); @@ -355,4 +379,51 @@ public static CrossChainMsgACLItem convertFromCrossChainMsgACLItem(CrossChainMsg return item; } + + public static DomainCertWrapper convertFromDomainCertEntity(DomainCertEntity entity) { + AbstractCrossChainCertificate crossChainCertificate = CrossChainCertificateFactory.createCrossChainCertificate( + entity.getDomainCert() + ); + Assert.equals( + CrossChainCertificateTypeEnum.DOMAIN_NAME_CERTIFICATE, + crossChainCertificate.getType() + ); + DomainNameCredentialSubject domainNameCredentialSubject = DomainNameCredentialSubject.decode( + crossChainCertificate.getCredentialSubject() + ); + + return new DomainCertWrapper( + crossChainCertificate, + domainNameCredentialSubject, + entity.getProduct(), + entity.getBlockchainId(), + entity.getDomain(), + entity.getDomainSpace() + ); + } + + public static DomainCertEntity convertFromDomainCertWrapper(DomainCertWrapper wrapper) { + DomainCertEntity entity = new DomainCertEntity(); + entity.setDomainCert(wrapper.getCrossChainCertificate().encode()); + entity.setDomain(wrapper.getDomain()); + entity.setProduct(wrapper.getBlockchainProduct()); + entity.setBlockchainId(wrapper.getBlockchainId()); + entity.setSubjectOid( + wrapper.getDomainNameCredentialSubject().getApplicant().encode() + ); + entity.setIssuerOid( + wrapper.getCrossChainCertificate().getIssuer().encode() + ); + entity.setDomainSpace(wrapper.getDomainSpace()); + return entity; + } + + public static DomainSpaceCertEntity convertFromDomainSpaceCertWrapper(DomainSpaceCertWrapper wrapper) { + DomainSpaceCertEntity entity = new DomainSpaceCertEntity(); + entity.setDomainSpace(wrapper.getDomainSpace()); + entity.setParentSpace(wrapper.getParentDomainSpace()); + entity.setDesc(wrapper.getDesc()); + entity.setDomainSpaceCert(wrapper.getDomainSpaceCert().encode()); + return entity; + } } diff --git a/r-dal/src/main/resources/mapper/BlockchainMapper.xml b/r-dal/src/main/resources/mapper/BlockchainMapper.xml index 2e4d710..bec842c 100644 --- a/r-dal/src/main/resources/mapper/BlockchainMapper.xml +++ b/r-dal/src/main/resources/mapper/BlockchainMapper.xml @@ -9,6 +9,10 @@ blockchain + + domain_cert + + id, product, @@ -27,4 +31,14 @@ values (#{product}, #{blockchainId}, #{alias}, #{description}, #{properties}); + + diff --git a/r-engine/pom.xml b/r-engine/pom.xml index 780ff72..a98040d 100644 --- a/r-engine/pom.xml +++ b/r-engine/pom.xml @@ -22,10 +22,6 @@ com.alipay.antchain.bridge r-core - - com.alipay.antchain.bridge - r-service - \ No newline at end of file diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/DistributedTaskEngine.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/DistributedTaskEngine.java new file mode 100644 index 0000000..365eff5 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/DistributedTaskEngine.java @@ -0,0 +1,114 @@ +package com.alipay.antchain.bridge.relayer.engine; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.engine.core.*; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@Getter +@Order(1) +public class DistributedTaskEngine implements ApplicationRunner { + + @Resource + private Activator activator; + + @Resource + private Dispatcher dispatcher; + + @Resource + private Duty duty; + + @Resource + private Cleaner cleaner; + + @Resource + private ScheduledExecutorService distributedTaskEngineScheduleThreadsPool; + + @Value("${relayer.engine.schedule.activate.period:1000}") + private long activatePeriod; + + @Value("${relayer.engine.schedule.cleaner.period:30000}") + private long cleanPeriod; + + @Value("${relayer.engine.schedule.dispatcher.period:1000}") + private long dispatchPeriod; + + @Value("${relayer.engine.schedule.duty.period:100}") + private long dutyPeriod; + + @Override + public void run(ApplicationArguments args) throws Exception { + + log.info("Starting DistributedTask Engine Now"); + + // schedule activator + distributedTaskEngineScheduleThreadsPool.scheduleWithFixedDelay( + () -> { + try { + activator.activate(); + } catch (Throwable e) { + log.error("schedule activator failed.", e); + } + }, + 0, + activatePeriod, + TimeUnit.MILLISECONDS + ); + + // schedule dispatcher + distributedTaskEngineScheduleThreadsPool.scheduleWithFixedDelay( + () -> { + try { + dispatcher.dispatch(); + } catch (Throwable e) { + log.error("schedule dispatch failed.", e); + } + }, + 0, + dispatchPeriod, + TimeUnit.MILLISECONDS + ); + + // schedule duty + distributedTaskEngineScheduleThreadsPool.scheduleWithFixedDelay( + () -> { + try { + duty.duty(); + } catch (Throwable e) { + log.error("schedule duty failed.", e); + } + }, + 0, + dutyPeriod, + TimeUnit.MILLISECONDS + ); + + // schedule cleaner + distributedTaskEngineScheduleThreadsPool.scheduleWithFixedDelay( + () -> { + try { + cleaner.clean(); + } catch (Throwable e) { + log.error("schedule cleaner failed.", e); + } + }, + 0, + cleanPeriod, + TimeUnit.MILLISECONDS + ); + } + + public void shutdown() { + distributedTaskEngineScheduleThreadsPool.shutdown(); + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Activator.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Activator.java new file mode 100644 index 0000000..6304112 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Activator.java @@ -0,0 +1,30 @@ +package com.alipay.antchain.bridge.relayer.engine.core; + +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.dal.repository.IScheduleRepository; +import org.springframework.stereotype.Component; + +/** + * Activator负责分布式节点的定时心跳,往全局DB定时登记心跳,表示节点活性 + */ +@Component +public class Activator { + + @Resource + private IScheduleRepository scheduleRepository; + + @Resource + private ScheduleContext scheduleContext; + + + /** + * 往全局DB定时登记心跳,表示节点活性 + */ + public void activate() { + scheduleRepository.activate( + scheduleContext.getNodeId(), + scheduleContext.getNodeIp() + ); + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Cleaner.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Cleaner.java new file mode 100644 index 0000000..6789ce0 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Cleaner.java @@ -0,0 +1,73 @@ +package com.alipay.antchain.bridge.relayer.engine.core; + +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.PluginServerStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.IBBCPluginManager; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.core.types.blockchain.BlockchainClientPool; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * Cleaner负责清理区块链 + */ +@Component +@Slf4j +public class Cleaner { + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private IBBCPluginManager bbcPluginManager; + + @Resource + private BlockchainClientPool blockchainClientPool; + + public void clean() { + + log.debug("begin clean."); + log.debug("all running clients : {}", StrUtil.join(", ", blockchainClientPool.getAllClient())); + + List stopBlockchains = getStopBlockchains(); + + for (BlockchainMeta blockchain : stopBlockchains) { + log.info("begin to clean the stopped blockchain {}-{}", blockchain.getProduct(), blockchain.getBlockchainId()); + try { + boolean ifRunning = blockchainClientPool.hasClient(blockchain.getProduct(), blockchain.getBlockchainId()); + log.info("blockchain's client {} is running or not: {} ", blockchain.getBlockchainId(), ifRunning); + if (!ifRunning) { + continue; + } + + blockchainClientPool.shutdownClient(blockchain.getProduct(), blockchain.getBlockchainId()); + + log.info("blockchain's client {} shutdown success.", blockchain.getBlockchainId()); + } catch (Throwable e) { + log.error("clean stopped blockchain {} fail.", blockchain.getBlockchainId(), e); + } + } + } + + @Synchronized + private List getStopBlockchains() { + List blockchainMetas = blockchainManager.getAllStoppedBlockchains(); + if (ObjectUtil.isNull(blockchainMetas)) { + return ListUtil.empty(); + } + return blockchainMetas.stream().filter( + blockchainMeta -> + PluginServerStateEnum.STOP == bbcPluginManager.getPluginServerState( + blockchainMeta.getProperties().getPluginServerId() + ) + ).collect(Collectors.toList()); + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Dispatcher.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Dispatcher.java new file mode 100644 index 0000000..0c96813 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Dispatcher.java @@ -0,0 +1,247 @@ +package com.alipay.antchain.bridge.relayer.engine.core; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.stream.Collectors; +import javax.annotation.Resource; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.relayer.commons.constant.DistributedTaskTypeEnum; +import com.alipay.antchain.bridge.relayer.commons.constant.PluginServerStateEnum; +import com.alipay.antchain.bridge.relayer.commons.model.ActiveNode; +import com.alipay.antchain.bridge.relayer.commons.model.BlockchainMeta; +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.manager.bbc.IBBCPluginManager; +import com.alipay.antchain.bridge.relayer.core.manager.blockchain.IBlockchainManager; +import com.alipay.antchain.bridge.relayer.dal.repository.IScheduleRepository; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Dispatcher负责拆分区块链任务,并根据节点心跳表获取在线节点排值班表 + */ +@Component +@Slf4j +public class Dispatcher { + + @Resource + private IBlockchainManager blockchainManager; + + @Resource + private IBBCPluginManager bbcPluginManager; + + @Resource + private IScheduleRepository scheduleRepository; + + @Value("${relayer.engine.schedule.dispatcher.dt_task.time_slice:180000}") + private long timeSliceLength; + + @Value("${relayer.engine.schedule.activate.ttl:3000}") + private long nodeTimeToLive; + +// @Value("${relayer.engine.schedule.dispatcher.task_diff_map:{anchor:5, committer:3, process:2}}") +// private Map taskTypeDiffMap; + + public void dispatch() { + Lock lock = getDistributeLock(); + if (!lock.tryLock()) { + log.debug("not my dispatch lock."); + return; + } + + try { + log.info("dispatch distributed tasks now."); + + // 运行的区块链 + List runningBlockchains = getRunningBlockchains(); + if (ObjectUtil.isEmpty(runningBlockchains)) { + log.debug("empty running blockchains to dispatch"); + return; + } + + // 拆分任务 + List allTasks = splitTask(runningBlockchains); + + // 剔除已分配过时间片的任务 + List tasksToDispatch = filterTasksInTimeSlice(allTasks); + if (ObjectUtil.isEmpty(tasksToDispatch.isEmpty())) { + log.info("empty tasks to dispatch"); + return; + } + + // 获取在线节点 + List onlineNodes = getOnlineNode(); + log.info("size of online node : {}", onlineNodes.size()); + + // 给剩余任务分配时间片 + doDispatch(onlineNodes, tasksToDispatch); + } catch (Exception e) { + log.error("failed to dispatch distributed task: ", e); + } finally { + lock.unlock(); + } + } + + private Lock getDistributeLock() { + return scheduleRepository.getDispatchLock(); + } + + @Synchronized + private List getRunningBlockchains() { + + List blockchainMetas = blockchainManager.getAllServingBlockchains(); + if (ObjectUtil.isNull(blockchainMetas)) { + return ListUtil.empty(); + } + return blockchainMetas.stream().filter( + blockchainMeta -> + PluginServerStateEnum.READY == bbcPluginManager.getPluginServerState( + blockchainMeta.getProperties().getPluginServerId() + ) + ).collect(Collectors.toList()); + } + + private List splitTask(List runningBlockchains) { + return runningBlockchains.stream().map( + blockchainMeta -> + ListUtil.toList( + new DistributedTask( + DistributedTaskTypeEnum.ANCHOR_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ), + new DistributedTask( + DistributedTaskTypeEnum.COMMIT_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ), + new DistributedTask( + DistributedTaskTypeEnum.PROCESS_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ), + new DistributedTask( + DistributedTaskTypeEnum.AM_CONFIRM_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ), + new DistributedTask( + DistributedTaskTypeEnum.ARCHIVE_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ), + new DistributedTask( + DistributedTaskTypeEnum.DEPLOY_SERVICE_TASK, + blockchainMeta.getProduct(), + blockchainMeta.getBlockchainId() + ) + ) + ).reduce((a, b) -> { + a.addAll(b); + return a; + }).get(); + } + + private List filterTasksInTimeSlice(List allTasks) { + + // to map + Map allTasksMap = Maps.newHashMap(); + for (DistributedTask task : allTasks) { + allTasksMap.put(task.getUniqueTaskKey(), task); + } + + List timeSliceTasks = scheduleRepository.getAllDistributedTasks(); + // 如果是新任务,差入执行记录到DB + Map newTaskMap = Maps.newHashMap(allTasksMap); + for (DistributedTask existedTask : timeSliceTasks) { + newTaskMap.remove(existedTask.getUniqueTaskKey()); + if (!existedTask.ifFinish(timeSliceLength)) { + allTasksMap.remove(existedTask.getUniqueTaskKey()); + } + } + if (!newTaskMap.isEmpty()) { + scheduleRepository.batchInsertDTTasks(ListUtil.toList(newTaskMap.values())); + } + + return Lists.newArrayList(allTasksMap.values()); + } + + private List getOnlineNode() { + List nodes = scheduleRepository.getAllActiveNodes(); + List onlineNodes = Lists.newArrayList(); + for (ActiveNode node : nodes) { + if (node.ifActive(nodeTimeToLive)) { + onlineNodes.add(node); + } + } + return onlineNodes; + } + + private void doDispatch(List nodes, List tasks) { + Collections.shuffle(nodes); + roundRobin(nodes, tasks); + // TODO: give a better algorithm for balancing tasks + scheduleRepository.batchUpdateDTTasks(tasks); + } + +// private void averageDiffPerBlockchainForEachNode(List nodes, List tasks) { +// Map> nodeCounterMap = nodes.stream().collect(Collectors.toMap( +// ActiveNode::getNodeId, +// node -> new HashMap<>() +// )); +// Map nodeMap = nodes.stream().collect(Collectors.toMap( +// ActiveNode::getNodeId, +// node -> node +// )); +// +// for (int i = 0; i < tasks.size(); ++i) { +// +// DistributedTask task = tasks.get(i); +// int diffNum = taskTypeDiffMap.getOrDefault(task.getTaskType().getCode(), 1); +// +// getTheMinDiffSumForBlockchain(nodeCounterMap, ) +// +// ActiveNode node = nodes.get(i % nodes.size()); +// tasks.get(i).setNodeId(node.getNodeId()); +// tasks.get(i).setStartTime(System.currentTimeMillis()); +// } +// } +// +// private String getTheMinDiffSumForBlockchain(Map> nodeCounterMap, String blockchainId) { +// String nodeId = ""; +// Integer minDiff = Integer.MAX_VALUE; +// for (Map.Entry> entry : nodeCounterMap.entrySet()) { +// Integer diff = entry.getValue().getOrDefault(blockchainId, 0); +// if (diff < minDiff) { +// nodeId = entry.getKey(); +// minDiff = diff; +// } +// } +// +// return nodeId; +// } +// +// private int calculateTotalDiff(Map> nodeCounterMap, String nodeId) { +// return nodeCounterMap.entrySet().stream() +// .collect(Collectors.toMap( +// Map.Entry::getKey, +// entry -> entry.getValue().values().stream().reduce(Integer::sum).orElse(0) +// )).getOrDefault(nodeId, 0); +// } + + private void roundRobin(List nodes, List tasks) { + Collections.shuffle(tasks); + for (int i = 0; i < tasks.size(); ++i) { + ActiveNode node = nodes.get(i % nodes.size()); + tasks.get(i).setNodeId(node.getNodeId()); + tasks.get(i).setStartTime(System.currentTimeMillis()); + } + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Duty.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Duty.java new file mode 100644 index 0000000..5ab541a --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/Duty.java @@ -0,0 +1,50 @@ +package com.alipay.antchain.bridge.relayer.engine.core; + +import java.util.List; +import java.util.Map; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.constant.DistributedTaskTypeEnum; +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.dal.repository.IScheduleRepository; +import com.alipay.antchain.bridge.relayer.engine.executor.BaseScheduleTaskExecutor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Duty会定时轮询任务表,读取当前时间片属于本节点的任务,交给系列职能线程池处理 + */ +@Component +@Slf4j +public class Duty { + + @Resource + private IScheduleRepository scheduleRepository; + + @Resource + private ScheduleContext scheduleContext; + + @Value("${relayer.engine.schedule.duty.dt_task.time_slice:180000}") + private long timeSliceLength; + + @Resource + private Map scheduleTaskExecutorMap; + + public void duty() { + + // 查询本节点的时间片任务 + List tasks = scheduleRepository.getDistributedTasksByNodeId(this.scheduleContext.getNodeId()); + if (tasks.isEmpty()) { + log.debug("empty duty tasks"); + } else { + log.info("duty tasks size {}", tasks.size()); + } + + // 分配给各个职能线程池处理 + for (DistributedTask task : tasks) { + task.setTimeSliceLength(timeSliceLength); + scheduleTaskExecutorMap.get(task.getTaskType()).execute(task); + } + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/ScheduleContext.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/ScheduleContext.java new file mode 100644 index 0000000..8dfd67a --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/core/ScheduleContext.java @@ -0,0 +1,38 @@ +package com.alipay.antchain.bridge.relayer.engine.core; + +import java.net.InetAddress; +import java.util.UUID; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; + +/** + * 定时任务框架上下文 + */ +@Getter +public class ScheduleContext { + + public final static String NODE_ID_MODE_IP = "IP"; + + public final static String NODE_ID_MODE_UUID = "UUID"; + + private final String nodeIp; + + private final String nodeId; + + public ScheduleContext(String mode) { + InetAddress localAddress = NetUtil.getLocalhost(); + if (ObjectUtil.isNull(localAddress)) { + throw new RuntimeException("null local ip"); + } + this.nodeIp = localAddress.getHostAddress(); + + if (StrUtil.equalsIgnoreCase(mode, NODE_ID_MODE_IP)) { + this.nodeId = this.nodeIp; + } else { + this.nodeId = UUID.randomUUID().toString(); + } + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AnchorScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AnchorScheduleTaskExecutor.java new file mode 100644 index 0000000..9bfd614 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AnchorScheduleTaskExecutor.java @@ -0,0 +1,35 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.anchor.MultiAnchorProcessService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AnchorScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private MultiAnchorProcessService multiAnchorProcessService; + + @Autowired + public AnchorScheduleTaskExecutor(@Qualifier("anchorScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + multiAnchorProcessService.runAnchorProcess(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Throwable e) { + log.error("AnchorScheduleTaskExecutor failed, blockchainId is {}", task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ArchiveScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ArchiveScheduleTaskExecutor.java new file mode 100644 index 0000000..6c458ff --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ArchiveScheduleTaskExecutor.java @@ -0,0 +1,35 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.archive.ArchiveService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class ArchiveScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private ArchiveService archiveService; + + @Autowired + public ArchiveScheduleTaskExecutor(@Qualifier("archiveScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + archiveService.process(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Throwable e) { + log.error("ArchiveScheduleTaskExecutor failed for blockchain {}", task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AsyncDeployScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AsyncDeployScheduleTaskExecutor.java new file mode 100644 index 0000000..7c006dc --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/AsyncDeployScheduleTaskExecutor.java @@ -0,0 +1,35 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.deploy.DeployService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AsyncDeployScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private DeployService deployService; + + @Autowired + public AsyncDeployScheduleTaskExecutor(@Qualifier("deployScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + deployService.process(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Throwable e) { + log.error("AsyncDeployScheduleTaskExecutor failed blockchain {}", task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/BaseScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/BaseScheduleTaskExecutor.java new file mode 100644 index 0000000..9f28aa6 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/BaseScheduleTaskExecutor.java @@ -0,0 +1,68 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import cn.hutool.core.lang.Assert; +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.google.common.collect.Maps; +import lombok.Getter; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; + +/** + * 分布式任务基类,传入具体分布式任务,使用线程池异步执行 + */ +@Slf4j +@Getter +public abstract class BaseScheduleTaskExecutor { + + private final ExecutorService executor; + + private final Map currentTasks = Maps.newConcurrentMap(); + + public BaseScheduleTaskExecutor(ExecutorService executor) { + Assert.notNull(executor); + this.executor = executor; + } + + /** + * 分布式任务执行基类 + * + * @param task + */ + @Synchronized + public void execute(DistributedTask task) { + + // 该任务是否已经在执行 + if (currentTasks.containsKey(task.getUniqueTaskKey())) { + if (!currentTasks.get(task.getUniqueTaskKey()).isDone()) { + log.info("task is running : {}", task.getUniqueTaskKey()); + return; + } else { + log.info("task finish : {}", task.getUniqueTaskKey()); + currentTasks.remove(task.getUniqueTaskKey()); + } + } + + // 判断时间片是否结束 + if (task.ifFinish()) { + log.debug("task out of time slice : {}", task.getUniqueTaskKey()); + return; + } + + // 触发执行 + log.info("execute task : {}", task.getUniqueTaskKey()); + + Future currentTask = executor.submit(genTask(task)); + + this.currentTasks.put(task.getUniqueTaskKey(), currentTask); + } + + //******************************************* + // 子类实现 + //******************************************* + + public abstract Runnable genTask(DistributedTask task); +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/CommitterScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/CommitterScheduleTaskExecutor.java new file mode 100644 index 0000000..a3d6900 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/CommitterScheduleTaskExecutor.java @@ -0,0 +1,35 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.committer.CommitterService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class CommitterScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private CommitterService committerService; + + @Autowired + public CommitterScheduleTaskExecutor(@Qualifier("committerScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + committerService.process(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Exception e) { + log.error("CommitterScheduleTaskExecutor failed for blockchain {}", task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ProcessScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ProcessScheduleTaskExecutor.java new file mode 100644 index 0000000..00ea592 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/ProcessScheduleTaskExecutor.java @@ -0,0 +1,35 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.process.ProcessService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class ProcessScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private ProcessService processService; + + @Autowired + public ProcessScheduleTaskExecutor(@Qualifier("processScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + processService.process(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Throwable e) { + log.error("ProcessScheduleTaskExecutor failed for blockchain {}", task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/TxConfirmScheduleTaskExecutor.java b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/TxConfirmScheduleTaskExecutor.java new file mode 100644 index 0000000..a701dc5 --- /dev/null +++ b/r-engine/src/main/java/com/alipay/antchain/bridge/relayer/engine/executor/TxConfirmScheduleTaskExecutor.java @@ -0,0 +1,34 @@ +package com.alipay.antchain.bridge.relayer.engine.executor; + +import java.util.concurrent.ExecutorService; +import javax.annotation.Resource; + +import com.alipay.antchain.bridge.relayer.commons.model.DistributedTask; +import com.alipay.antchain.bridge.relayer.core.service.confirm.AMConfirmService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class TxConfirmScheduleTaskExecutor extends BaseScheduleTaskExecutor { + + @Resource + private AMConfirmService amConfirmService; + + public TxConfirmScheduleTaskExecutor(@Qualifier("confirmScheduleTaskExecutorThreadsPool") ExecutorService executorService) { + super(executorService); + } + + @Override + public Runnable genTask(DistributedTask task) { + return () -> { + try { + amConfirmService.process(task.getBlockchainProduct(), task.getBlockchainId()); + } catch (Exception e) { + log.error("failed to process am confirm task for ( product: {}, bid: {} )", + task.getBlockchainProduct(), task.getBlockchainId(), e); + } + }; + } +} diff --git a/r-server/pom.xml b/r-server/pom.xml index 3e2458a..0b21cae 100644 --- a/r-server/pom.xml +++ b/r-server/pom.xml @@ -20,7 +20,7 @@ com.alipay.antchain.bridge - r-service + r-core diff --git a/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/BaseRelayerServer.java b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/BaseRelayerServer.java new file mode 100644 index 0000000..311a834 --- /dev/null +++ b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/BaseRelayerServer.java @@ -0,0 +1,226 @@ +/* + * 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.relayer.server.network; + +import com.alipay.antchain.bridge.relayer.commons.model.RelayerNodeInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.network.request.RelayerRequestType; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * Endpoint Server基类 + */ +@Getter +@Setter +@Slf4j +@NoArgsConstructor +public abstract class BaseRelayerServer { + + private IRelayerNetworkManager relayerNetworkManager; + + private IRelayerCredentialManager relayerCredentialManager; + + private boolean isDiscoveryServer; + + private String defaultNetworkId; + + private ReceiverService receiverService; + + public BaseRelayerServer( + IRelayerNetworkManager relayerNetworkManager, + IRelayerCredentialManager relayerCredentialManager, + ReceiverService receiverService, + String defaultNetworkId, + boolean isDiscoveryServer + ) { + this.relayerNetworkManager = relayerNetworkManager; + this.relayerCredentialManager = relayerCredentialManager; + this.receiverService = receiverService; + this.defaultNetworkId = defaultNetworkId; + this.isDiscoveryServer = isDiscoveryServer; + } + + public void amRequest(String domainName, String authMsg, String udagProof, String ledgerInfo) { + + receiverService.receiveOffChainAMRequest(domainName, authMsg, udagProof, ledgerInfo); + } + + public void doHandshake(RelayerNodeInfo nodeInfo, String networkId) { +// DBTransactionManager.getInstance().execute(new TransactionCallbackWithoutResult() { +// @Override +// protected void doInTransactionWithoutResult(TransactionStatus status) { +// if (!relayerNetworkManager.addRelayerNodeWithoutDomainInfo(nodeInfo)) { +// throw new RuntimeException(String.format("[doHandshake] failed to add relayer node %s", +// nodeInfo.getNodeId())); +// } +// +// if (!relayerNetworkManager.verifyWithTrustedOracle( +// OracleserviceRuntimeCache.getInstance().getTrustedOracleCache(), nodeInfo, networkId)) { +// throw new RuntimeException(String.format("[doHandshake] failed to verifyWithTrustedOracle for relayer node %s", +// nodeInfo.getNodeId())); +// } +// +// relayerNetworkManager.addRelayerNodeProperty(nodeInfo.getNodeId(), RelayerNodeInfo.LAST_TIME_HANDSHAKE, +// Long.toString(System.currentTimeMillis())); +// +// // 现在在进行handshake之前,需要先配置好,两个relayer之间,哪些域名和合约可以互相调用,也许以后再增加ACL的策略。 +// } +// }); + } + + /** + * 获取对应domain的relayer信息。 + * + * @param domain 域名 + * @return relayer信息 + */ + public RelayerNodeInfo getRelayerForDomain(String domain) { + return this.relayerNetworkManager.getRelayerNodeInfoForDomain(domain); + } + + /** + * 向发现服务注册域名和relayer信息; + * + * @param nodeInfo + * @param networkId + */ + public void registerDomains(RelayerNodeInfo nodeInfo, String networkId) { +// DBTransactionManager.getInstance().execute(new TransactionCallbackWithoutResult() { +// @Override +// protected void doInTransactionWithoutResult(TransactionStatus status) { +// RelayerNodeInfo nodeInDB = relayerNetworkManager.getRelayerNode(nodeInfo.getNodeId()); +// if (nodeInDB != null) { +// nodeInDB.addDomains(nodeInfo.getDomains()); +// nodeInDB.endpoints = nodeInfo.getEndpoints(); +// if (!relayerNetworkManager.updateRelayerNode(nodeInDB)) { +// throw new RuntimeException(String.format("failed to addRelayerNode(nodeId: %s)", nodeInfo.getNodeId())); +// } +// } else { +// nodeInDB = nodeInfo; +// if (!relayerNetworkManager.addRelayerNode(nodeInDB)) { +// throw new RuntimeException(String.format("failed to addRelayerNode(nodeId: %s)", nodeInfo.getNodeId())); +// } +// } +// +// for (int i = 0; i < nodeInfo.getDomains().size(); i++) { +// if (relayerNetworkManager.getRelayerNodeInfoForDomain(nodeInfo.getDomains().get(i)) != null) { +// throw new RuntimeException(String.format("domain %s already registered", nodeInfo.getDomains().get(i))); +// } +// +// if (!relayerNetworkManager.addRelayerNetworkItem( +// networkId, nodeInfo.getDomains().get(i), nodeInfo.getNodeId(), RelayerNodeSyncState.init)) { +// throw new RuntimeException( +// String.format("failed to addRelayerNetworkItem(networkId: %s, domain: %s, nodeId: %s)", +// networkId, nodeInfo.getDomains().get(i), nodeInfo.getNodeId())); +// } +// } +// } +// }); + } + + /** + * 更新对应的域名。目前仅支持更新relayer的endpoints,property这些。 + * 该域名必须注册过; + * nodeId不可以更改; + * 暂时先不判断权限的问题; + * + * @param nodeInfo + */ + public void updateDomains(RelayerNodeInfo nodeInfo) { +// DBTransactionManager.getInstance().execute(new TransactionCallbackWithoutResult() { +// @Override +// protected void doInTransactionWithoutResult(TransactionStatus status) { +// RelayerNodeInfo nodeInDB = relayerNetworkManager.getRelayerNode(nodeInfo.getNodeId()); +// if (nodeInDB == null) { +// throw new RuntimeException(String.format("this relayer %s is not registered", nodeInfo.getNodeId())); +// } +// +// if (!nodeInDB.getNodeId().equals(nodeInfo.getNodeId())) { +// throw new RuntimeException(String.format("wrong node id and original one is %s", nodeInDB.getNodeId())); +// } +// +// if (!nodeInDB.getDomains().containsAll(nodeInfo.getDomains())) { +// throw new RuntimeException(String.format( +// "some domains are not registered, please register them first. domains registered: %s", +// String.join(", ", nodeInDB.getDomains()))); +// } +// +// nodeInDB.endpoints = nodeInfo.getEndpoints(); +// +// if (!relayerNetworkManager.updateRelayerNode(nodeInDB)) { +// throw new RuntimeException("failed to add relayer node. "); +// } +// } +// }); + } + + public void deleteDomains(RelayerNodeInfo nodeInfo) { +// DBTransactionManager.getInstance().execute(new TransactionCallbackWithoutResult() { +// @Override +// protected void doInTransactionWithoutResult(TransactionStatus status) { +// RelayerNodeInfo nodeInDB = relayerNetworkManager.getRelayerNode(nodeInfo.getNodeId()); +// if (nodeInDB == null) { +// throw new RuntimeException(String.format("this relayer %s is not registered", nodeInfo.getNodeId())); +// } +// +// if (!nodeInDB.getNodeId().equals(nodeInfo.getNodeId())) { +// throw new RuntimeException(String.format("wrong node id and original one is %s", nodeInDB.getNodeId())); +// } +// +// if (!nodeInDB.getDomains().containsAll(nodeInfo.getDomains())) { +// throw new RuntimeException(String.format( +// "some domains are not registered, please register them first. domains registered: %s", +// String.join(", ", nodeInDB.getDomains()))); +// } +// +// if (!nodeInDB.domains.removeAll(nodeInfo.getDomains())) { +// throw new RuntimeException("failed to delete domains"); +// } +// +// if (!relayerNetworkManager.updateRelayerNode(nodeInDB)) { +// throw new RuntimeException("failed to add relayer node. "); +// } +// +// for (String domain : nodeInfo.getDomains()) { +// if (!relayerNetworkManager.deleteRelayerNetworkItem(domain, nodeInDB.getNodeId())) { +// throw new RuntimeException( +// String.format("failed to delete network item for domain %s and node %s", +// domain, nodeInDB.getNodeId())); +// } +// } +// } +// }); + } + + public boolean isAboutDomain(RelayerRequestType type) { + switch (type) { + case GET_RELAYER_FOR_DOMAIN: + case REGISTER_DOMAIN: + case UPDATE_DOMAIN: + case DELETE_DOMAIN: + break; + default: + return false; + } + return true; + } +} diff --git a/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServer.java b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServer.java new file mode 100644 index 0000000..ac2ff19 --- /dev/null +++ b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServer.java @@ -0,0 +1,154 @@ +/* + * 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.relayer.server.network; + +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.xml.ws.Endpoint; + +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.network.ws.WsSslFactory; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsParameters; +import com.sun.net.httpserver.HttpsServer; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; + +@Slf4j +@Getter +@Order +public class WSRelayerServer implements ApplicationRunner { + + private final String serverMode; + + private final int port; + + private Endpoint endpoint; + + private HttpsServer httpsServer; + + private HttpServer httpServer; + + private final WSRelayerServerAPI wsRelayerServerAPI; + + private final ExecutorService workers; + + private final WsSslFactory wsSslFactory; + + public WSRelayerServer( + String serverMode, + int port, + String defaultNetworkId, + ExecutorService wsRelayerServerExecutorService, + WsSslFactory wsSslFactory, + IRelayerNetworkManager relayerNetworkManager, + IRelayerCredentialManager relayerCredentialManager, + ReceiverService receiverService, + boolean isDiscoveryService + ) { + this.serverMode = serverMode; + this.port = port; + this.wsRelayerServerAPI = new WSRelayerServerAPImpl( + relayerNetworkManager, + relayerCredentialManager, + receiverService, + defaultNetworkId, + isDiscoveryService + ); + this.workers = wsRelayerServerExecutorService; + this.wsSslFactory = wsSslFactory; + } + + public void run(ApplicationArguments args) throws Exception { + try { + log.info("your mode for webservice relayer node server is {}", serverMode); + switch (serverMode) { + case "http": + startWithHttp(); + break; + case "https": + startWithHttps(false); + break; + case "https_client_auth": + startWithHttps(true); + break; + default: + log.warn("mode for webservice connection is not found and start in https with two way authentication. "); + startWithHttps(true); + break; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void startWithHttps(boolean needClientAuth) throws Exception { + httpsServer = HttpsServer.create(new InetSocketAddress(port), 0); + httpsServer.setHttpsConfigurator( + new HttpsConfigurator(wsSslFactory.getSslContext()) { + public void configure(HttpsParameters params) { + SSLContext c = getSSLContext(); + SSLParameters sslparams = c.getDefaultSSLParameters(); + sslparams.setNeedClientAuth(needClientAuth); + params.setSSLParameters(sslparams); + } + } + ); + + log.info("endpoint startup webservice : {}", httpsServer.getAddress().toString()); + endpoint = Endpoint.create(this.wsRelayerServerAPI); + endpoint.publish(httpsServer.createContext("/WSEndpointServer")); + + httpsServer.setExecutor(workers); + httpsServer.start(); + } + + private void startWithHttp() throws Exception { + httpServer = HttpServer.create(new InetSocketAddress(port), 16); + log.info("endpoint startup webservice : {}", httpServer.getAddress().toString()); + + endpoint = Endpoint.create(this.wsRelayerServerAPI); + endpoint.publish(httpServer.createContext("/WSEndpointServer")); + + httpServer.setExecutor(workers); + httpServer.start(); + } + + public boolean shutdown() { + if (null != endpoint) { + endpoint.stop(); + } + + if (null != httpsServer) { + httpsServer.stop(0); + } + + if (null != httpServer) { + httpServer.stop(0); + } + + return true; + } +} diff --git a/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPI.java b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPI.java new file mode 100644 index 0000000..2d0d23a --- /dev/null +++ b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPI.java @@ -0,0 +1,31 @@ +/* + * 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.relayer.server.network; + +/** + * WebService API + */ +public interface WSRelayerServerAPI { + + /** + * endpoint请求 + * + * @param relayerRequest Base64 编码的请求体 + * @return Base64 编码的响应体 + */ + String request(String relayerRequest); +} diff --git a/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPImpl.java b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPImpl.java new file mode 100644 index 0000000..6af36b9 --- /dev/null +++ b/r-server/src/main/java/com/alipay/antchain/bridge/relayer/server/network/WSRelayerServerAPImpl.java @@ -0,0 +1,288 @@ +/* + * 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.relayer.server.network; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import javax.jws.soap.SOAPBinding; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ObjectUtil; +import com.alipay.antchain.bridge.relayer.commons.exception.AntChainBridgeRelayerException; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainContent; +import com.alipay.antchain.bridge.relayer.commons.model.RelayerBlockchainInfo; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerCredentialManager; +import com.alipay.antchain.bridge.relayer.core.manager.network.IRelayerNetworkManager; +import com.alipay.antchain.bridge.relayer.core.service.receiver.ReceiverService; +import com.alipay.antchain.bridge.relayer.core.types.network.exception.RejectRequestException; +import com.alipay.antchain.bridge.relayer.core.types.network.request.*; +import com.alipay.antchain.bridge.relayer.core.types.network.response.HandshakeRespPayload; +import com.alipay.antchain.bridge.relayer.core.types.network.response.RelayerResponse; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@WebService(targetNamespace = "http://ws.offchainapi.oracle.mychain.alipay.com/") +@SOAPBinding +@Slf4j +@NoArgsConstructor +public class WSRelayerServerAPImpl extends BaseRelayerServer implements WSRelayerServerAPI { + + public WSRelayerServerAPImpl( + IRelayerNetworkManager relayerNetworkManager, + IRelayerCredentialManager relayerCredentialManager, + ReceiverService receiverService, + String defaultNetworkId, + boolean isDiscoveryServer + ) { + super(relayerNetworkManager, relayerCredentialManager, receiverService, defaultNetworkId, isDiscoveryServer); + } + + @Override + @WebMethod + public String request(@WebParam(name = "relayerRequest") String relayerRequest) { + + log.debug("receive ws request"); + + byte[] rawResponse = doRequest(Base64.decode(relayerRequest)); + + String response = Base64.encode(rawResponse); + + log.debug("finish ws request process"); + + return response; + } + + /** + * 处理请求 + * + * @param rawRequest + * @return + */ + private byte[] doRequest(byte[] rawRequest) { + + try { + RelayerRequest request = RelayerRequest.decode(rawRequest, RelayerRequest.class); + if (ObjectUtil.isNull(request)) { + log.error("Invalid relayer request that failed to decode"); + return RelayerResponse.createFailureResponse( + "failed to decode request", + getRelayerCredentialManager() + ).encode(); + } + + if (isAboutDomain(request.getRequestType()) && !isDiscoveryServer()) { + log.error("Invalid relayer request from relayer {} that sending request about Discovery Service", request.calcRelayerNodeId()); + return RelayerResponse.createFailureResponse( + "node you connected isn't a relayer discovery server", + getRelayerCredentialManager() + ).encode(); + } + + switch (request.getRequestType()) { + case GET_RELAYER_NODE_INFO: + return processGetRelayerNodeInfo().encode(); + case GET_RELAYER_BLOCKCHAIN_INFO: + return processGetRelayerBlockchainInfo( + GetRelayerBlockchainInfoRelayerRequest.createFrom(request) + ).encode(); + case AM_REQUEST: + return processAMRequest( + AMRelayerRequest.createFrom(request) + ).encode(); + case HANDSHAKE: + return processHandshakeRequest( + HandshakeRelayerRequest.createFrom(request) + ).encode(); + case GET_RELAYER_BLOCKCHAIN_CONTENT: + return processGetRelayerBlockchainContent( + GetRelayerBlockchainContentRelayerRequest.createFrom(request) + ).encode(); + default: + return RelayerResponse.createFailureResponse( + "request type not supported: " + request.getRequestType().getCode(), + getRelayerCredentialManager() + ).encode(); + } + } catch (Exception e) { + log.error("unexpected exception happened: ", e); + return RelayerResponse.createFailureResponse( + "unexpected exception happened", + getRelayerCredentialManager() + ).encode(); + } + } + + private RelayerResponse processGetRelayerNodeInfo() { + return RelayerResponse.createSuccessResponse( + () -> Base64.encode(getRelayerNetworkManager().getRelayerNodeInfo().getEncode()), + getRelayerCredentialManager() + ); + } + + private RelayerResponse processGetRelayerBlockchainInfo(GetRelayerBlockchainInfoRelayerRequest request) { + if (!getRelayerCredentialManager().validateRelayerRequest(request)) { + log.error("failed to validate request from relayer {}", request.calcRelayerNodeId()); + return RelayerResponse.createFailureResponse( + "verify sig failed", + getRelayerCredentialManager() + ); + } + + RelayerBlockchainInfo blockchainInfo; + try { + blockchainInfo = getRelayerNetworkManager().getRelayerBlockchainInfo( + request.getDomainToQuery() + ); + } catch (AntChainBridgeRelayerException e) { + log.error("failed to query blockchain info for domain {}", request.getDomainToQuery(), e); + return RelayerResponse.createFailureResponse( + e.getMsg(), + getRelayerCredentialManager() + ); + } + if (ObjectUtil.isNull(blockchainInfo)) { + return RelayerResponse.createFailureResponse( + "empty result", + getRelayerCredentialManager() + ); + } + + return RelayerResponse.createSuccessResponse( + blockchainInfo::encode, + getRelayerCredentialManager() + ); + } + + private RelayerResponse processGetRelayerBlockchainContent(GetRelayerBlockchainContentRelayerRequest request) { + if (!getRelayerCredentialManager().validateRelayerRequest(request)) { + log.error("failed to validate request from relayer {}", request.calcRelayerNodeId()); + return RelayerResponse.createFailureResponse( + "verify sig failed", + getRelayerCredentialManager() + ); + } + + RelayerBlockchainContent blockchainContent; + try { + blockchainContent = getRelayerNetworkManager().getRelayerNodeInfoWithContent() + .getRelayerBlockchainContent(); + } catch (AntChainBridgeRelayerException e) { + log.error("failed to query local blockchain content", e); + return RelayerResponse.createFailureResponse( + e.getMsg(), + getRelayerCredentialManager() + ); + } + if (ObjectUtil.isNull(blockchainContent)) { + return RelayerResponse.createFailureResponse( + "empty result", + getRelayerCredentialManager() + ); + } + + return RelayerResponse.createSuccessResponse( + blockchainContent::encodeToJson, + getRelayerCredentialManager() + ); + } + + private RelayerResponse processAMRequest(AMRelayerRequest request) { + if (!getRelayerCredentialManager().validateRelayerRequest(request)) { + log.error("failed to validate request from relayer {}", request.calcRelayerNodeId()); + return RelayerResponse.createFailureResponse( + "verify sig failed", + getRelayerCredentialManager() + ); + } + + try { + amRequest( + request.getDomainName(), + request.getAuthMsg(), + request.getUdagProof(), + request.getLedgerInfo() + ); + } catch (RejectRequestException e) { + log.error( + "reject am request from (blockchain: {}, relayer: {}) failed: ", + request.getDomainName(), request.calcRelayerNodeId(), + e + ); + return RelayerResponse.createFailureResponse( + e.getErrorMsg(), + getRelayerCredentialManager() + ); + } catch (AntChainBridgeRelayerException e) { + log.error( + "handle am request from (blockchain: {}, relayer: {}) failed: ", + request.getDomainName(), request.calcRelayerNodeId(), + e + ); + return RelayerResponse.createFailureResponse( + e.getMsg(), + getRelayerCredentialManager() + ); + } + + log.info( "handle am request from relayer {} success: ", request.calcRelayerNodeId()); + + return RelayerResponse.createSuccessResponse( + () -> null, + getRelayerCredentialManager() + ); + } + + private RelayerResponse processHandshakeRequest(HandshakeRelayerRequest request) { + if (!getRelayerCredentialManager().validateRelayerRequest(request)) { + log.error("failed to validate request from relayer {}", request.calcRelayerNodeId()); + return RelayerResponse.createFailureResponse( + "verify sig failed", + getRelayerCredentialManager() + ); + } + + try { + doHandshake( + request.getSenderNodeInfo(), + request.getNetworkId() + ); + } catch (AntChainBridgeRelayerException e) { + log.error( + "handle handshake request from relayer {} failed: ", + request.calcRelayerNodeId(), + e + ); + return RelayerResponse.createFailureResponse( + e.getMsg(), + getRelayerCredentialManager() + ); + } + + log.info( "handle handshake request from relayer {} success: ", request.calcRelayerNodeId()); + + return RelayerResponse.createSuccessResponse( + new HandshakeRespPayload( + getDefaultNetworkId(), + Base64.encode( + getRelayerNetworkManager().getRelayerNodeInfoWithContent().encodeWithProperties() + ) + ), + getRelayerCredentialManager() + ); + } +} diff --git a/r-service/.gitignore b/r-service/.gitignore deleted file mode 100644 index 5ff6309..0000000 --- a/r-service/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws -*.iml -*.ipr - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store \ No newline at end of file diff --git a/r-service/pom.xml b/r-service/pom.xml deleted file mode 100644 index 25d7b1a..0000000 --- a/r-service/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - 4.0.0 - - com.alipay.antchain.bridge - antchain-bridge-relayer - 0.1-SNAPSHOT - - - r-service - - - 8 - 8 - UTF-8 - - - - - com.alipay.antchain.bridge - r-core - - - \ No newline at end of file