From 7028baa7011ec14ed4904c5a742934609df854a9 Mon Sep 17 00:00:00 2001 From: Steven Sheehy <17552371+steven-sheehy@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:56:14 -0600 Subject: [PATCH] Add a get address book nodes gRPC API (#3141) * Add a NetworkService.getNodes() gRPC API * Change AddressBookEntry and AddressBookServiceEndpoint to use @IdClass with flattened properties * Change TopicMessageFilter to use EntityId instead of long * Fix AddressBookServiceEndpoint incorrectly modeling nodeId as EntityId * Move DomainBuilder to common module Signed-off-by: Steven Sheehy Signed-off-by: Matheus DallRosa --- docs/configuration.md | 69 +++--- docs/grpc/README.md | 29 ++- hedera-mirror-common/pom.xml | 12 + .../domain/addressbook/AddressBook.java | 23 +- .../domain/addressbook/AddressBookEntry.java | 35 +-- .../AddressBookServiceEndpoint.java | 51 +++-- .../mirror/common/domain/entity/EntityId.java | 9 + .../common/domain/entity/EntityIdEndec.java | 2 +- .../common/domain/entity/EntityType.java | 12 +- .../mirror/common}/domain/DomainBuilder.java | 169 ++++++++++---- .../mirror/common/domain/DomainWrapper.java | 32 +++ hedera-mirror-grpc/pom.xml | 7 + .../hedera/mirror/grpc/GrpcProperties.java | 5 - .../grpc/config/CacheConfiguration.java | 14 ++ .../mirror/grpc/config/GrpcConfiguration.java | 6 +- .../grpc/controller/ConsensusController.java | 62 +---- .../grpc/controller/NetworkController.java | 110 +++++++++ .../converter/EncodedIdToEntityConverter.java | 59 ----- .../mirror/grpc/domain/AddressBookFilter.java | 38 ++++ .../mirror/grpc/domain/TopicMessage.java | 67 +++--- .../grpc/domain/TopicMessageFilter.java | 7 +- ...tion.java => EntityNotFoundException.java} | 9 +- .../grpc/listener/CompositeTopicListener.java | 2 +- .../grpc/listener/RedisTopicListener.java | 2 +- .../AddressBookEntryRepository.java | 38 ++++ .../repository/AddressBookRepository.java | 33 +++ .../TopicMessageRepositoryCustomImpl.java | 2 +- .../grpc/service/AddressBookProperties.java | 53 +++++ .../mirror/grpc/service/NetworkService.java | 32 +++ .../grpc/service/NetworkServiceImpl.java | 127 +++++++++++ .../grpc/service/TopicMessageServiceImpl.java | 11 +- .../com/hedera/mirror/grpc/util/EntityId.java | 63 ----- .../hedera/mirror/grpc/util/ProtoUtil.java | 74 +++++- .../src/main/resources/application.yml | 2 + .../mirror/grpc/GrpcIntegrationTest.java | 33 ++- .../grpc/ResetCacheTestExecutionListener.java | 36 --- .../controller/ConsensusControllerTest.java | 17 +- .../controller/NetworkControllerTest.java | 215 ++++++++++++++++++ .../EncodedIdToEntityConverterTest.java | 60 ----- .../mirror/grpc/domain/DomainBuilder.java | 6 +- .../AbstractSharedTopicListenerTest.java | 2 + .../listener/AbstractTopicListenerTest.java | 22 +- .../listener/NotifyingTopicListenerTest.java | 6 +- .../AddressBookEntryRepositoryTest.java | 76 +++++++ .../repository/AddressBookRepositoryTest.java | 78 +++++++ .../TopicMessageRepositoryTest.java | 14 +- .../PollingTopicMessageRetrieverTest.java | 22 +- .../grpc/service/NetworkServiceTest.java | 205 +++++++++++++++++ .../grpc/service/TopicMessageServiceTest.java | 71 +++--- .../hedera/mirror/grpc/util/EntityIdTest.java | 72 ------ .../mirror/grpc/util/ProtoUtilTest.java | 64 ++++++ hedera-mirror-importer/pom.xml | 7 + .../addressbook/AddressBookServiceImpl.java | 41 ++-- .../entity/redis/RedisEntityListener.java | 1 - .../mirror/importer/IntegrationTest.java | 4 +- .../AddressBookServiceImplTest.java | 5 +- ...java => IntegrationTestConfiguration.java} | 13 +- .../importer/domain/DomainPersister.java | 57 ----- .../downloader/AbstractDownloaderTest.java | 2 +- ...ressBookServiceEndpointsMigrationTest.java | 26 ++- .../MissingAddressBooksMigrationTest.java | 33 +-- ...TransferTransactionPayerMigrationTest.java | 15 +- .../parser/batch/BatchInserterTest.java | 2 +- .../parser/batch/BatchUpserterTest.java | 4 +- .../batch/CompositeBatchPersisterTest.java | 11 +- .../parser/domain/RecordItemBuilder.java | 4 +- .../AbstractEntityRecordItemListenerTest.java | 2 +- .../entity/redis/RedisEntityListenerTest.java | 6 +- .../entity/sql/SqlEntityListenerTest.java | 2 +- .../repository/AbstractRepositoryTest.java | 2 +- .../AddressBookEntryRepositoryTest.java | 5 +- .../repository/AddressBookRepositoryTest.java | 18 +- ...ressBookServiceEndpointRepositoryTest.java | 49 ++-- .../repository/EntityRepositoryTest.java | 2 +- .../mirror/api/proto/network_service.proto | 43 ++++ 75 files changed, 1837 insertions(+), 782 deletions(-) rename {hedera-mirror-importer/src/test/java/com/hedera/mirror/importer => hedera-mirror-common/src/test/java/com/hedera/mirror/common}/domain/DomainBuilder.java (62%) create mode 100644 hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainWrapper.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/NetworkController.java delete mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverter.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/AddressBookFilter.java rename hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/{TopicNotFoundException.java => EntityNotFoundException.java} (72%) create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepository.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookRepository.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/AddressBookProperties.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkService.java create mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkServiceImpl.java delete mode 100644 hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/EntityId.java delete mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/ResetCacheTestExecutionListener.java create mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/NetworkControllerTest.java delete mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverterTest.java create mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepositoryTest.java create mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookRepositoryTest.java create mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/NetworkServiceTest.java delete mode 100644 hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/util/EntityIdTest.java rename hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/{MeterRegistryConfiguration.java => IntegrationTestConfiguration.java} (69%) delete mode 100644 hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainPersister.java create mode 100644 hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto diff --git a/docs/configuration.md b/docs/configuration.md index 6bced0ae67f..7fb2c7713fa 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -182,39 +182,44 @@ to configure the application. The following table lists the available properties along with their default values. Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `application.yml`. -| Name | Default | Description | -| ------------------------------------------------------------| -----------------| -----------------------------------------------------------------------------------------------| -| `hedera.mirror.grpc.checkTopicExists` | true | Whether to throw an error when the topic doesn't exist | -| `hedera.mirror.grpc.db.host` | 127.0.0.1 | The IP or hostname used to connect to the database | -| `hedera.mirror.grpc.db.name` | mirror_node | The name of the database | -| `hedera.mirror.grpc.db.password` | mirror_grpc_pass | The database password used to connect to the database. | -| `hedera.mirror.grpc.db.port` | 5432 | The port used to connect to the database | +| Name | Default | Description | +|-------------------------------------------------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `hedera.mirror.grpc.addressbook.cacheExpiry` | 5s | The amount of time to cache address book entries | +| `hedera.mirror.grpc.addressbook.cacheSize` | 50 | The maximum number of address book pages to cache | +| `hedera.mirror.grpc.addressbook.maxPageDelay` | 250ms | The maximum amount of time to sleep between paging for address book entries | +| `hedera.mirror.grpc.addressbook.minPageDelay` | 100ms | The minimum amount of time to sleep between paging for address book entries | +| `hedera.mirror.grpc.addressbook.pageSize` | 10 | The maximum number of address book entries to return in a single page | +| `hedera.mirror.grpc.checkTopicExists` | true | Whether to throw an error when the topic doesn't exist | +| `hedera.mirror.grpc.db.host` | 127.0.0.1 | The IP or hostname used to connect to the database | +| `hedera.mirror.grpc.db.name` | mirror_node | The name of the database | +| `hedera.mirror.grpc.db.password` | mirror_grpc_pass | The database password used to connect to the database. | +| `hedera.mirror.grpc.db.port` | 5432 | The port used to connect to the database | | `hedera.mirror.grpc.db.sslMode` | DISABLE | The ssl level of protection against Eavesdropping, Man-in-the-middle (MITM) and Impersonation on the db connection. Accepts either DISABLE, ALLOW, PREFER, REQUIRE, VERIFY_CA or VERIFY_FULL. | -| `hedera.mirror.grpc.db.username` | mirror_grpc | The username used to connect to the database | -| `hedera.mirror.grpc.endTimeInterval` | 30s | How often we should check if a subscription has gone past the end time | -| `hedera.mirror.grpc.entityCacheSize` | 50000 | The maximum size of the cache to store entities used for existence check | -| `hedera.mirror.grpc.listener.enabled` | true | Whether to listen for incoming massages or not | -| `hedera.mirror.grpc.listener.interval` | 500ms | How often to poll or retry errors (varies by type). Can accept duration units like `50ms`, `10s`, etc. | -| `hedera.mirror.grpc.listener.maxBufferSize` | 16384 | The maximum number of messages the notifying listener or the shared polling listener buffers before sending an error to a client | -| `hedera.mirror.grpc.listener.maxPageSize` | 5000 | The maximum number of messages the listener can return in a single call to the database | -| `hedera.mirror.grpc.listener.prefetch` | 48 | The prefetch queue size for shared listeners | -| `hedera.mirror.grpc.listener.type` | REDIS | The type of listener to use for incoming messages. Accepts either NOTIFY, POLL, REDIS or SHARED_POLL | -| `hedera.mirror.grpc.netty.executorCoreThreadCount` | 10 | The number of core threads | -| `hedera.mirror.grpc.netty.executorMaxThreadCount` | 1000 | The maximum allowed number of threads | -| `hedera.mirror.grpc.netty.maxConnectionIdle` | 10m | The max amount of time a connection can be idle before it will be gracefully terminated | -| `hedera.mirror.grpc.netty.maxConcurrentCallsPerConnection` | 5 | The maximum number of concurrent calls permitted for each incoming connection | -| `hedera.mirror.grpc.netty.maxInboundMessageSize` | 1024 | The maximum message size allowed to be received on the server | -| `hedera.mirror.grpc.netty.maxInboundMetadataSize` | 1024 | The maximum size of metadata allowed to be received | -| `hedera.mirror.grpc.netty.threadKeepAliveTime` | 1m | The amount of time for which threads may remain idle before being terminated | -| `hedera.mirror.grpc.port` | 5600 | The GRPC API port | -| `hedera.mirror.grpc.retriever.enabled` | true | Whether to retrieve historical massages or not | -| `hedera.mirror.grpc.retriever.maxPageSize` | 1000 | The maximum number of messages the retriever can return in a single call to the database | -| `hedera.mirror.grpc.retriever.pollingFrequency` | 2s | How often to poll for historical messages. Can accept duration units like `50ms`, `10s` etc | -| `hedera.mirror.grpc.retriever.threadMultiplier` | 4 | Multiplied by the CPU count to calculate the number of retriever threads | -| `hedera.mirror.grpc.retriever.timeout` | 60s | How long to wait between emission of messages before returning an error | -| `hedera.mirror.grpc.retriever.unthrottled.maxPageSize` | 5000 | The maximum number of messages the retriever can return in a single call to the database when unthrottled | -| `hedera.mirror.grpc.retriever.unthrottled.maxPolls` | 12 | The max number of polls when unthrottled | -| `hedera.mirror.grpc.retriever.unthrottled.pollingFrequency` | 20ms | How often to poll for messages when unthrottled. Can accept duration units like `50ms`, `10s` etc | +| `hedera.mirror.grpc.db.username` | mirror_grpc | The username used to connect to the database | +| `hedera.mirror.grpc.endTimeInterval` | 30s | How often we should check if a subscription has gone past the end time | +| `hedera.mirror.grpc.entityCacheSize` | 50000 | The maximum size of the cache to store entities used for existence check | +| `hedera.mirror.grpc.listener.enabled` | true | Whether to listen for incoming massages or not | +| `hedera.mirror.grpc.listener.interval` | 500ms | How often to poll or retry errors (varies by type). Can accept duration units like `50ms`, `10s`, etc. | +| `hedera.mirror.grpc.listener.maxBufferSize` | 16384 | The maximum number of messages the notifying listener or the shared polling listener buffers before sending an error to a client | +| `hedera.mirror.grpc.listener.maxPageSize` | 5000 | The maximum number of messages the listener can return in a single call to the database | +| `hedera.mirror.grpc.listener.prefetch` | 48 | The prefetch queue size for shared listeners | +| `hedera.mirror.grpc.listener.type` | REDIS | The type of listener to use for incoming messages. Accepts either NOTIFY, POLL, REDIS or SHARED_POLL | +| `hedera.mirror.grpc.netty.executorCoreThreadCount` | 10 | The number of core threads | +| `hedera.mirror.grpc.netty.executorMaxThreadCount` | 1000 | The maximum allowed number of threads | +| `hedera.mirror.grpc.netty.maxConnectionIdle` | 10m | The max amount of time a connection can be idle before it will be gracefully terminated | +| `hedera.mirror.grpc.netty.maxConcurrentCallsPerConnection` | 5 | The maximum number of concurrent calls permitted for each incoming connection | +| `hedera.mirror.grpc.netty.maxInboundMessageSize` | 1024 | The maximum message size allowed to be received on the server | +| `hedera.mirror.grpc.netty.maxInboundMetadataSize` | 1024 | The maximum size of metadata allowed to be received | +| `hedera.mirror.grpc.netty.threadKeepAliveTime` | 1m | The amount of time for which threads may remain idle before being terminated | +| `hedera.mirror.grpc.port` | 5600 | The GRPC API port | +| `hedera.mirror.grpc.retriever.enabled` | true | Whether to retrieve historical massages or not | +| `hedera.mirror.grpc.retriever.maxPageSize` | 1000 | The maximum number of messages the retriever can return in a single call to the database | +| `hedera.mirror.grpc.retriever.pollingFrequency` | 2s | How often to poll for historical messages. Can accept duration units like `50ms`, `10s` etc | +| `hedera.mirror.grpc.retriever.threadMultiplier` | 4 | Multiplied by the CPU count to calculate the number of retriever threads | +| `hedera.mirror.grpc.retriever.timeout` | 60s | How long to wait between emission of messages before returning an error | +| `hedera.mirror.grpc.retriever.unthrottled.maxPageSize` | 5000 | The maximum number of messages the retriever can return in a single call to the database when unthrottled | +| `hedera.mirror.grpc.retriever.unthrottled.maxPolls` | 12 | The max number of polls when unthrottled | +| `hedera.mirror.grpc.retriever.unthrottled.pollingFrequency` | 20ms | How often to poll for messages when unthrottled. Can accept duration units like `50ms`, `10s` etc | ## Monitor diff --git a/docs/grpc/README.md b/docs/grpc/README.md index 62fc0cbccc0..9dea439f5b7 100644 --- a/docs/grpc/README.md +++ b/docs/grpc/README.md @@ -1,4 +1,29 @@ # gRPC API -The gRPC API provides a protobuf defined interface for interacting with the mirror node. Currently only the Hedera -Consensus Service (HCS) topic subscription is supported. +The gRPC API provides a protobuf defined interface for interacting with the mirror node. + +## Consensus Service + +### Subscribe Topic + +The Hedera Consensus Service (HCS) provides decentralized consensus on the validity and order of messages submitted to a +topic on the network and transparency into the history of these events over time. The `subscribeTopic` API allows a +client to subscribe to a topic and stream messages asynchronously as they arrive at the mirror node. See the protobuf +[definition](hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/consensus_service.proto). + +Example invocation using [grpcurl](https://github.com/fullstorydev/grpcurl): + +`grpcurl -plaintext -d '{"topicID": {"topicNum": 41110}, "limit": 0}' localhost:5600 com.hedera.mirror.api.proto.ConsensusService/subscribeTopic` + +## Network Service + +### Get Nodes + +[HIP-21](https://hips.hedera.com/hip/hip-21) describes a need for clients to retrieve address book information without +incurring the costs of multiple queries to get the network file's contents. The `getNode` API will return the list of +nodes associated with the latest address book file. See the protobuf +[definition](/hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto). + +Example invocation using `grpcurl`: + +`grpcurl -plaintext -d '{"file_id": {"fileNum": 102}, "limit": 0}' localhost:5600 com.hedera.mirror.api.proto.NetworkService/getNodes` diff --git a/hedera-mirror-common/pom.xml b/hedera-mirror-common/pom.xml index 86cde9c90db..5e1a478b94d 100644 --- a/hedera-mirror-common/pom.xml +++ b/hedera-mirror-common/pom.xml @@ -96,6 +96,18 @@ org.jacoco jacoco-maven-plugin + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + test-jar + + + + diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBook.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBook.java index 1fe186ebe80..53cd81bae81 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBook.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBook.java @@ -34,9 +34,7 @@ import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Transient; - -import com.hedera.mirror.common.domain.entity.EntityId; - +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -45,12 +43,13 @@ import lombok.ToString; import com.hedera.mirror.common.converter.FileIdConverter; +import com.hedera.mirror.common.domain.entity.EntityId; @Builder(toBuilder = true) @Data @Entity @NoArgsConstructor -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) // For builder public class AddressBook { // consensusTimestamp + 1ns of transaction containing final fileAppend operation @Id @@ -59,25 +58,25 @@ public class AddressBook { // consensusTimestamp of transaction containing final fileAppend operation of next address book private Long endConsensusTimestamp; + @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER) + @JoinColumn(name = "consensusTimestamp") + private List entries = new ArrayList<>(); + + @ToString.Exclude + private byte[] fileData; + @Convert(converter = FileIdConverter.class) private EntityId fileId; private Integer nodeCount; - @ToString.Exclude - private byte[] fileData; - @ToString.Exclude @Transient @Getter(lazy = true) private final Map nodeAccountIDPubKeyMap = this.getEntries() .stream() .collect(Collectors - .toMap(AddressBookEntry::getNodeAccountIdString, AddressBookEntry::getPublicKeyAsObject)); - - @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER) - @JoinColumn(name = "consensusTimestamp") - private List entries = new ArrayList<>(); + .toMap(e -> e.getNodeAccountId().toString(), AddressBookEntry::getPublicKeyAsObject)); public Set getNodeSet() { return entries.stream() diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookEntry.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookEntry.java index aa2495b7619..4f674e406ef 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookEntry.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookEntry.java @@ -35,12 +35,14 @@ import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.FetchType; +import javax.persistence.IdClass; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Transient; import com.hedera.mirror.common.domain.entity.EntityId; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -55,28 +57,30 @@ @Builder(toBuilder = true) @Data @Entity +@IdClass(AddressBookEntry.Id.class) @NoArgsConstructor -@AllArgsConstructor -@ToString(exclude = {"publicKey", "nodeCertHash"}) -public class AddressBookEntry implements Persistable, Serializable { - private static final long serialVersionUID = -2037596800253225229L; +@AllArgsConstructor(access = AccessLevel.PRIVATE) // For builder +public class AddressBookEntry implements Persistable { - @JsonIgnore - @EmbeddedId - @JsonUnwrapped - private AddressBookEntry.Id id; + @javax.persistence.Id + private long consensusTimestamp; private String description; private String memo; - private String publicKey; + @javax.persistence.Id + private long nodeId; @Convert(converter = AccountIdConverter.class) private EntityId nodeAccountId; + @ToString.Exclude private byte[] nodeCertHash; + @ToString.Exclude + private String publicKey; + @EqualsAndHashCode.Exclude @JoinColumn(name = "consensusTimestamp", referencedColumnName = "consensusTimestamp") @JoinColumn(name = "nodeId", referencedColumnName = "nodeId") @@ -97,9 +101,13 @@ public PublicKey getPublicKeyAsObject() { } } - @Transient - public String getNodeAccountIdString() { - return nodeAccountId.entityIdToString(); + @JsonIgnore + @Override + public AddressBookEntry.Id getId() { + AddressBookEntry.Id id = new AddressBookEntry.Id(); + id.setConsensusTimestamp(consensusTimestamp); + id.setNodeId(nodeId); + return id; } @JsonIgnore @@ -109,9 +117,6 @@ public boolean isNew() { } @Data - @Embeddable - @AllArgsConstructor - @NoArgsConstructor public static class Id implements Serializable { private static final long serialVersionUID = -3761184325551298389L; diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookServiceEndpoint.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookServiceEndpoint.java index 1183dcff805..cc13a018050 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookServiceEndpoint.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/addressbook/AddressBookServiceEndpoint.java @@ -21,39 +21,48 @@ */ import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import java.io.Serializable; import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Embeddable; -import javax.persistence.EmbeddedId; import javax.persistence.Entity; - -import com.hedera.mirror.common.domain.entity.EntityId; - +import javax.persistence.IdClass; +import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.domain.Persistable; -import com.hedera.mirror.common.converter.AccountIdConverter; - +@Builder(toBuilder = true) @Data @Entity +@IdClass(AddressBookServiceEndpoint.Id.class) @NoArgsConstructor -@AllArgsConstructor -public class AddressBookServiceEndpoint implements Persistable, Serializable { +@AllArgsConstructor(access = AccessLevel.PRIVATE) // For builder +public class AddressBookServiceEndpoint implements Persistable { - private static final long serialVersionUID = 6964963511683419945L; + @javax.persistence.Id + private long consensusTimestamp; - public AddressBookServiceEndpoint(long consensusTimestamp, String ip, int port, EntityId nodeAccountId) { - id = new AddressBookServiceEndpoint.Id(consensusTimestamp, ip, nodeAccountId, port); - } + @javax.persistence.Id + @Column(name = "ip_address_v4") + private String ipAddressV4; + + @javax.persistence.Id + private long nodeId; + + @javax.persistence.Id + private int port; @JsonIgnore - @EmbeddedId - @JsonUnwrapped - private Id id; + @Override + public Id getId() { + Id id = new Id(); + id.setConsensusTimestamp(consensusTimestamp); + id.setIpAddressV4(ipAddressV4); + id.setNodeId(nodeId); + id.setPort(port); + return id; + } @JsonIgnore @Override @@ -62,9 +71,6 @@ public boolean isNew() { } @Data - @Embeddable - @AllArgsConstructor - @NoArgsConstructor public static class Id implements Serializable { private static final long serialVersionUID = -7779136597707252814L; @@ -74,8 +80,7 @@ public static class Id implements Serializable { @Column(name = "ip_address_v4") private String ipAddressV4; - @Convert(converter = AccountIdConverter.class) - private EntityId nodeId; + private long nodeId; private int port; } diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityId.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityId.java index b375ceb6ab4..20dc0856f70 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityId.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityId.java @@ -131,11 +131,20 @@ public static boolean isEmpty(EntityId entityId) { return entityId == null || EMPTY.equals(entityId); } + /** + * @deprecated in favor of using toString() + */ + @Deprecated(since = "v0.49.0") public String entityIdToString() { return String.format("%d.%d.%d", getShardNum(), getRealmNum(), getEntityNum()); } + @SuppressWarnings("deprecation") + public String toString() { + return entityIdToString(); + } + public T toEntity() { T entity = createEntity(); entity.setId(id); diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityIdEndec.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityIdEndec.java index 3b55eb719bc..8168c0991e7 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityIdEndec.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityIdEndec.java @@ -49,7 +49,7 @@ public static Long encode(long shardNum, long realmNum, long entityNum) { if (shardNum > SHARD_MASK || shardNum < 0 || realmNum > REALM_MASK || realmNum < 0 || entityNum > NUM_MASK || entityNum < 0) { - throw new InvalidEntityException("Entity outside encoding range: " + throw new InvalidEntityException("Invalid entity ID: " + shardNum + "." + realmNum + "." + entityNum); } return (entityNum & NUM_MASK) | diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityType.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityType.java index 6f0efa1d11b..0c8399eeafe 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityType.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/entity/EntityType.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; @Getter @RequiredArgsConstructor @@ -39,13 +40,16 @@ public enum EntityType { TOKEN(5), SCHEDULE(6); - private final int id; - private static final Map ID_MAP = Arrays.stream(values()) - .collect(Collectors.toUnmodifiableMap(EntityType::getId, Function - .identity())); + .collect(Collectors.toUnmodifiableMap(EntityType::getId, Function.identity())); + + private final int id; public static EntityType fromId(int id) { return ID_MAP.getOrDefault(id, UNKNOWN); } + + public String toDisplayString() { + return StringUtils.capitalize(name().toLowerCase()); + } } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainBuilder.java b/hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainBuilder.java similarity index 62% rename from hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainBuilder.java rename to hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainBuilder.java index 10974d046a4..2758c88259a 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainBuilder.java +++ b/hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainBuilder.java @@ -1,4 +1,4 @@ -package com.hedera.mirror.importer.domain; +package com.hedera.mirror.common.domain; /*- * ‌ @@ -30,24 +30,29 @@ import com.google.protobuf.ByteString; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.SignaturePair; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.SecureRandom; import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import javax.inject.Named; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javax.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import lombok.Value; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; -import org.springframework.core.GenericTypeResolver; -import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionOperations; -import com.hedera.mirror.common.domain.DigestAlgorithm; +import com.hedera.mirror.common.domain.addressbook.AddressBook; +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.common.domain.addressbook.AddressBookServiceEndpoint; import com.hedera.mirror.common.domain.contract.Contract; import com.hedera.mirror.common.domain.contract.ContractLog; import com.hedera.mirror.common.domain.contract.ContractResult; @@ -66,40 +71,83 @@ import com.hedera.mirror.common.domain.transaction.TransactionSignature; import com.hedera.mirror.common.util.DomainUtils; +@Component @Log4j2 -@Named +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class DomainBuilder { public static final int KEY_LENGTH_ECDSA = 33; public static final int KEY_LENGTH_ED25519 = 32; + private final EntityManager entityManager; + private final TransactionOperations transactionOperations; private final AtomicLong id = new AtomicLong(0L); private final Instant now = Instant.now(); private final SecureRandom random = new SecureRandom(); - private final Map, CrudRepository> repositories; // Intended for use by unit tests that don't need persistence public DomainBuilder() { - this(Collections.emptyList()); + this(null, null); } - @Autowired - public DomainBuilder(Collection> crudRepositories) { - repositories = new HashMap<>(); - - for (CrudRepository crudRepository : crudRepositories) { - try { - Class domainClass = GenericTypeResolver.resolveTypeArguments(crudRepository.getClass(), - CrudRepository.class)[0]; - repositories.put(domainClass, crudRepository); - } catch (Exception e) { - log.warn("Unable to map repository {} to domain class", crudRepository.getClass()); - } + public DomainWrapper addressBook() { + AddressBook.AddressBookBuilder builder = AddressBook.builder() + .fileData(bytes(10)) + .fileId(EntityId.of(0L, 0L, 102, FILE)) + .nodeCount(6) + .startConsensusTimestamp(timestamp()) + .endConsensusTimestamp(timestamp()); + return new DomainWrapperImpl<>(builder, builder::build); + } + + public DomainWrapper addressBookEntry() { + return addressBookEntry(0); + } + + public DomainWrapper addressBookEntry(int endpoints) { + long consensusTimestamp = timestamp(); + long nodeId = id(); + AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() + .consensusTimestamp(consensusTimestamp) + .description(text(10)) + .memo(text(10)) + .nodeId(nodeId) + .nodeAccountId(EntityId.of(0L, 0L, nodeId + 3, ACCOUNT)) + .nodeCertHash(bytes(96)) + .publicKey(text(64)) + .stake(0L); + + var serviceEndpoints = new HashSet(); + builder.serviceEndpoints(serviceEndpoints); + + for (int i = 0; i < endpoints; ++i) { + var endpoint = addressBookServiceEndpoint() + .customize(a -> a.consensusTimestamp(consensusTimestamp).nodeId(nodeId)) + .get(); + serviceEndpoints.add(endpoint); } + + return new DomainWrapperImpl<>(builder, builder::build); + } + + public DomainWrapper addressBookServiceEndpoint() { + String ipAddress = ""; + try { + ipAddress = InetAddress.getByAddress(bytes(4)).getHostAddress(); + } catch (UnknownHostException e) { + // This shouldn't happen + } + + AddressBookServiceEndpoint.AddressBookServiceEndpointBuilder builder = AddressBookServiceEndpoint.builder() + .consensusTimestamp(timestamp()) + .ipAddressV4(ipAddress) + .nodeId(id()) + .port(50211); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister contract() { + public DomainWrapper contract() { long id = id(); long timestamp = timestamp(); @@ -120,10 +168,10 @@ public DomainPersister contract() { .timestampRange(Range.atLeast(timestamp)) .type(CONTRACT); - return new DomainPersister<>(getRepository(Contract.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister contractLog() { + public DomainWrapper contractLog() { ContractLog.ContractLogBuilder builder = ContractLog.builder() .bloom(bytes(256)) .consensusTimestamp(timestamp()) @@ -135,10 +183,10 @@ public DomainPersister contractLog( .topic1(bytes(64)) .topic2(bytes(64)) .topic3(bytes(64)); - return new DomainPersister<>(getRepository(ContractLog.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister contractResult() { + public DomainWrapper contractResult() { ContractResult.ContractResultBuilder builder = ContractResult.builder() .amount(1000L) .bloom(bytes(256)) @@ -152,10 +200,10 @@ public DomainPersister con .gasLimit(200L) .gasUsed(100L) .payerAccountId(entityId(ACCOUNT)); - return new DomainPersister<>(getRepository(ContractResult.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister entity() { + public DomainWrapper entity() { long id = id(); long timestamp = timestamp(); @@ -179,29 +227,29 @@ public DomainPersister entity() { .timestampRange(Range.atLeast(timestamp)) .type(ACCOUNT); - return new DomainPersister<>(getRepository(Entity.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister nftTransfer() { + public DomainWrapper nftTransfer() { NftTransfer.NftTransferBuilder builder = NftTransfer.builder() .id(new NftTransferId(timestamp(), 1L, entityId(TOKEN))) .receiverAccountId(entityId(ACCOUNT)) .payerAccountId(entityId(ACCOUNT)) .senderAccountId(entityId(ACCOUNT)); - return new DomainPersister<>(getRepository(NftTransfer.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister nonFeeTransfer() { + public DomainWrapper nonFeeTransfer() { NonFeeTransfer.NonFeeTransferBuilder builder = NonFeeTransfer.builder() .amount(100L) .id(new NonFeeTransfer.Id(timestamp(), entityId(ACCOUNT))) .payerAccountId(entityId(ACCOUNT)); - return new DomainPersister<>(getRepository(NonFeeTransfer.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister recordFile() { + public DomainWrapper recordFile() { long timestamp = timestamp(); RecordFile.RecordFileBuilder builder = RecordFile.builder() .consensusStart(timestamp) @@ -216,20 +264,20 @@ public DomainPersister recordFile() { .name(now.toString().replace(':', '_') + ".rcd") .nodeAccountId(entityId(ACCOUNT)) .previousHash(text(96)); - return new DomainPersister<>(getRepository(RecordFile.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister schedule() { + public DomainWrapper schedule() { Schedule.ScheduleBuilder builder = Schedule.builder() .consensusTimestamp(timestamp()) .creatorAccountId(entityId(ACCOUNT)) .payerAccountId(entityId(ACCOUNT)) .scheduleId(entityId(SCHEDULE).getId()) .transactionBody(bytes(64)); - return new DomainPersister<>(getRepository(Schedule.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister token() { + public DomainWrapper token() { long timestamp = timestamp(); Token.TokenBuilder builder = Token.builder() .createdTimestamp(timestamp) @@ -248,27 +296,27 @@ public DomainPersister token() { .tokenId(new TokenId(entityId(TOKEN))) .treasuryAccountId(entityId(ACCOUNT)) .wipeKey(key()); - return new DomainPersister<>(getRepository(Token.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister tokenTransfer() { + public DomainWrapper tokenTransfer() { TokenTransfer.TokenTransferBuilder builder = TokenTransfer.builder() .amount(100L) .id(new TokenTransfer.Id(timestamp(), entityId(TOKEN), entityId(ACCOUNT))) .payerAccountId(entityId(ACCOUNT)) .tokenDissociate(false); - return new DomainPersister<>(getRepository(TokenTransfer.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } - public DomainPersister transactionSignature() { + public DomainWrapper transactionSignature() { TransactionSignature.TransactionSignatureBuilder builder = TransactionSignature.builder() .consensusTimestamp(timestamp()) .entityId(entityId(ACCOUNT)) .publicKeyPrefix(bytes(16)) .signature(bytes(32)) .type(SignaturePair.SignatureCase.ED25519.getNumber()); - return new DomainPersister<>(getRepository(TransactionSignature.class), builder, builder::build); + return new DomainWrapperImpl<>(builder, builder::build); } // Helper methods @@ -304,7 +352,32 @@ public long timestamp() { return DomainUtils.convertToNanosMax(now.getEpochSecond(), now.getNano()) + id(); } - private CrudRepository getRepository(Class domainClass) { - return (CrudRepository) repositories.get(domainClass); + @Value + private class DomainWrapperImpl implements DomainWrapper { + + private final B builder; + private final Supplier supplier; + + public DomainWrapper customize(Consumer customizer) { + customizer.accept(builder); + return this; + } + + public T get() { + return supplier.get(); + } + + // The DomainBuilder can be used without an active ApplicationContext. If so, this method shouldn't be used. + public T persist() { + T entity = get(); + + if (entityManager == null) { + throw new IllegalStateException("Unable to persist entity without an EntityManager"); + } + + transactionOperations.executeWithoutResult(t -> entityManager.persist(entity)); + log.trace("Inserted {}", entity); + return entity; + } } } diff --git a/hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainWrapper.java b/hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainWrapper.java new file mode 100644 index 00000000000..2c971695397 --- /dev/null +++ b/hedera-mirror-common/src/test/java/com/hedera/mirror/common/domain/DomainWrapper.java @@ -0,0 +1,32 @@ +package com.hedera.mirror.common.domain; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import java.util.function.Consumer; + +public interface DomainWrapper { + + DomainWrapper customize(Consumer customizer); + + T get(); + + T persist(); +} diff --git a/hedera-mirror-grpc/pom.xml b/hedera-mirror-grpc/pom.xml index d5b95bd65ec..4d86d01055b 100644 --- a/hedera-mirror-grpc/pom.xml +++ b/hedera-mirror-grpc/pom.xml @@ -157,6 +157,13 @@ 2.1 + + com.hedera + hedera-mirror-common + ${release.version} + test-jar + test + io.projectreactor reactor-test diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/GrpcProperties.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/GrpcProperties.java index 3b193dff54e..bcce895cd6f 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/GrpcProperties.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/GrpcProperties.java @@ -21,8 +21,6 @@ */ import java.time.Duration; -import java.util.HashMap; -import java.util.Map; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import lombok.Data; @@ -38,9 +36,6 @@ public class GrpcProperties { private boolean checkTopicExists = true; - @NotNull - private Map connectionOptions = new HashMap<>(); - @NotNull private Duration endTimeInterval = Duration.ofSeconds(30); diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/CacheConfiguration.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/CacheConfiguration.java index 091a5d2371c..115ed4862cf 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/CacheConfiguration.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/CacheConfiguration.java @@ -20,23 +20,37 @@ * ‍ */ +import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import com.hedera.mirror.grpc.GrpcProperties; +import com.hedera.mirror.grpc.service.AddressBookProperties; @Configuration @ConditionalOnProperty(prefix = "spring.cache", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableCaching public class CacheConfiguration { + public static final String ADDRESS_BOOK_ENTRY_CACHE = "addressBookEntryCache"; public static final String ENTITY_CACHE = "entityCache"; + @Bean(ADDRESS_BOOK_ENTRY_CACHE) + CacheManager addressBookEntryCache(AddressBookProperties addressBookProperties) { + CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); + caffeineCacheManager.setCaffeine(Caffeine.newBuilder() + .expireAfterWrite(addressBookProperties.getCacheExpiry()) + .maximumSize(addressBookProperties.getCacheSize())); + return caffeineCacheManager; + } + @Bean(ENTITY_CACHE) + @Primary CacheManager entityCache(GrpcProperties grpcProperties) { int cacheSize = grpcProperties.getEntityCacheSize(); CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/GrpcConfiguration.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/GrpcConfiguration.java index 9c9277f9ac2..a2383dbdbfc 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/GrpcConfiguration.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/config/GrpcConfiguration.java @@ -34,13 +34,15 @@ import net.devh.boot.grpc.server.service.GrpcServiceDiscoverer; import org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.hedera.mirror.grpc.GrpcProperties; @Configuration -public class GrpcConfiguration { +@EntityScan({"com.hedera.mirror.common.domain.addressbook", "com.hedera.mirror.grpc.domain"}) +class GrpcConfiguration { @Bean CompositeHealthContributor grpcServices(GrpcServiceDiscoverer grpcServiceDiscoverer, @@ -57,7 +59,7 @@ CompositeHealthContributor grpcServices(GrpcServiceDiscoverer grpcServiceDiscove } @Bean - public GrpcServerConfigurer grpcServerConfigurer(GrpcProperties grpcProperties) { + GrpcServerConfigurer grpcServerConfigurer(GrpcProperties grpcProperties) { NettyProperties nettyProperties = grpcProperties.getNetty(); Executor executor = new ThreadPoolExecutor( nettyProperties.getExecutorCoreThreadCount(), diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/ConsensusController.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/ConsensusController.java index ee873074760..8d5c2ac765e 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/ConsensusController.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/ConsensusController.java @@ -21,29 +21,21 @@ */ import com.hederahashgraph.api.proto.java.Timestamp; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; import java.time.Instant; -import java.util.concurrent.TimeoutException; -import javax.validation.ConstraintViolationException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import net.devh.boot.grpc.server.service.GrpcService; -import org.springframework.dao.NonTransientDataAccessResourceException; -import org.springframework.dao.TransientDataAccessException; -import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import com.hedera.mirror.api.proto.ConsensusTopicQuery; import com.hedera.mirror.api.proto.ConsensusTopicResponse; import com.hedera.mirror.api.proto.ReactorConsensusServiceGrpc; +import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.grpc.converter.InstantToLongConverter; import com.hedera.mirror.grpc.domain.TopicMessage; import com.hedera.mirror.grpc.domain.TopicMessageFilter; -import com.hedera.mirror.grpc.exception.TopicNotFoundException; import com.hedera.mirror.grpc.service.TopicMessageService; -import com.hedera.mirror.grpc.util.EntityId; import com.hedera.mirror.grpc.util.ProtoUtil; /** @@ -57,10 +49,6 @@ @RequiredArgsConstructor public class ConsensusController extends ReactorConsensusServiceGrpc.ConsensusServiceImplBase { - private static final String DB_ERROR = "Error querying the data source. Please retry later"; - private static final String OVERFLOW_ERROR = "Client lags too much behind. Please retry later"; - private static final String UNKNOWN_ERROR = "Unknown error subscribing to topic"; - private final TopicMessageService topicMessageService; @Override @@ -68,62 +56,30 @@ public Flux subscribeTopic(Mono req return request.map(this::toFilter) .flatMapMany(topicMessageService::subscribeTopic) .map(TopicMessage::getResponse) - .onErrorMap(this::mapError); // consolidate error mappings to avoid deep flux operation chaining + .onErrorMap(ProtoUtil::toStatusRuntimeException); } private TopicMessageFilter toFilter(ConsensusTopicQuery query) { - if (!query.hasTopicID()) { - throw new IllegalArgumentException("Missing required topicID"); - } + var filter = TopicMessageFilter.builder() + .limit(query.getLimit()); - Long topicId = EntityId.encode(query.getTopicID()); - if (topicId == null) { - throw new IllegalArgumentException("Invalid entity ID"); + if (query.hasTopicID()) { + filter.topicId(EntityId.of(query.getTopicID())); } - TopicMessageFilter.TopicMessageFilterBuilder builder = TopicMessageFilter.builder() - .limit(query.getLimit()) - .topicId(topicId); - if (query.hasConsensusStartTime()) { Timestamp startTimeStamp = query.getConsensusStartTime(); Instant startInstant = ProtoUtil.fromTimestamp(startTimeStamp); - builder.startTime(startInstant.isBefore(Instant.EPOCH) ? Instant.EPOCH : startInstant); + filter.startTime(startInstant.isBefore(Instant.EPOCH) ? Instant.EPOCH : startInstant); } if (query.hasConsensusEndTime()) { Timestamp endTimeStamp = query.getConsensusEndTime(); Instant endInstant = ProtoUtil.fromTimestamp(endTimeStamp); - builder.endTime(endInstant.isAfter(InstantToLongConverter.LONG_MAX_INSTANT) ? + filter.endTime(endInstant.isAfter(InstantToLongConverter.LONG_MAX_INSTANT) ? InstantToLongConverter.LONG_MAX_INSTANT : endInstant); } - return builder.build(); - } - - private StatusRuntimeException mapError(Throwable t) { - if (t instanceof ConstraintViolationException || t instanceof IllegalArgumentException) { - return clientError(t, Status.INVALID_ARGUMENT, t.getMessage()); - } else if (Exceptions.isOverflow(t)) { - return clientError(t, Status.DEADLINE_EXCEEDED, OVERFLOW_ERROR); - } else if (t instanceof NonTransientDataAccessResourceException) { - return serverError(t, Status.UNAVAILABLE, DB_ERROR); - } else if (t instanceof TopicNotFoundException) { - return clientError(t, Status.NOT_FOUND, t.getMessage()); - } else if (t instanceof TransientDataAccessException || t instanceof TimeoutException) { - return serverError(t, Status.RESOURCE_EXHAUSTED, DB_ERROR); - } else { - return serverError(t, Status.UNKNOWN, UNKNOWN_ERROR); - } - } - - private StatusRuntimeException clientError(Throwable t, Status status, String message) { - log.warn("Client error {} subscribing to topic: {}", t.getClass().getSimpleName(), t.getMessage()); - return status.augmentDescription(message).asRuntimeException(); - } - - private StatusRuntimeException serverError(Throwable t, Status status, String message) { - log.error("Server error subscribing to topic: ", t); - return status.augmentDescription(message).asRuntimeException(); + return filter.build(); } } diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/NetworkController.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/NetworkController.java new file mode 100644 index 00000000000..b308ec04603 --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/controller/NetworkController.java @@ -0,0 +1,110 @@ +package com.hedera.mirror.grpc.controller; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import com.google.protobuf.ByteString; +import com.hederahashgraph.api.proto.java.NodeAddress; +import com.hederahashgraph.api.proto.java.ServiceEndpoint; +import java.net.InetAddress; +import java.net.UnknownHostException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import net.devh.boot.grpc.server.service.GrpcService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import com.hedera.mirror.api.proto.AddressBookQuery; +import com.hedera.mirror.api.proto.ReactorNetworkServiceGrpc; +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.grpc.domain.AddressBookFilter; +import com.hedera.mirror.grpc.service.NetworkService; +import com.hedera.mirror.grpc.util.ProtoUtil; + +@GrpcService +@Log4j2 +@RequiredArgsConstructor +public class NetworkController extends ReactorNetworkServiceGrpc.NetworkServiceImplBase { + + private final NetworkService networkService; + + @Override + public Flux getNodes(Mono request) { + return request.map(this::toFilter) + .flatMapMany(networkService::getNodes) + .map(this::toNodeAddress) + .onErrorMap(ProtoUtil::toStatusRuntimeException); + } + + private AddressBookFilter toFilter(AddressBookQuery query) { + var filter = AddressBookFilter.builder() + .limit(query.getLimit()); + + if (query.hasFileId()) { + filter.fileId(EntityId.of(query.getFileId())); + } + + return filter.build(); + } + + @SuppressWarnings("deprecation") + private NodeAddress toNodeAddress(AddressBookEntry addressBookEntry) { + var nodeAddress = NodeAddress.newBuilder() + .setNodeAccountId(ProtoUtil.toAccountID(addressBookEntry.getNodeAccountId())) + .setNodeId(addressBookEntry.getNodeId()); + + if (addressBookEntry.getDescription() != null) { + nodeAddress.setDescription(addressBookEntry.getDescription()); + } + + if (addressBookEntry.getMemo() != null) { + nodeAddress.setMemo(ByteString.copyFromUtf8(addressBookEntry.getMemo())); + } + + if (addressBookEntry.getNodeCertHash() != null) { + nodeAddress.setNodeCertHash(ProtoUtil.toByteString(addressBookEntry.getNodeCertHash())); + } + + if (addressBookEntry.getPublicKey() != null) { + nodeAddress.setRSAPubKey(addressBookEntry.getPublicKey()); + } + + if (addressBookEntry.getStake() != null) { + nodeAddress.setStake(addressBookEntry.getStake()); + } + + for (var s : addressBookEntry.getServiceEndpoints()) { + try { + var ipAddressV4 = InetAddress.getByName(s.getIpAddressV4()).getAddress(); + var serviceEndpoint = ServiceEndpoint.newBuilder() + .setIpAddressV4(ProtoUtil.toByteString(ipAddressV4)) + .setPort(s.getPort()) + .build(); + nodeAddress.addServiceEndpoint(serviceEndpoint); + } catch (UnknownHostException e) { + // Shouldn't occur since we never pass hostnames to InetAddress.getByName() + log.warn("Unable to convert IP address to byte array", e.getMessage()); + } + } + + return nodeAddress.build(); + } +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverter.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverter.java deleted file mode 100644 index f8776c6ff15..00000000000 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverter.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.hedera.mirror.grpc.converter; - -/*- - * ‌ - * Hedera Mirror Node - * ​ - * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC - * ​ - * 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. - * ‍ - */ - -import javax.inject.Named; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.WritingConverter; - -import com.hedera.mirror.common.domain.entity.EntityType; -import com.hedera.mirror.grpc.domain.Entity; - -@Named -@WritingConverter -public class EncodedIdToEntityConverter implements Converter { - public static final EncodedIdToEntityConverter INSTANCE = new EncodedIdToEntityConverter(); - - static final int REALM_BITS = 16; - static final int NUM_BITS = 32; // bits for entity num - private static final long REALM_MASK = (1L << REALM_BITS) - 1; - private static final long NUM_MASK = (1L << NUM_BITS) - 1; - - @Override - public Entity convert(Long encodedId) { - if (encodedId == null || encodedId < 0) { - return null; - } - - long shard = encodedId >> (REALM_BITS + NUM_BITS); - long realm = (encodedId >> NUM_BITS) & REALM_MASK; - long num = encodedId & NUM_MASK; - - Entity.EntityBuilder builder = Entity.builder() - .num(num) - .realm(realm) - .shard(shard) - .id(encodedId) - .type(EntityType.ACCOUNT); - - return builder.build(); - } -} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/AddressBookFilter.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/AddressBookFilter.java new file mode 100644 index 00000000000..540dac3e0db --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/AddressBookFilter.java @@ -0,0 +1,38 @@ +package com.hedera.mirror.grpc.domain; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Value; + +import com.hedera.mirror.common.domain.entity.EntityId; + +@Builder +@Value +public class AddressBookFilter { + @NotNull + private final EntityId fileId; + + @Min(0) + private final int limit; +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessage.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessage.java index b2599c79931..911836da1c5 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessage.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessage.java @@ -25,10 +25,12 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.UnsafeByteOperations; -import com.hederahashgraph.api.proto.java.AccountID; + +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; +import com.hedera.mirror.grpc.util.ProtoUtil; + import com.hederahashgraph.api.proto.java.ConsensusMessageChunkInfo; -import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.TransactionID; import java.time.Instant; import java.util.Comparator; @@ -46,7 +48,6 @@ import org.springframework.data.domain.Persistable; import com.hedera.mirror.api.proto.ConsensusTopicResponse; -import com.hedera.mirror.grpc.converter.EncodedIdToEntityConverter; import com.hedera.mirror.grpc.converter.InstantToLongConverter; import com.hedera.mirror.grpc.converter.LongToInstantConverter; @@ -95,17 +96,8 @@ public class TopicMessage implements Comparable, Persistable @ToString.Exclude private Long payerAccountId; - @Getter(lazy = true) - @Transient - private com.hedera.mirror.grpc.domain.Entity payerAccountEntity = EncodedIdToEntityConverter.INSTANCE - .convert(payerAccountId); - private Long validStartTimestamp; - @Getter(lazy = true) - @Transient - private Instant validStartInstant = LongToInstantConverter.INSTANCE.convert(validStartTimestamp); - // Cache this to avoid paying the conversion penalty for multiple subscribers to the same topic @EqualsAndHashCode.Exclude @Getter(lazy = true) @@ -115,12 +107,9 @@ public class TopicMessage implements Comparable, Persistable private ConsensusTopicResponse toResponse() { var consensusTopicResponseBuilder = ConsensusTopicResponse.newBuilder() - .setConsensusTimestamp(Timestamp.newBuilder() - .setSeconds(getConsensusTimestampInstant().getEpochSecond()) - .setNanos(getConsensusTimestampInstant().getNano()) - .build()) - .setMessage(UnsafeByteOperations.unsafeWrap(message)) - .setRunningHash(UnsafeByteOperations.unsafeWrap(runningHash)) + .setConsensusTimestamp(ProtoUtil.toTimestamp(getConsensusTimestampInstant())) + .setMessage(ProtoUtil.toByteString(message)) + .setRunningHash(ProtoUtil.toByteString(runningHash)) .setRunningHashVersion(runningHashVersion) .setSequenceNumber(sequenceNumber); @@ -130,19 +119,15 @@ private ConsensusTopicResponse toResponse() { .setTotal(getChunkTotal()); TransactionID transactionID = parseTransactionID(initialTransactionId); + EntityId payerAccountEntity = EntityId.of(payerAccountId, EntityType.ACCOUNT); + Instant validStartInstant = LongToInstantConverter.INSTANCE.convert(validStartTimestamp); + if (transactionID != null) { chunkBuilder.setInitialTransactionID(transactionID); - } else if (getPayerAccountEntity() != null && getValidStartTimestamp() != null) { + } else if (payerAccountEntity != null && validStartInstant != null) { chunkBuilder.setInitialTransactionID(TransactionID.newBuilder() - .setAccountID(AccountID.newBuilder() - .setShardNum(getPayerAccountEntity().getShard()) - .setRealmNum(getPayerAccountEntity().getRealm()) - .setAccountNum(getPayerAccountEntity().getNum()) - .build()) - .setTransactionValidStart(Timestamp.newBuilder() - .setSeconds(getValidStartInstant().getEpochSecond()) - .setNanos(getValidStartInstant().getNano()) - .build()) + .setAccountID(ProtoUtil.toAccountID(payerAccountEntity)) + .setTransactionValidStart(ProtoUtil.toTimestamp(validStartInstant)) .build()); } @@ -167,18 +152,6 @@ public boolean isNew() { return true; } - public static class TopicMessageBuilder { - public TopicMessageBuilder consensusTimestamp(Instant consensusTimestamp) { - this.consensusTimestamp = InstantToLongConverter.INSTANCE.convert(consensusTimestamp); - return this; - } - - public TopicMessageBuilder validStartTimestamp(Instant validStartTimestamp) { - this.validStartTimestamp = InstantToLongConverter.INSTANCE.convert(validStartTimestamp); - return this; - } - } - private TransactionID parseTransactionID(byte[] transactionIdBytes) { if (transactionIdBytes == null) { return null; @@ -190,4 +163,16 @@ private TransactionID parseTransactionID(byte[] transactionIdBytes) { return null; } } + + public static class TopicMessageBuilder { + public TopicMessageBuilder consensusTimestamp(Instant consensusTimestamp) { + this.consensusTimestamp = InstantToLongConverter.INSTANCE.convert(consensusTimestamp); + return this; + } + + public TopicMessageBuilder validStartTimestamp(Instant validStartTimestamp) { + this.validStartTimestamp = InstantToLongConverter.INSTANCE.convert(validStartTimestamp); + return this; + } + } } diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessageFilter.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessageFilter.java index 8f7ca062672..3bd9e580abc 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessageFilter.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/domain/TopicMessageFilter.java @@ -24,6 +24,9 @@ import java.time.Instant; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; + +import com.hedera.mirror.common.domain.entity.EntityId; + import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -60,8 +63,8 @@ public class TopicMessageFilter { @Builder.Default private String subscriberId = RandomStringUtils.random(8, 0, 0, true, true, null, RANDOM); - @Min(0) - private long topicId; + @NotNull + private EntityId topicId; public boolean hasLimit() { return limit > 0; diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/TopicNotFoundException.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/EntityNotFoundException.java similarity index 72% rename from hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/TopicNotFoundException.java rename to hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/EntityNotFoundException.java index 84b9b3becf3..5492ccf51fa 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/TopicNotFoundException.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/exception/EntityNotFoundException.java @@ -20,14 +20,15 @@ * ‍ */ +import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.exception.MirrorNodeException; -public class TopicNotFoundException extends MirrorNodeException { +public class EntityNotFoundException extends MirrorNodeException { - private static final String MESSAGE = "Topic does not exist"; + private static final String MESSAGE = "%s %s does not exist"; private static final long serialVersionUID = 809036847722840635L; - public TopicNotFoundException() { - super(MESSAGE); + public EntityNotFoundException(EntityId entityId) { + super(String.format(MESSAGE, entityId.getType().toDisplayString(), entityId)); } } diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/CompositeTopicListener.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/CompositeTopicListener.java index e13bd4558f2..3119352c284 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/CompositeTopicListener.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/CompositeTopicListener.java @@ -86,7 +86,7 @@ private TopicListener getTopicListener() { } private boolean filterMessage(TopicMessage message, TopicMessageFilter filter) { - return message.getTopicId() == filter.getTopicId() && + return message.getTopicId() == filter.getTopicId().getId() && message.getConsensusTimestamp() >= filter.getStartTimeLong(); } diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/RedisTopicListener.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/RedisTopicListener.java index cc5f941f6c5..30fa50e67d8 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/RedisTopicListener.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/listener/RedisTopicListener.java @@ -76,7 +76,7 @@ protected Flux getSharedListener(TopicMessageFilter filter) { } private Topic getTopic(TopicMessageFilter filter) { - return ChannelTopic.of(String.format("topic.%d", filter.getTopicId())); + return ChannelTopic.of(String.format("topic.%d", filter.getTopicId().getId())); } private Flux subscribe(Topic topic) { diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepository.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepository.java new file mode 100644 index 00000000000..ba4cf8bc5e8 --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepository.java @@ -0,0 +1,38 @@ +package com.hedera.mirror.grpc.repository; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import java.util.List; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.grpc.config.CacheConfiguration; + +public interface AddressBookEntryRepository extends CrudRepository { + + @Cacheable(cacheManager = CacheConfiguration.ADDRESS_BOOK_ENTRY_CACHE, cacheNames = "address_book_entry", + unless = "#result == null or #result.size() == 0") + @Query(value = "select * from address_book_entry where consensus_timestamp = ? and node_id >= ? " + + "order by node_id asc limit ?", nativeQuery = true) + List findByConsensusTimestampAndNodeId(long consensusTimestamp, long nodeId, int limit); +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookRepository.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookRepository.java new file mode 100644 index 00000000000..43b076e3da7 --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/AddressBookRepository.java @@ -0,0 +1,33 @@ +package com.hedera.mirror.grpc.repository; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import java.util.Optional; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + +import com.hedera.mirror.common.domain.addressbook.AddressBook; + +public interface AddressBookRepository extends CrudRepository { + + @Query(value = "select max(start_consensus_timestamp) from address_book where file_id = ?", nativeQuery = true) + Optional findLatestTimestamp(long fileId); +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryCustomImpl.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryCustomImpl.java index 6b9cd8b3e49..a9430e5edb0 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryCustomImpl.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryCustomImpl.java @@ -55,7 +55,7 @@ public Stream findByFilter(TopicMessageFilter filter) { Root root = query.from(TopicMessage.class); Predicate predicate = cb.and( - cb.equal(root.get("topicId"), filter.getTopicId()), + cb.equal(root.get("topicId"), filter.getTopicId().getId()), cb.greaterThanOrEqualTo(root.get("consensusTimestamp"), converter.convert(filter.getStartTime())) ); diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/AddressBookProperties.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/AddressBookProperties.java new file mode 100644 index 00000000000..26c4ca029c2 --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/AddressBookProperties.java @@ -0,0 +1,53 @@ +package com.hedera.mirror.grpc.service; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import java.time.Duration; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.time.DurationMin; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Data +@Validated +@ConfigurationProperties("hedera.mirror.grpc.addressbook") +public class AddressBookProperties { + + @DurationMin(millis = 500L) + @NotNull + private Duration cacheExpiry = Duration.ofSeconds(5); + + @Min(0) + private long cacheSize = 50L; + + @DurationMin(millis = 100L) + @NotNull + private Duration maxPageDelay = Duration.ofMillis(250L); + + @DurationMin(millis = 100L) + @NotNull + private Duration minPageDelay = Duration.ofMillis(100L); + + @Min(1) + private int pageSize = 10; +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkService.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkService.java new file mode 100644 index 00000000000..8150db491e6 --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkService.java @@ -0,0 +1,32 @@ +package com.hedera.mirror.grpc.service; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import javax.validation.Valid; +import reactor.core.publisher.Flux; + +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.grpc.domain.AddressBookFilter; + +public interface NetworkService { + + Flux getNodes(@Valid AddressBookFilter addressBookFilter); +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkServiceImpl.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkServiceImpl.java new file mode 100644 index 00000000000..3205fb4ff3f --- /dev/null +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/NetworkServiceImpl.java @@ -0,0 +1,127 @@ +package com.hedera.mirror.grpc.service; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import javax.inject.Named; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import org.springframework.validation.annotation.Validated; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; +import reactor.retry.Jitter; +import reactor.retry.Repeat; + +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; +import com.hedera.mirror.grpc.domain.AddressBookFilter; +import com.hedera.mirror.grpc.exception.EntityNotFoundException; +import com.hedera.mirror.grpc.repository.AddressBookEntryRepository; +import com.hedera.mirror.grpc.repository.AddressBookRepository; + +@Log4j2 +@Named +@RequiredArgsConstructor +@Validated +public class NetworkServiceImpl implements NetworkService { + + static final String INVALID_FILE_ID = "Not a valid address book file"; + private static final Collection VALID_FILE_IDS = Set.of( + EntityId.of(0L, 0L, 101L, EntityType.FILE), + EntityId.of(0L, 0L, 102L, EntityType.FILE) + ); + + private final AddressBookProperties addressBookProperties; + private final AddressBookRepository addressBookRepository; + private final AddressBookEntryRepository addressBookEntryRepository; + + @Override + public Flux getNodes(AddressBookFilter filter) { + var fileId = filter.getFileId(); + if (!VALID_FILE_IDS.contains(fileId)) { + throw new IllegalArgumentException(INVALID_FILE_ID); + } + + long timestamp = addressBookRepository.findLatestTimestamp(fileId.getId()) + .orElseThrow(() -> new EntityNotFoundException(fileId)); + var context = new AddressBookContext(timestamp); + + return Flux.defer(() -> page(context)) + .repeatWhen(Repeat.onlyIf(c -> !context.isComplete()) + .randomBackoff(addressBookProperties.getMinPageDelay(), addressBookProperties.getMaxPageDelay()) + .jitter(Jitter.random()) + .withBackoffScheduler(Schedulers.parallel())) + .take(filter.getLimit() > 0 ? filter.getLimit() : Long.MAX_VALUE) + .name("addressBook") + .metrics() + .doOnNext(context::onNext) + .doOnSubscribe(s -> log.info("Querying for address book: {}", filter)) + .doOnComplete(() -> log.info("Retrieved {} nodes from the address book", context.getCount())); + } + + private Flux page(AddressBookContext context) { + var timestamp = context.getTimestamp(); + var nextNodeId = context.getNextNodeId(); + var pageSize = addressBookProperties.getPageSize(); + var nodes = addressBookEntryRepository.findByConsensusTimestampAndNodeId(timestamp, nextNodeId, pageSize); + + if (nodes.size() < pageSize) { + context.completed(); + } + + log.info("Retrieved {} address book entries for timestamp {} and node ID {}", + nodes.size(), timestamp, nextNodeId); + return Flux.fromIterable(nodes); + } + + @Value + private class AddressBookContext { + + private final AtomicBoolean complete = new AtomicBoolean(false); + private final AtomicLong count = new AtomicLong(0L); + private final AtomicReference last = new AtomicReference<>(); + private final long timestamp; + + void onNext(AddressBookEntry entry) { + count.incrementAndGet(); + last.set(entry); + } + + long getNextNodeId() { + AddressBookEntry entry = last.get(); + return entry != null ? entry.getNodeId() + 1 : 0L; + } + + boolean isComplete() { + return complete.get(); + } + + void completed() { + complete.set(true); + } + } +} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/TopicMessageServiceImpl.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/TopicMessageServiceImpl.java index d904a11223c..27d46770f14 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/TopicMessageServiceImpl.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/service/TopicMessageServiceImpl.java @@ -38,12 +38,13 @@ import reactor.core.publisher.SignalType; import reactor.retry.Repeat; +import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.grpc.GrpcProperties; import com.hedera.mirror.grpc.domain.Entity; import com.hedera.mirror.grpc.domain.TopicMessage; import com.hedera.mirror.grpc.domain.TopicMessageFilter; -import com.hedera.mirror.grpc.exception.TopicNotFoundException; +import com.hedera.mirror.grpc.exception.EntityNotFoundException; import com.hedera.mirror.grpc.listener.TopicListener; import com.hedera.mirror.grpc.repository.EntityRepository; import com.hedera.mirror.grpc.retriever.TopicMessageRetriever; @@ -93,9 +94,9 @@ public Flux subscribeTopic(TopicMessageFilter filter) { } private Mono topicExists(TopicMessageFilter filter) { - return Mono.justOrEmpty(entityRepository - .findById(filter.getTopicId())) - .switchIfEmpty(grpcProperties.isCheckTopicExists() ? Mono.error(new TopicNotFoundException()) : + var topicId = filter.getTopicId(); + return Mono.justOrEmpty(entityRepository.findById(topicId.getId())) + .switchIfEmpty(grpcProperties.isCheckTopicExists() ? Mono.error(new EntityNotFoundException(topicId)) : Mono.just(Entity.builder().type(EntityType.TOPIC).build())) .filter(e -> e.getType() == EntityType.TOPIC) .switchIfEmpty(Mono.error(new IllegalArgumentException("Not a valid topic"))); @@ -179,7 +180,7 @@ private class TopicContext { private final AtomicReference last; private final Instant startTime; private final Stopwatch stopwatch; - private final long topicId; + private final EntityId topicId; private TopicContext(TopicMessageFilter filter) { this.count = new AtomicLong(0L); diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/EntityId.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/EntityId.java deleted file mode 100644 index beb4b7a47d5..00000000000 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/EntityId.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.hedera.mirror.grpc.util; - -/*- - * ‌ - * Hedera Mirror Node - * ​ - * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC - * ​ - * 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. - * ‍ - */ - -import com.hederahashgraph.api.proto.java.TopicID; -import lombok.experimental.UtilityClass; - -/** - * Encodes given shard, realm, num into 8 bytes long. - *

- * Only 63 bits (excluding signed bit) are used for encoding to make it easy to encode/decode using mathematical - * operations too. That's because javascript's (REST server) support for bitwise operations is very limited (truncates - * numbers to 32 bits internally before bitwise operation). - *

- * Format:
First bit (sign bit) is left 0.
Next 15 bits are for shard, followed by 16 bits for realm, and - * then 32 bits for entity num.
This encoding will support following ranges:
shard: 0 - 32767
realm: 0 - * - 65535
num: 0 - 4294967295
Placing entity num in the end has the advantage that encoded ids <= - * 4294967295 will also be human readable. - */ -@UtilityClass -public final class EntityId { - - static final int SHARD_BITS = 15; - static final int REALM_BITS = 16; - static final int NUM_BITS = 32; // bits for entity num - private static final long SHARD_MASK = (1L << SHARD_BITS) - 1; - private static final long REALM_MASK = (1L << REALM_BITS) - 1; - private static final long NUM_MASK = (1L << NUM_BITS) - 1; - - public static Long encode(TopicID topicID) { - if (topicID == null) { - return null; - } - - if (topicID.getShardNum() > SHARD_MASK || topicID.getShardNum() < 0 || - topicID.getRealmNum() > REALM_MASK || topicID.getRealmNum() < 0 || - topicID.getTopicNum() > NUM_MASK || topicID.getTopicNum() < 0) { - return null; - } - - return (topicID.getTopicNum() & NUM_MASK) | - (topicID.getRealmNum() & REALM_MASK) << NUM_BITS | - (topicID.getShardNum() & SHARD_MASK) << (REALM_BITS + NUM_BITS); - } -} diff --git a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/ProtoUtil.java b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/ProtoUtil.java index 56f1e13bd42..6827f59d260 100644 --- a/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/ProtoUtil.java +++ b/hedera-mirror-grpc/src/main/java/com/hedera/mirror/grpc/util/ProtoUtil.java @@ -20,14 +20,34 @@ * ‍ */ +import com.google.protobuf.ByteString; +import com.google.protobuf.UnsafeByteOperations; +import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Timestamp; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import java.time.Instant; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; +import java.util.concurrent.TimeoutException; +import javax.validation.ConstraintViolationException; +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import org.springframework.dao.NonTransientDataAccessResourceException; +import org.springframework.dao.TransientDataAccessException; +import reactor.core.Exceptions; -@NoArgsConstructor(access = AccessLevel.PRIVATE) +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.exception.InvalidEntityException; +import com.hedera.mirror.grpc.exception.EntityNotFoundException; + +@Log4j2 +@UtilityClass public final class ProtoUtil { - public static final Instant fromTimestamp(Timestamp timestamp) { + + static final String DB_ERROR = "Error querying the data source. Please retry later"; + static final String OVERFLOW_ERROR = "Client lags too much behind. Please retry later"; + static final String UNKNOWN_ERROR = "Unknown error"; + + public static Instant fromTimestamp(Timestamp timestamp) { if (timestamp == null) { return null; } @@ -35,12 +55,52 @@ public static final Instant fromTimestamp(Timestamp timestamp) { return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos()); } - public static final Timestamp toTimestamp(Instant instant) { + public static AccountID toAccountID(EntityId entityId) { + return AccountID.newBuilder() + .setShardNum(entityId.getShardNum()) + .setRealmNum(entityId.getRealmNum()) + .setAccountNum(entityId.getEntityNum()) + .build(); + } + + public static ByteString toByteString(byte[] bytes) { + if (bytes == null) { + return ByteString.EMPTY; + } + return UnsafeByteOperations.unsafeWrap(bytes); + } + + public static StatusRuntimeException toStatusRuntimeException(Throwable t) { + if (Exceptions.isOverflow(t)) { + return clientError(t, Status.DEADLINE_EXCEEDED, OVERFLOW_ERROR); + } else if (t instanceof ConstraintViolationException || t instanceof IllegalArgumentException || t instanceof InvalidEntityException) { + return clientError(t, Status.INVALID_ARGUMENT, t.getMessage()); + } else if (t instanceof EntityNotFoundException) { + return clientError(t, Status.NOT_FOUND, t.getMessage()); + } else if (t instanceof TransientDataAccessException || t instanceof TimeoutException) { + return serverError(t, Status.RESOURCE_EXHAUSTED, DB_ERROR); + } else if (t instanceof NonTransientDataAccessResourceException) { + return serverError(t, Status.UNAVAILABLE, DB_ERROR); + } else { + return serverError(t, Status.UNKNOWN, UNKNOWN_ERROR); + } + } + + private static StatusRuntimeException clientError(Throwable t, Status status, String message) { + log.warn("Client error {}: {}", t.getClass().getSimpleName(), t.getMessage()); + return status.augmentDescription(message).asRuntimeException(); + } + + private static StatusRuntimeException serverError(Throwable t, Status status, String message) { + log.error("Server error: ", t); + return status.augmentDescription(message).asRuntimeException(); + } + + public static Timestamp toTimestamp(Instant instant) { if (instant == null) { return null; } - return Timestamp - .newBuilder() + return Timestamp.newBuilder() .setSeconds(instant.getEpochSecond()) .setNanos(instant.getNano()) .build(); diff --git a/hedera-mirror-grpc/src/main/resources/application.yml b/hedera-mirror-grpc/src/main/resources/application.yml index 1ec95a980fb..61bf140a7dd 100644 --- a/hedera-mirror-grpc/src/main/resources/application.yml +++ b/hedera-mirror-grpc/src/main/resources/application.yml @@ -50,6 +50,8 @@ management: server: compression: enabled: true + http2: + enabled: true port: 8081 shutdown: graceful spring: diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/GrpcIntegrationTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/GrpcIntegrationTest.java index 0b578aebd45..5681e552271 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/GrpcIntegrationTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/GrpcIntegrationTest.java @@ -20,26 +20,55 @@ * ‍ */ +import javax.annotation.Resource; +import javax.persistence.EntityManager; + +import java.time.Instant; +import java.util.Collection; + +import com.hedera.mirror.common.domain.DomainBuilder; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.support.TransactionOperations; +import org.springframework.transaction.support.TransactionTemplate; -@TestExecutionListeners(value = {ResetCacheTestExecutionListener.class}, - mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) // Same database is used for all tests, so clean it up before each test. @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:cleanup.sql") @Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, scripts = "classpath:cleanup.sql") @SpringBootTest +@Import(GrpcIntegrationTest.Config.class) public abstract class GrpcIntegrationTest { protected final Logger log = LogManager.getLogger(getClass()); + @Resource + private Collection cacheManagers; + @BeforeEach void logTest(TestInfo testInfo) { + reset(); log.info("Executing: {}", testInfo.getDisplayName()); } + + private void reset() { + cacheManagers.forEach(c -> c.getCacheNames().forEach(name -> c.getCache(name).clear())); + } + + @TestConfiguration + static class Config { + @Bean + DomainBuilder domainBuilder(EntityManager entityManager, TransactionOperations transactionOperations) { + return new DomainBuilder(entityManager, transactionOperations); + } + } } diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/ResetCacheTestExecutionListener.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/ResetCacheTestExecutionListener.java deleted file mode 100644 index 2c139041549..00000000000 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/ResetCacheTestExecutionListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.hedera.mirror.grpc; - -/*- - * ‌ - * Hedera Mirror Node - * ​ - * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC - * ​ - * 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. - * ‍ - */ - -import org.springframework.cache.CacheManager; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.TestExecutionListener; - -// Clears all caches before each test is run (equivalent of @BeforeEach). -public class ResetCacheTestExecutionListener implements TestExecutionListener { - @Override - public void beforeTestMethod(TestContext testContext) { - testContext.getApplicationContext().getBeansOfType(CacheManager.class).forEach( - (cacheManagerName, cacheManager) -> - cacheManager.getCacheNames().forEach(name -> cacheManager.getCache(name).clear()) - ); - } -} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/ConsensusControllerTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/ConsensusControllerTest.java index 50ef154a153..1eaa6371d2a 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/ConsensusControllerTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/ConsensusControllerTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -55,7 +56,7 @@ class ConsensusControllerTest extends GrpcIntegrationTest { @GrpcClient("local") private ConsensusServiceGrpc.ConsensusServiceBlockingStub blockingService; - @Resource + @Autowired private DomainBuilder domainBuilder; @Resource @@ -77,7 +78,7 @@ void missingTopicID() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder().build(); grpcConsensusService.subscribeTopic(Mono.just(query)) .as(StepVerifier::create) - .expectErrorSatisfies(t -> assertException(t, Status.Code.INVALID_ARGUMENT, "Missing required topicID")) + .expectErrorSatisfies(t -> assertException(t, Status.Code.INVALID_ARGUMENT, "topicId: must not be null")) .verify(Duration.ofMillis(500)); } @@ -115,7 +116,7 @@ void subscribeTopicReactive() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder() .setLimit(5L) .setConsensusStartTime(Timestamp.newBuilder().setSeconds(0).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); Flux generator = domainBuilder.topicMessages(2, Instant.now().plusSeconds(10L)); @@ -140,7 +141,7 @@ void subscribeTopicBlocking() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder() .setLimit(3L) .setConsensusStartTime(Timestamp.newBuilder().setSeconds(0).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); assertThat(blockingService.subscribeTopic(query)) @@ -159,7 +160,7 @@ void subscribeTopicQueryPreEpochStartTime() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder() .setLimit(5L) .setConsensusStartTime(Timestamp.newBuilder().setSeconds(-123).setNanos(-456).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); Flux generator = domainBuilder.topicMessages(2, Instant.now().plusSeconds(10L)); @@ -187,7 +188,7 @@ void subscribeTopicQueryLongOverflowEndTime() { .setConsensusStartTime(Timestamp.newBuilder().setSeconds(1).setNanos(2).build()) .setConsensusEndTime(Timestamp.newBuilder().setSeconds(31556889864403199L) .setNanos(999999999).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); Flux generator = domainBuilder.topicMessages(2, Instant.now().plusSeconds(10L)); @@ -213,7 +214,7 @@ void subscribeVerifySequence() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder() .setLimit(7L) .setConsensusStartTime(Timestamp.newBuilder().setSeconds(0).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); Flux generator = domainBuilder.topicMessages(4, Instant.now().plusSeconds(10L)); @@ -257,7 +258,7 @@ void fragmentedMessagesGroupAcrossHistoricAndIncoming() { ConsensusTopicQuery query = ConsensusTopicQuery.newBuilder() .setConsensusStartTime(Timestamp.newBuilder().setSeconds(0).build()) - .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(0).build()) + .setTopicID(TopicID.newBuilder().setRealmNum(0).setTopicNum(100).build()) .build(); grpcConsensusService.subscribeTopic(Mono.just(query)) diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/NetworkControllerTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/NetworkControllerTest.java new file mode 100644 index 00000000000..8f2e2abc1ab --- /dev/null +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/controller/NetworkControllerTest.java @@ -0,0 +1,215 @@ +package com.hedera.mirror.grpc.controller; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.ByteString; +import com.hederahashgraph.api.proto.java.FileID; +import com.hederahashgraph.api.proto.java.NodeAddress; +import com.hederahashgraph.api.proto.java.ServiceEndpoint; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import java.net.InetAddress; +import java.time.Duration; +import javax.annotation.Resource; +import lombok.extern.log4j.Log4j2; +import net.devh.boot.grpc.client.inject.GrpcClient; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import com.hedera.mirror.api.proto.AddressBookQuery; +import com.hedera.mirror.api.proto.ReactorNetworkServiceGrpc; +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.addressbook.AddressBook; +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.grpc.GrpcIntegrationTest; +import com.hedera.mirror.grpc.util.ProtoUtil; + +@Log4j2 +class NetworkControllerTest extends GrpcIntegrationTest { + + private static final long CONSENSUS_TIMESTAMP = 1L; + + @GrpcClient("local") + private ReactorNetworkServiceGrpc.ReactorNetworkServiceStub reactiveService; + + @Resource + private DomainBuilder domainBuilder; + + @Test + void missingFileId() { + AddressBookQuery query = AddressBookQuery.newBuilder().build(); + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .expectErrorSatisfies(t -> assertException(t, Status.Code.INVALID_ARGUMENT, "fileId: must not be null")) + .verify(Duration.ofSeconds(1L)); + } + + @Test + void invalidFileId() { + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().setFileNum(-1).build()) + .build(); + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .expectErrorSatisfies(t -> assertException(t, Status.Code.INVALID_ARGUMENT, "Invalid entity ID")) + .verify(Duration.ofSeconds(1L)); + } + + @Test + void invalidLimit() { + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().build()) + .setLimit(-1) + .build(); + + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .expectErrorSatisfies(t -> assertException(t, Status.Code.INVALID_ARGUMENT, "limit: must be greater " + + "than or equal to 0")) + .verify(Duration.ofSeconds(1L)); + } + + @Test + void notFound() { + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().setFileNum(102L).build()) + .build(); + + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .expectErrorSatisfies(t -> assertException(t, Status.Code.NOT_FOUND, "does not exist")) + .verify(Duration.ofSeconds(1L)); + } + + @Test + void noLimit() { + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + AddressBookEntry addressBookEntry2 = addressBookEntry(); + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().setFileNum(addressBook.getFileId().getEntityNum()).build()) + .build(); + + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .thenAwait(Duration.ofMillis(50)) + .consumeNextWith(n -> assertEntry(addressBookEntry1, n)) + .consumeNextWith(n -> assertEntry(addressBookEntry2, n)) + .expectComplete() + .verify(Duration.ofSeconds(1L)); + } + + @Test + void limitReached() { + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + addressBookEntry(); + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().setFileNum(addressBook.getFileId().getEntityNum()).build()) + .setLimit(1) + .build(); + + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .thenAwait(Duration.ofMillis(50)) + .consumeNextWith(n -> assertEntry(addressBookEntry1, n)) + .expectComplete() + .verify(Duration.ofSeconds(1L)); + } + + @Test + void nullFields() { + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry = domainBuilder.addressBookEntry() + .customize(a -> a.consensusTimestamp(CONSENSUS_TIMESTAMP) + .description(null) + .memo(null) + .nodeCertHash(null) + .publicKey(null) + .stake(null)) + .persist(); + AddressBookQuery query = AddressBookQuery.newBuilder() + .setFileId(FileID.newBuilder().setFileNum(addressBook.getFileId().getEntityNum()).build()) + .build(); + + reactiveService.getNodes(Mono.just(query)) + .as(StepVerifier::create) + .thenAwait(Duration.ofMillis(50)) + .consumeNextWith(n -> assertThat(n).isNotNull() + .returns("", NodeAddress::getDescription) + .returns(ByteString.EMPTY, NodeAddress::getMemo) + .returns(addressBookEntry.getNodeAccountId(), t -> EntityId.of(n.getNodeAccountId())) + .returns(ByteString.EMPTY, NodeAddress::getNodeCertHash) + .returns(addressBookEntry.getNodeId(), NodeAddress::getNodeId) + .returns("", NodeAddress::getRSAPubKey) + .returns(0L, NodeAddress::getStake)) + .expectComplete() + .verify(Duration.ofSeconds(1L)); + } + + private AddressBook addressBook() { + return domainBuilder.addressBook().customize(a -> a.startConsensusTimestamp(CONSENSUS_TIMESTAMP)).persist(); + } + + private AddressBookEntry addressBookEntry() { + return domainBuilder.addressBookEntry(1).customize(a -> a.consensusTimestamp(CONSENSUS_TIMESTAMP)).persist(); + } + + @SuppressWarnings("deprecation") + private void assertEntry(AddressBookEntry addressBookEntry, NodeAddress nodeAddress) { + assertThat(nodeAddress) + .isNotNull() + .returns(addressBookEntry.getDescription(), NodeAddress::getDescription) + .returns(ByteString.copyFromUtf8(addressBookEntry.getMemo()), NodeAddress::getMemo) + .returns(addressBookEntry.getNodeAccountId(), n -> EntityId.of(n.getNodeAccountId())) + .returns(ProtoUtil.toByteString(addressBookEntry.getNodeCertHash()), NodeAddress::getNodeCertHash) + .returns(addressBookEntry.getNodeId(), NodeAddress::getNodeId) + .returns(addressBookEntry.getPublicKey(), NodeAddress::getRSAPubKey) + .returns(addressBookEntry.getStake(), NodeAddress::getStake); + + var serviceEndpoint = addressBookEntry.getServiceEndpoints().iterator().next(); + ByteString ipAddress = null; + try { + ipAddress = ProtoUtil.toByteString(InetAddress.getByName(serviceEndpoint.getIpAddressV4()).getAddress()); + } catch (Exception e) { + // Ignore + } + + assertThat(nodeAddress.getServiceEndpointList()) + .hasSize(1) + .first() + .returns(ipAddress, ServiceEndpoint::getIpAddressV4) + .returns(serviceEndpoint.getPort(), ServiceEndpoint::getPort); + } + + private void assertException(Throwable t, Status.Code status, String message) { + assertThat(t).isNotNull() + .isInstanceOf(StatusRuntimeException.class) + .hasMessageContaining(message); + + StatusRuntimeException statusRuntimeException = (StatusRuntimeException) t; + assertThat(statusRuntimeException.getStatus().getCode()).isEqualTo(status); + } +} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverterTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverterTest.java deleted file mode 100644 index 4a5c6e8cf55..00000000000 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/converter/EncodedIdToEntityConverterTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.hedera.mirror.grpc.converter; - -/*- - * ‌ - * Hedera Mirror Node - * ​ - * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC - * ​ - * 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. - * ‍ - */ - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import com.hedera.mirror.common.domain.entity.EntityType; -import com.hedera.mirror.grpc.domain.Entity; - -class EncodedIdToEntityConverterTest { - - @DisplayName("Convert Instant to Long") - @ParameterizedTest(name = "with input {0} and output {1}") - @CsvSource({ - "2814792716779530, 10, 10, 10", - "9223372036854775807, 32767, 65535, 4294967295", - "0, 0, 0, 0", - "1001, 0, 0, 1001" - }) - void convert(Long encodedInput, Long shardOutput, Long realmOutput, Long numOutput) { - Entity result = EncodedIdToEntityConverter.INSTANCE.convert(encodedInput); - Entity expected = Entity.builder() - .id(encodedInput) - .shard(shardOutput) - .realm(realmOutput) - .num(numOutput) - .type(EntityType.ACCOUNT) - .build(); - assertEquals(expected, result); - } - - @Test - void convertNull() { - Entity result = EncodedIdToEntityConverter.INSTANCE.convert(null); - assertNull(result); - } -} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/domain/DomainBuilder.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/domain/DomainBuilder.java index c28262475b7..5685738bc9e 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/domain/DomainBuilder.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/domain/DomainBuilder.java @@ -44,7 +44,7 @@ import com.hedera.mirror.grpc.repository.TopicMessageRepository; @Log4j2 -@Named +@Named("grpcDomainBuilder") @RequiredArgsConstructor @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class DomainBuilder { @@ -70,7 +70,7 @@ public Mono entity(Consumer customizer) { .num(0L) .realm(0L) .shard(0L) - .id(0L) + .id(100L) .timestampRange(Range.atLeast(0L)) .type(EntityType.TOPIC); @@ -106,7 +106,7 @@ public Mono topicMessage(Consumer publisher) { void noMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -98,6 +106,7 @@ void noMessages() { void lessThanPageSize() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -117,6 +126,7 @@ void equalPageSize() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -138,6 +148,7 @@ void greaterThanPageSize() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -156,6 +167,7 @@ void greaterThanPageSize() { void startTimeBefore() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -173,6 +185,7 @@ void startTimeEquals() { Mono topicMessage = domainBuilder.topicMessage(t -> t.consensusTimestamp(future)); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(future) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -190,6 +203,7 @@ void startTimeAfter() { Mono topicMessage = domainBuilder.topicMessage(t -> t.consensusTimestamp(future.minusNanos(1))); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(future) + .topicId(topicId) .build(); topicListener.listen(filter) @@ -211,7 +225,7 @@ void topicId() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(1) + .topicId(EntityId.of(1L, EntityType.TOPIC)) .build(); topicListener.listen(filter) @@ -242,11 +256,11 @@ void multipleSubscribers() { TopicMessageFilter filter1 = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(1) + .topicId(EntityId.of(1L, EntityType.TOPIC)) .build(); TopicMessageFilter filter2 = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(2) + .topicId(EntityId.of(2L, EntityType.TOPIC)) .build(); StepVerifier stepVerifier1 = topicListener diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/listener/NotifyingTopicListenerTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/listener/NotifyingTopicListenerTest.java index 0c5397b420b..24d1dead308 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/listener/NotifyingTopicListenerTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/listener/NotifyingTopicListenerTest.java @@ -23,6 +23,10 @@ import java.time.Duration; import java.time.Instant; import javax.annotation.Resource; + +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; + import org.junit.jupiter.api.Test; import org.springframework.jdbc.core.JdbcTemplate; import reactor.core.publisher.Flux; @@ -75,7 +79,7 @@ void json() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(1001) + .topicId(EntityId.of(1001L, EntityType.TOPIC)) .build(); topicListener.listen(filter) diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepositoryTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepositoryTest.java new file mode 100644 index 00000000000..3ede2e88895 --- /dev/null +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookEntryRepositoryTest.java @@ -0,0 +1,76 @@ +package com.hedera.mirror.grpc.repository; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Resource; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.grpc.GrpcIntegrationTest; + +class AddressBookEntryRepositoryTest extends GrpcIntegrationTest { + + @Resource + private AddressBookEntryRepository addressBookEntryRepository; + + @Autowired + private DomainBuilder domainBuilder; + + @Test + void findByConsensusTimestampAndNodeId() { + long consensusTimestamp = 1L; + int limit = 2; + AddressBookEntry addressBookEntry1 = addressBookEntry(consensusTimestamp, 0L); + AddressBookEntry addressBookEntry2 = addressBookEntry(consensusTimestamp, 1L); + AddressBookEntry addressBookEntry3 = addressBookEntry(consensusTimestamp, 2L); + addressBookEntry(consensusTimestamp + 1, 0L); + + assertThat(addressBookEntryRepository.findByConsensusTimestampAndNodeId(consensusTimestamp, 0L, limit)) + .as("First page has a length equal to limit") + .hasSize(limit) + .containsExactly(addressBookEntry1, addressBookEntry2); + + assertThat(addressBookEntryRepository.findByConsensusTimestampAndNodeId(consensusTimestamp, limit, limit)) + .as("Second page has less than limit") + .containsExactly(addressBookEntry3); + } + + @Test + void serviceEndpoints() { + AddressBookEntry addressBookEntry = domainBuilder.addressBookEntry(3).persist(); + assertThat(addressBookEntryRepository.findById(addressBookEntry.getId())) + .get() + .extracting(AddressBookEntry::getServiceEndpoints) + .asInstanceOf(InstanceOfAssertFactories.COLLECTION) + .containsExactlyInAnyOrderElementsOf(addressBookEntry.getServiceEndpoints()); + } + + private AddressBookEntry addressBookEntry(long consensusTimestamp, long nodeId) { + return domainBuilder.addressBookEntry() + .customize(e -> e.consensusTimestamp(consensusTimestamp).nodeId(nodeId)) + .persist(); + } +} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookRepositoryTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookRepositoryTest.java new file mode 100644 index 00000000000..5765aef02ba --- /dev/null +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/AddressBookRepositoryTest.java @@ -0,0 +1,78 @@ +package com.hedera.mirror.grpc.repository; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.transaction.annotation.Transactional; + +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.addressbook.AddressBook; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; +import com.hedera.mirror.grpc.GrpcIntegrationTest; + +class AddressBookRepositoryTest extends GrpcIntegrationTest { + + @Resource + private AddressBookRepository addressBookRepository; + + @Resource + private DomainBuilder domainBuilder; + + @Test + void findLatestTimestamp() { + EntityId fileId = EntityId.of(101L, EntityType.FILE); + assertThat(addressBookRepository.findLatestTimestamp(fileId.getId())).isEmpty(); + + domainBuilder.addressBook().customize(a -> a.fileId(EntityId.of(999L, EntityType.FILE))).persist(); + assertThat(addressBookRepository.findLatestTimestamp(fileId.getId())).isEmpty(); + + AddressBook addressBook2 = domainBuilder.addressBook().customize(a -> a.fileId(fileId)).persist(); + assertThat(addressBookRepository.findLatestTimestamp(fileId.getId())).get() + .isEqualTo(addressBook2.getStartConsensusTimestamp()); + + AddressBook addressBook3 = domainBuilder.addressBook().customize(a -> a.fileId(fileId)).persist(); + assertThat(addressBookRepository.findLatestTimestamp(fileId.getId())).get() + .isEqualTo(addressBook3.getStartConsensusTimestamp()); + } + + @Test + @Transactional + void cascade() { + AddressBook addressBook = domainBuilder.addressBook().persist(); + assertThat(addressBookRepository.findById(addressBook.getStartConsensusTimestamp())) + .get() + .extracting(AddressBook::getEntries) + .isNull(); + + domainBuilder.addressBookEntry(1) + .customize(a -> a.consensusTimestamp(addressBook.getStartConsensusTimestamp())) + .persist(); + assertThat(addressBookRepository.findById(addressBook.getStartConsensusTimestamp())) + .get() + .extracting(AddressBook::getEntries) + .as("Ensure entries aren't eagerly loaded") + .isNull(); + } +} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryTest.java index f2e0effbaba..aee00d9bac1 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/repository/TopicMessageRepositoryTest.java @@ -24,7 +24,12 @@ import java.time.Instant; import javax.annotation.Resource; + +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; + import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -38,13 +43,14 @@ class TopicMessageRepositoryTest extends GrpcIntegrationTest { @Resource private TopicMessageRepository topicMessageRepository; - @Resource + @Autowired private DomainBuilder domainBuilder; @Test void findByFilterEmpty() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(EntityId.of(100L, EntityType.TOPIC)) .build(); assertThat(topicMessageRepository.findByFilter(filter)).isEmpty(); @@ -58,6 +64,7 @@ void findByFilterNoMatch() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.now().plusSeconds(10)) + .topicId(EntityId.of(topicMessage1.getTopicId(), EntityType.TOPIC)) .build(); assertThat(topicMessageRepository.findByFilter(filter)).isEmpty(); @@ -70,7 +77,7 @@ void findByFilterWithTopicId() { TopicMessage topicMessage3 = domainBuilder.topicMessage(t -> t.topicId(3)).block(); TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(2) + .topicId(EntityId.of(2L, EntityType.TOPIC)) .startTime(topicMessage1.getConsensusTimestampInstant()) .build(); @@ -85,6 +92,7 @@ void findByFilterWithStartTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(topicMessage2.getConsensusTimestampInstant()) + .topicId(EntityId.of(topicMessage1.getTopicId(), EntityType.TOPIC)) .build(); assertThat(topicMessageRepository.findByFilter(filter)).containsExactly(topicMessage2, topicMessage3); @@ -99,6 +107,7 @@ void findByFilterWithEndTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(topicMessage1.getConsensusTimestampInstant()) .endTime(topicMessage3.getConsensusTimestampInstant()) + .topicId(EntityId.of(topicMessage1.getTopicId(), EntityType.TOPIC)) .build(); assertThat(topicMessageRepository.findByFilter(filter)).containsExactly(topicMessage1, topicMessage2); @@ -113,6 +122,7 @@ void findByFilterWithLimit() { TopicMessageFilter filter = TopicMessageFilter.builder() .limit(1) .startTime(topicMessage1.getConsensusTimestampInstant()) + .topicId(EntityId.of(topicMessage1.getTopicId(), EntityType.TOPIC)) .build(); assertThat(topicMessageRepository.findByFilter(filter)).containsExactly(topicMessage1); diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/retriever/PollingTopicMessageRetrieverTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/retriever/PollingTopicMessageRetrieverTest.java index a291682a2d0..23ebe2b01ad 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/retriever/PollingTopicMessageRetrieverTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/retriever/PollingTopicMessageRetrieverTest.java @@ -25,11 +25,16 @@ import java.util.stream.Collectors; import java.util.stream.LongStream; import javax.annotation.Resource; + +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; @@ -40,7 +45,9 @@ class PollingTopicMessageRetrieverTest extends GrpcIntegrationTest { - @Resource + private static final EntityId TOPIC_ID = EntityId.of(100L, EntityType.TOPIC); + + @Autowired private DomainBuilder domainBuilder; @Resource @@ -75,6 +82,7 @@ void notEnabled(boolean throttled) { domainBuilder.topicMessage().block(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttled) @@ -92,6 +100,7 @@ void notEnabled(boolean throttled) { void noMessages(boolean throttled) { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttled) @@ -108,6 +117,7 @@ void lessThanPageSize(boolean throttle) { domainBuilder.topicMessage().block(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -128,6 +138,7 @@ void equalPageSize(boolean throttle) { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -150,6 +161,7 @@ void limitEqualPageSize(boolean throttle) { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .limit(2L) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -173,6 +185,7 @@ void greaterThanPageSize(boolean throttle) { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -191,6 +204,7 @@ void startTimeBefore(boolean throttle) { domainBuilder.topicMessages(10, Instant.now()).blockLast(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -208,6 +222,7 @@ void startTimeEquals(boolean throttle) { domainBuilder.topicMessage(t -> t.consensusTimestamp(now)).block(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(now) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -225,6 +240,7 @@ void startTimeAfter(boolean throttle) { domainBuilder.topicMessage(t -> t.consensusTimestamp(now.minusNanos(1))).block(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(now) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -243,7 +259,7 @@ void topicId(boolean throttle) { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(1) + .topicId(EntityId.of(1L, EntityType.TOPIC)) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -265,6 +281,7 @@ void timeout(boolean throttle) { domainBuilder.topicMessages(10, Instant.now()).blockLast(); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); pollingTopicMessageRetriever.retrieve(filter, throttle) @@ -287,6 +304,7 @@ void unthrottledShouldKeepPolling() { Flux secondBatch = domainBuilder.topicMessages(5, now.plusNanos(5)); TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(TOPIC_ID) .build(); // in unthrottled mode, the retriever should query the db for up to MaxPolls + 1 times when no limit is set, diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/NetworkServiceTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/NetworkServiceTest.java new file mode 100644 index 00000000000..7a623274039 --- /dev/null +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/NetworkServiceTest.java @@ -0,0 +1,205 @@ +package com.hedera.mirror.grpc.service; + +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +import static com.hedera.mirror.grpc.service.NetworkServiceImpl.INVALID_FILE_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.List; +import javax.annotation.Resource; +import javax.validation.ConstraintViolationException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reactor.test.StepVerifier; + +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.addressbook.AddressBook; +import com.hedera.mirror.common.domain.addressbook.AddressBookEntry; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; +import com.hedera.mirror.grpc.GrpcIntegrationTest; +import com.hedera.mirror.grpc.domain.AddressBookFilter; +import com.hedera.mirror.grpc.exception.EntityNotFoundException; +import com.hedera.mirror.grpc.repository.AddressBookEntryRepository; + +class NetworkServiceTest extends GrpcIntegrationTest { + + private static final long CONSENSUS_TIMESTAMP = 1L; + + @Resource + private AddressBookEntryRepository addressBookEntryRepository; + + @Resource + private AddressBookProperties addressBookProperties; + + @Autowired + private DomainBuilder domainBuilder; + + @Resource + private NetworkService networkService; + + private int pageSize; + + @BeforeEach + void setup() { + pageSize = addressBookProperties.getPageSize(); + } + + @AfterEach + void cleanup() { + addressBookProperties.setPageSize(pageSize); + } + + @Test + void invalidFilter() { + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(null) + .limit(-1) + .build(); + + assertThatThrownBy(() -> networkService.getNodes(filter)) + .isInstanceOf(ConstraintViolationException.class) + .hasMessageContaining("limit: must be greater than or equal to 0") + .hasMessageContaining("fileId: must not be null"); + } + + @Test + void addressBookNotFound() { + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(EntityId.of(102L, EntityType.FILE)) + .build(); + + assertThatThrownBy(() -> networkService.getNodes(filter)).isInstanceOf(EntityNotFoundException.class) + .hasMessage("File 0.0.102 does not exist"); + } + + @Test + void invalidAddressBookFile() { + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(EntityId.of(999L, EntityType.FILE)) + .build(); + + assertThatThrownBy(() -> networkService.getNodes(filter)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(INVALID_FILE_ID); + } + + @Test + void noNodes() { + AddressBook addressBook = addressBook(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .build(); + + networkService.getNodes(filter) + .as(StepVerifier::create) + .thenAwait(Duration.ofMillis(100)) + .expectNextCount(0L) + .expectComplete() + .verify(Duration.ofMillis(500)); + } + + @Test + void lessThanPageSize() { + addressBookProperties.setPageSize(2); + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry = addressBookEntry(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .build(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry); + } + + @Test + void equalToPageSize() { + addressBookProperties.setPageSize(2); + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + AddressBookEntry addressBookEntry2 = addressBookEntry(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .build(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry1, addressBookEntry2); + } + + @Test + void moreThanPageSize() { + addressBookProperties.setPageSize(2); + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + AddressBookEntry addressBookEntry2 = addressBookEntry(); + AddressBookEntry addressBookEntry3 = addressBookEntry(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .build(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry1, addressBookEntry2, addressBookEntry3); + } + + @Test + void limitReached() { + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + addressBookEntry(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .limit(1) + .build(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry1); + } + + @Test + void cached() { + addressBookProperties.setPageSize(2); + AddressBook addressBook = addressBook(); + AddressBookEntry addressBookEntry1 = addressBookEntry(); + AddressBookEntry addressBookEntry2 = addressBookEntry(); + AddressBookEntry addressBookEntry3 = addressBookEntry(); + AddressBookFilter filter = AddressBookFilter.builder() + .fileId(addressBook.getFileId()) + .build(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry1, addressBookEntry2, addressBookEntry3); + + addressBookEntryRepository.deleteAll(); + + assertThat(getNodes(filter)).containsExactly(addressBookEntry1, addressBookEntry2, addressBookEntry3); + } + + private List getNodes(AddressBookFilter filter) { + return networkService.getNodes(filter).collectList().block(Duration.ofMillis(1000L)); + } + + private AddressBook addressBook() { + return domainBuilder.addressBook().customize(a -> a.startConsensusTimestamp(CONSENSUS_TIMESTAMP)).persist(); + } + + private AddressBookEntry addressBookEntry() { + return domainBuilder.addressBookEntry().customize(a -> a.consensusTimestamp(CONSENSUS_TIMESTAMP)).persist(); + } +} diff --git a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/TopicMessageServiceTest.java b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/TopicMessageServiceTest.java index 0b9638ece6a..cf8daaff2dc 100644 --- a/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/TopicMessageServiceTest.java +++ b/hedera-mirror-grpc/src/test/java/com/hedera/mirror/grpc/service/TopicMessageServiceTest.java @@ -34,9 +34,11 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; +import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.grpc.GrpcIntegrationTest; import com.hedera.mirror.grpc.GrpcProperties; @@ -44,7 +46,7 @@ import com.hedera.mirror.grpc.domain.Entity; import com.hedera.mirror.grpc.domain.TopicMessage; import com.hedera.mirror.grpc.domain.TopicMessageFilter; -import com.hedera.mirror.grpc.exception.TopicNotFoundException; +import com.hedera.mirror.grpc.exception.EntityNotFoundException; import com.hedera.mirror.grpc.listener.ListenerProperties; import com.hedera.mirror.grpc.listener.TopicListener; import com.hedera.mirror.grpc.repository.EntityRepository; @@ -55,11 +57,12 @@ class TopicMessageServiceTest extends GrpcIntegrationTest { private final Instant now = Instant.now(); private final Instant future = now.plusSeconds(30L); + private final EntityId topicId = EntityId.of(100L, EntityType.TOPIC); @Resource private TopicMessageService topicMessageService; - @Resource + @Autowired private DomainBuilder domainBuilder; @Resource @@ -85,7 +88,7 @@ void after() { @Test void invalidFilter() { TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(-1) + .topicId(null) .startTime(null) .limit(-1) .build(); @@ -94,7 +97,7 @@ void invalidFilter() { .isInstanceOf(ConstraintViolationException.class) .hasMessageContaining("limit: must be greater than or equal to 0") .hasMessageContaining("startTime: must not be null") - .hasMessageContaining("topicId: must be greater than or equal to 0"); + .hasMessageContaining("topicId: must not be null"); } @Test @@ -102,6 +105,7 @@ void endTimeBeforeStartTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(now) .endTime(now.minus(1, ChronoUnit.DAYS)) + .topicId(topicId) .build(); assertThatThrownBy(() -> topicMessageService.subscribeTopic(filter)) @@ -114,6 +118,7 @@ void endTimeEqualsStartTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(now) .endTime(now) + .topicId(topicId) .build(); assertThatThrownBy(() -> topicMessageService.subscribeTopic(filter)) @@ -125,6 +130,7 @@ void endTimeEqualsStartTime() { void startTimeAfterNow() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.now().plusSeconds(10L)) + .topicId(topicId) .build(); assertThatThrownBy(() -> topicMessageService.subscribeTopic(filter)) @@ -135,12 +141,12 @@ void startTimeAfterNow() { @Test void topicNotFound() { TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(999) + .topicId(EntityId.of(999L, EntityType.TOPIC)) .build(); topicMessageService.subscribeTopic(filter) .as(StepVerifier::create) - .expectError(TopicNotFoundException.class) + .expectError(EntityNotFoundException.class) .verify(Duration.ofMillis(100)); } @@ -148,7 +154,7 @@ void topicNotFound() { void topicNotFoundWithCheckTopicExistsFalse() { grpcProperties.setCheckTopicExists(false); TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(999) + .topicId(EntityId.of(999L, EntityType.TOPIC)) .build(); topicMessageService.subscribeTopic(filter) @@ -163,9 +169,9 @@ void topicNotFoundWithCheckTopicExistsFalse() { @Test void invalidTopic() { - domainBuilder.entity(e -> e.type(EntityType.ACCOUNT).num(1L).id(1L)).block(); + domainBuilder.entity(e -> e.type(EntityType.ACCOUNT).num(100L).id(100L)).block(); TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(1) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -177,7 +183,7 @@ void invalidTopic() { @Test void noMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() - .topicId(1) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -193,6 +199,7 @@ void noMessagesWithPastEndTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(Instant.EPOCH.plusSeconds(1)) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -209,6 +216,7 @@ void noMessagesWithFutureEndTime() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(now) .endTime(endTime) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -227,6 +235,7 @@ void historicalMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -245,6 +254,7 @@ void historicalMessagesWithEndTimeAfter() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(topicMessage3.getConsensusTimestampInstant().plusNanos(1)) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -264,6 +274,7 @@ void historicalMessagesWithEndTimeEquals() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(topicMessage4.getConsensusTimestampInstant()) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -286,6 +297,7 @@ void historicalMessagesWithEndTimeExceedsPageSize() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(topicMessage4.getConsensusTimestampInstant()) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -308,6 +320,7 @@ void historicalMessagesWithLimit() { TopicMessageFilter filter = TopicMessageFilter.builder() .limit(2) .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -322,6 +335,7 @@ void historicalMessagesWithLimit() { void incomingMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -339,6 +353,7 @@ void incomingMessagesWithLimit() { TopicMessageFilter filter = TopicMessageFilter.builder() .limit(2) .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -359,6 +374,7 @@ void incomingMessagesWithEndTimeBefore() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(endTime) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -378,6 +394,7 @@ void incomingMessagesWithEndTimeEquals() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) .endTime(future) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -397,6 +414,7 @@ void bothMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() .limit(5) .startTime(Instant.EPOCH) + .topicId(topicId) .build(); topicMessageService.subscribeTopic(filter) @@ -426,7 +444,7 @@ void bothMessagesWithTopicId() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) - .topicId(1) + .topicId(EntityId.of(1L, EntityType.TOPIC)) .build(); topicMessageService.subscribeTopic(filter) @@ -449,12 +467,11 @@ void duplicateMessages() { TopicMessageFilter retrieverFilter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); - Mockito.when(entityRepository - .findById(retrieverFilter.getTopicId())) - .thenReturn(Optional - .of(Entity.builder().type(EntityType.TOPIC).build())); + Mockito.when(entityRepository.findById(retrieverFilter.getTopicId().getId())) + .thenReturn(Optional.of(Entity.builder().type(EntityType.TOPIC).build())); Mockito.when(topicMessageRetriever .retrieve(ArgumentMatchers.isA(TopicMessageFilter.class), ArgumentMatchers.eq(true))) @@ -485,14 +502,14 @@ void missingMessages() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); TopicMessage beforeMissing = topicMessage(1); TopicMessage afterMissing = topicMessage(4); - Mockito.when(entityRepository.findById(filter.getTopicId())) - .thenReturn(Optional - .of(Entity.builder().type(EntityType.TOPIC).build())); + Mockito.when(entityRepository.findById(filter.getTopicId().getId())) + .thenReturn(Optional.of(Entity.builder().type(EntityType.TOPIC).build())); Mockito.when(topicMessageRetriever.retrieve(ArgumentMatchers.eq(filter), ArgumentMatchers.eq(true))) .thenReturn(Flux.empty()); Mockito.when(topicListener.listen(filter)).thenReturn(Flux.just(beforeMissing, afterMissing)); @@ -518,6 +535,7 @@ void missingMessages() { void missingMessagesFromListenerAllRetrieved() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); missingMessagesFromListenerTest(filter, Flux.just(topicMessage(5), topicMessage(6), topicMessage(7))); @@ -534,6 +552,7 @@ void missingMessagesFromListenerAllRetrieved() { void missingMessagesFromListenerSomeRetrieved() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); missingMessagesFromListenerTest(filter, Flux.just(topicMessage(5), topicMessage(6))); @@ -550,6 +569,7 @@ void missingMessagesFromListenerSomeRetrieved() { void missingMessagesFromListenerNoneRetrieved() { TopicMessageFilter filter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); missingMessagesFromListenerTest(filter, Flux.empty()); @@ -572,6 +592,7 @@ void missingMessagesFromRetrieverAndListener() { TopicMessageFilter retrieverFilter = TopicMessageFilter.builder() .startTime(Instant.EPOCH) + .topicId(topicId) .build(); TopicMessage retrieved1 = topicMessage(1); @@ -583,10 +604,8 @@ void missingMessagesFromRetrieverAndListener() { TopicMessage afterMissing2 = topicMessage(9); TopicMessage afterMissing3 = topicMessage(10); - Mockito.when(entityRepository - .findById(retrieverFilter.getTopicId())) - .thenReturn(Optional - .of(Entity.builder().type(EntityType.TOPIC).build())); + Mockito.when(entityRepository.findById(retrieverFilter.getTopicId().getId())) + .thenReturn(Optional.of(Entity.builder().type(EntityType.TOPIC).build())); TopicMessageFilter listenerFilter = TopicMessageFilter.builder() .startTime(retrieved2.getConsensusTimestampInstant()) @@ -640,10 +659,8 @@ private void missingMessagesFromListenerTest(TopicMessageFilter filter, Flux${sql-formatter.version} test + + com.hedera + hedera-mirror-common + ${release.version} + test-jar + test + commons-beanutils commons-beanutils diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImpl.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImpl.java index c552bbad0e6..1e0d7bf3939 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImpl.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImpl.java @@ -371,12 +371,13 @@ private static Pair getNodeIds(NodeAddress nodeAddressProto) { private static AddressBookEntry getAddressBookEntry(NodeAddress nodeAddressProto, long consensusTimestamp, Pair nodeIds) { AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, nodeIds.getLeft())) + .consensusTimestamp(consensusTimestamp) .description(nodeAddressProto.getDescription()) + .nodeAccountId(nodeIds.getRight()) + .nodeId(nodeIds.getLeft()) .publicKey(nodeAddressProto.getRSAPubKey()) .serviceEndpoints(Set.of()) - .stake(nodeAddressProto.getStake()) - .nodeAccountId(nodeIds.getRight()); + .stake(nodeAddressProto.getStake()); if (nodeAddressProto.getNodeCertHash() != null) { builder.nodeCertHash(nodeAddressProto.getNodeCertHash().toByteArray()); @@ -391,55 +392,59 @@ private static AddressBookEntry getAddressBookEntry(NodeAddress nodeAddressProto private static Set getAddressBookServiceEndpoints(NodeAddress nodeAddressProto, long consensusTimestamp) throws UnknownHostException { - var nodeAccountId = EntityId.of(nodeAddressProto.getNodeAccountId()); + var nodeId = nodeAddressProto.getNodeId(); Set serviceEndpoints = new HashSet<>(); // create an AddressBookServiceEndpoint for deprecated port and IP if populated AddressBookServiceEndpoint deprecatedServiceEndpoint = getAddressBookServiceEndpoint( nodeAddressProto, consensusTimestamp, - nodeAccountId); + nodeId); if (deprecatedServiceEndpoint != null) { serviceEndpoints.add(deprecatedServiceEndpoint); } // create an AddressBookServiceEndpoint for every ServiceEndpoint found for (ServiceEndpoint serviceEndpoint : nodeAddressProto.getServiceEndpointList()) { - serviceEndpoints.add(getAddressBookServiceEndpoint(serviceEndpoint, consensusTimestamp, nodeAccountId)); + serviceEndpoints.add(getAddressBookServiceEndpoint(serviceEndpoint, consensusTimestamp, nodeId)); } return serviceEndpoints; } + @SuppressWarnings("deprecation") private static AddressBookServiceEndpoint getAddressBookServiceEndpoint(NodeAddress nodeAddressProto, long consensusTimestamp, - EntityId nodeAccountId) { + long nodeId) { String ip = nodeAddressProto.getIpAddress().toStringUtf8(); if (StringUtils.isBlank(ip)) { return null; } - return new AddressBookServiceEndpoint( - consensusTimestamp, - ip, - nodeAddressProto.getPortno(), - nodeAccountId); + AddressBookServiceEndpoint addressBookServiceEndpoint = new AddressBookServiceEndpoint(); + addressBookServiceEndpoint.setConsensusTimestamp(consensusTimestamp); + addressBookServiceEndpoint.setIpAddressV4(ip); + addressBookServiceEndpoint.setPort(nodeAddressProto.getPortno()); + addressBookServiceEndpoint.setNodeId(nodeId); + return addressBookServiceEndpoint; } private static AddressBookServiceEndpoint getAddressBookServiceEndpoint(ServiceEndpoint serviceEndpoint, long consensusTimestamp, - EntityId nodeAccountId) throws UnknownHostException { + long nodeId) throws UnknownHostException { var ipAddressByteString = serviceEndpoint.getIpAddressV4(); if (ipAddressByteString == null || ipAddressByteString.size() != 4) { throw new IllegalStateException(String .format("Invalid IpAddressV4: %s", ipAddressByteString)); } - return new AddressBookServiceEndpoint( - consensusTimestamp, - InetAddress.getByAddress(ipAddressByteString.toByteArray()).getHostAddress(), - serviceEndpoint.getPort(), - nodeAccountId); + var ip = InetAddress.getByAddress(ipAddressByteString.toByteArray()).getHostAddress(); + AddressBookServiceEndpoint addressBookServiceEndpoint = new AddressBookServiceEndpoint(); + addressBookServiceEndpoint.setConsensusTimestamp(consensusTimestamp); + addressBookServiceEndpoint.setIpAddressV4(ip); + addressBookServiceEndpoint.setPort(serviceEndpoint.getPort()); + addressBookServiceEndpoint.setNodeId(nodeId); + return addressBookServiceEndpoint; } /** diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListener.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListener.java index 9120cb36a2d..cc276e1208d 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListener.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListener.java @@ -59,7 +59,6 @@ public class RedisEntityListener implements BatchEntityListener { private static final String TOPIC_FORMAT = "topic.%d"; - private final MirrorProperties mirrorProperties; private final RedisProperties redisProperties; private final RedisOperations redisOperations; private final MeterRegistry meterRegistry; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/IntegrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/IntegrationTest.java index 0973a42119c..b0b08732b1a 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/IntegrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/IntegrationTest.java @@ -41,14 +41,14 @@ import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityIdEndec; import com.hedera.mirror.common.domain.entity.EntityType; -import com.hedera.mirror.importer.config.MeterRegistryConfiguration; +import com.hedera.mirror.importer.config.IntegrationTestConfiguration; import com.hedera.mirror.importer.config.MirrorDateRangePropertiesProcessor; // Same database is used for all tests, so clean it up before each test. @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/scripts/cleanup.sql") @Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, scripts = "classpath:db/scripts/cleanup.sql") @SpringBootTest -@Import(MeterRegistryConfiguration.class) +@Import(IntegrationTestConfiguration.class) public abstract class IntegrationTest { protected final Logger log = LogManager.getLogger(getClass()); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImplTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImplTest.java index 9b1d4ea9874..7159c03bab4 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImplTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/addressbook/AddressBookServiceImplTest.java @@ -74,7 +74,6 @@ class AddressBookServiceImplTest extends IntegrationTest { private static final NodeAddressBook FINAL = addressBook(15, 0); private static final int TEST_INITIAL_ADDRESS_BOOK_NODE_COUNT = 4; private static final String baseAccountId = "0.0."; - private static final String baseIp = "127.0.0."; private static final int basePort = 50211; private static byte[] initialAddressBookBytes; @TempDir @@ -550,7 +549,7 @@ void verifyAddressBookEntriesWithNodeIdAndPortNotSet() { assertThat(abe.getNodeAccountId()).isEqualTo(EntityId.of(nodeAddress.getNodeAccountId())); assertThat(abe.getNodeCertHash()).isEqualTo(nodeAddress.getNodeCertHash().toByteArray()); assertThat(abe.getPublicKey()).isEqualTo(nodeAddress.getRSAPubKey()); - assertThat(abe.getId().getNodeId()).isEqualTo(expectedNodeId); // both entries have null node id + assertThat(abe.getNodeId()).isEqualTo(expectedNodeId); // both entries have null node id assertAddressBookEndPoints(abe.getServiceEndpoints(), nodeAddress.getServiceEndpointList()); }); @@ -1002,7 +1001,7 @@ private void assertAddressBook(AddressBook actual, NodeAddressBook expected) { assertThat(abe.getNodeAccountId()).isEqualTo(EntityId.of(nodeAddress.getNodeAccountId())); assertThat(abe.getNodeCertHash()).isEqualTo(nodeAddress.getNodeCertHash().toByteArray()); assertThat(abe.getPublicKey()).isEqualTo(nodeAddress.getRSAPubKey()); - assertThat(abe.getId().getNodeId()).isEqualTo(nodeAddress.getNodeId()); + assertThat(abe.getNodeId()).isEqualTo(nodeAddress.getNodeId()); assertAddressBookEndPoints(abe.getServiceEndpoints(), nodeAddress.getServiceEndpointList()); }); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/MeterRegistryConfiguration.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/IntegrationTestConfiguration.java similarity index 69% rename from hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/MeterRegistryConfiguration.java rename to hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/IntegrationTestConfiguration.java index 70baedd58aa..91b991b5704 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/MeterRegistryConfiguration.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/config/IntegrationTestConfiguration.java @@ -22,14 +22,23 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import javax.persistence.EntityManager; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.transaction.support.TransactionOperations; + +import com.hedera.mirror.common.domain.DomainBuilder; @TestConfiguration -public class MeterRegistryConfiguration { +public class IntegrationTestConfiguration { @Bean - public MeterRegistry prometheusMeterRegistry() { + MeterRegistry prometheusMeterRegistry() { return new SimpleMeterRegistry(); } + + @Bean + DomainBuilder domainBuilder(EntityManager entityManager, TransactionOperations transactionOperations) { + return new DomainBuilder(entityManager, transactionOperations); + } } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainPersister.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainPersister.java deleted file mode 100644 index 9a5e1fa58af..00000000000 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/domain/DomainPersister.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.hedera.mirror.importer.domain; - -/*- - * ‌ - * Hedera Mirror Node - * ​ - * Copyright (C) 2019 - 2021 Hedera Hashgraph, LLC - * ​ - * 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. - * ‍ - */ - -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.data.repository.CrudRepository; - -@Log4j2 -@RequiredArgsConstructor -public class DomainPersister { - - private final CrudRepository crudRepository; - private final B builder; - private final Supplier supplier; - - public DomainPersister customize(Consumer customizer) { - customizer.accept(builder); - return this; - } - - public T get() { - return supplier.get(); - } - - public T persist() { - T t = get(); - - // The DomainBuilder can be used without an active ApplicationContext. If so, this method shouldn't be used. - if (crudRepository == null) { - throw new IllegalStateException("Unable to save without a repository"); - } - - log.trace("Inserting {}", t); - return crudRepository.save(t); - } -} diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/AbstractDownloaderTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/AbstractDownloaderTest.java index be3263ab7f9..d1c36624c56 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/AbstractDownloaderTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/AbstractDownloaderTest.java @@ -304,7 +304,7 @@ void oneThirdConsensus() throws Exception { AddressBook addressBookWith3Nodes = addressBook.toBuilder().entries(entries).nodeCount(entries.size()).build(); doReturn(addressBookWith3Nodes).when(addressBookService).getCurrent(); - String nodeAccountId = entries.get(0).getNodeAccountIdString(); + String nodeAccountId = entries.get(0).getNodeAccountId().toString(); log.info("Only copy node {}'s stream files and signature files for a 3-node network", nodeAccountId); fileCopier.filterDirectories("*" + nodeAccountId).copy(); expectLastStreamFile(Instant.EPOCH); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/AddAddressBookServiceEndpointsMigrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/AddAddressBookServiceEndpointsMigrationTest.java index 5f5bb95fc52..e296e00cd7d 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/AddAddressBookServiceEndpointsMigrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/AddAddressBookServiceEndpointsMigrationTest.java @@ -214,8 +214,9 @@ void verifyAddressBookEntryDuplicatesRemoved() throws IOException { int nodeIdCount = 3; AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, 0L)) + .consensusTimestamp(consensusTimestamp) .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(0L) .publicKey("rsa+public/key"); List nodeIds = List.of(0L, 1L, 2L); @@ -271,8 +272,9 @@ void verifyAddressBookEntryDuplicatesRemoved() throws IOException { void verifyInitialAddressBookNullEntriesUpdated() throws IOException { long consensusTimestamp = 1; AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, 0L)) + .consensusTimestamp(consensusTimestamp) .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(0L) .publicKey("rsa+public/key"); List nodeIds = List.of(0L, 1L, 2L, 3L); @@ -315,16 +317,20 @@ void verifyAddressBookWithValidIpAndInvalidPortMigration() throws IOException { insertAddressBook(AddressBookServiceImpl.ADDRESS_BOOK_102_ENTITY_ID, consensusTimestamp, nodeIds.size()); insertAddressBookEntry( builder.memo(baseAccountId + (nodeIds.get(0) + nodeAccountOffset)) - .id(new AddressBookEntry.Id(consensusTimestamp, nodeIds.get(0))) + .consensusTimestamp(consensusTimestamp) + .nodeId(nodeIds.get(0)) .build(), "127.0.0.1", null); insertAddressBookEntry(builder.memo(baseAccountId + (nodeIds.get(1) + nodeAccountOffset)) - .id(new AddressBookEntry.Id(consensusTimestamp, nodeIds.get(1))) + .consensusTimestamp(consensusTimestamp) + .nodeId(nodeIds.get(1)) .build(), "127.0.0.2", 0); insertAddressBookEntry(builder.memo(baseAccountId + (nodeIds.get(2) + nodeAccountOffset)) - .id(new AddressBookEntry.Id(consensusTimestamp, nodeIds.get(2))) + .consensusTimestamp(consensusTimestamp) + .nodeId(nodeIds.get(2)) .build(), "127.0.0.3", null); insertAddressBookEntry(builder.memo(baseAccountId + (nodeIds.get(3) + nodeAccountOffset)) - .id(new AddressBookEntry.Id(consensusTimestamp, nodeIds.get(3))) + .consensusTimestamp(consensusTimestamp) + .nodeId(nodeIds.get(3)) .build(), "127.0.0.4", 50211); runMigration(); @@ -352,7 +358,6 @@ void verifyAddressBookWithValidIpAndInvalidPortMigration() throws IOException { serviceListAssert.extracting(AddressBookServiceEndpoint::getId) .extracting(AddressBookServiceEndpoint.Id::getNodeId) - .extracting(EntityId::getId) .containsExactlyInAnyOrder(0L, 1L, 2L, 3L); serviceListAssert.extracting(AddressBookServiceEndpoint::getId) @@ -382,7 +387,8 @@ private List getAndSaveAddressBookEntry(boolean deprecatedIp, String accountId = baseAccountId + accountIdNum; List addressBookEntries = new ArrayList<>(); AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, nodeId)) + .consensusTimestamp(consensusTimestamp) + .nodeId(nodeId) .memo(accountId) .nodeCertHash("nodeCertHash".getBytes()) .nodeAccountId(EntityId.of(accountId, EntityType.ACCOUNT)) @@ -451,12 +457,12 @@ private void insertAddressBookEntry(AddressBookEntry addressBookEntry, String ip "node_cert_hash, node_id, port, public_key) values" + " (?, ?, ?, ?, ?, ?, ?, ?, ?)", addressBookEntryIdCounter++, - addressBookEntry.getId().getConsensusTimestamp(), + addressBookEntry.getConsensusTimestamp(), ip, addressBookEntry.getMemo(), nodeAccountId, addressBookEntry.getNodeCertHash(), - addressBookEntry.getId().getNodeId(), + addressBookEntry.getNodeId(), port, addressBookEntry.getPublicKey()); } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/MissingAddressBooksMigrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/MissingAddressBooksMigrationTest.java index c4afe016b46..27eb1bb388d 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/MissingAddressBooksMigrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/MissingAddressBooksMigrationTest.java @@ -57,7 +57,6 @@ class MissingAddressBooksMigrationTest extends IntegrationTest { - private static final NodeAddressBook UPDATED = addressBook(10, 0); private static final NodeAddressBook FINAL = addressBook(15, 0); @Resource @@ -72,9 +71,7 @@ class MissingAddressBooksMigrationTest extends IntegrationTest { @Resource private AddressBookServiceEndpointRepository addressBookServiceEndpointRepository; - @Resource - private EntityProperties entityProperties; - + @SuppressWarnings("deprecation") private static NodeAddressBook addressBook(int size, int endPointSize) { NodeAddressBook.Builder builder = NodeAddressBook.newBuilder(); for (int i = 0; i < size; ++i) { @@ -152,11 +149,12 @@ void verifyAddressBookMigrationWithNewFileDataAfterCurrentAddressBook() { }) void skipMigrationPreAddressBookService(int serviceEndpointCount, boolean result) { for (int j = 1; j <= serviceEndpointCount; ++j) { - addressBookServiceEndpointRepository.save(new AddressBookServiceEndpoint( - j, - "127.0.0.1", - 443, - EntityId.of(0, 0, 100, EntityType.ACCOUNT))); + AddressBookServiceEndpoint addressBookServiceEndpoint = new AddressBookServiceEndpoint(); + addressBookServiceEndpoint.setConsensusTimestamp(j); + addressBookServiceEndpoint.setIpAddressV4("127.0.0.1"); + addressBookServiceEndpoint.setPort(443); + addressBookServiceEndpoint.setNodeId(100L); + addressBookServiceEndpointRepository.save(addressBookServiceEndpoint); } assertThat(missingAddressBooksMigration.skipMigration(getConfiguration())).isEqualTo(result); } @@ -165,12 +163,14 @@ private AddressBook addressBook(Consumer address long consensusTimestamp, int nodeCount) { long startConsensusTimestamp = consensusTimestamp + 1; List addressBookEntryList = new ArrayList<>(); - for (int i = 0; i < nodeCount; i++) { - long nodeId = 3 + i; + for (long i = 0; i < nodeCount; i++) { + long nodeId = i; + long nodeAccountId = 3 + nodeId; addressBookEntryList - .add(addressBookEntry(a -> a.id(new AddressBookEntry.Id(startConsensusTimestamp, nodeId)) - .memo("0.0." + nodeId) - .nodeAccountId(EntityId.of("0.0." + nodeId, EntityType.ACCOUNT)))); + .add(addressBookEntry(a -> a.consensusTimestamp(startConsensusTimestamp) + .nodeId(nodeId) + .memo("0.0." + nodeAccountId) + .nodeAccountId(EntityId.of("0.0." + nodeAccountId, EntityType.ACCOUNT)))); } AddressBook.AddressBookBuilder builder = AddressBook.builder() @@ -189,12 +189,13 @@ private AddressBook addressBook(Consumer address private AddressBookEntry addressBookEntry(Consumer nodeAddressCustomizer) { AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(Instant.now().getEpochSecond(), 5L)) + .consensusTimestamp(Instant.now().getEpochSecond()) .description("address book entry") .publicKey("rsa+public/key") .memo("0.0.3") .nodeAccountId(EntityId.of("0.0.5", EntityType.ACCOUNT)) .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(5L) .stake(5L); if (nodeAddressCustomizer != null) { @@ -222,7 +223,7 @@ private void assertAddressBook(AddressBook actual, NodeAddressBook expected) { assertThat(abe.getNodeAccountId()).isEqualTo(EntityId.of(nodeAddress.getNodeAccountId())); assertThat(abe.getNodeCertHash()).isEqualTo(nodeAddress.getNodeCertHash().toByteArray()); assertThat(abe.getPublicKey()).isEqualTo(nodeAddress.getRSAPubKey()); - assertThat(abe.getId().getNodeId()).isEqualTo(nodeAddress.getNodeId()); + assertThat(abe.getNodeId()).isEqualTo(nodeAddress.getNodeId()); }); } } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/TransferTransactionPayerMigrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/TransferTransactionPayerMigrationTest.java index a5ba49e1914..2d1006a9946 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/TransferTransactionPayerMigrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/migration/TransferTransactionPayerMigrationTest.java @@ -46,21 +46,20 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.test.context.TestPropertySource; +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.entity.Entity; import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityIdEndec; import com.hedera.mirror.common.domain.entity.EntityType; -import com.hedera.mirror.importer.EnabledIfV1; -import com.hedera.mirror.importer.IntegrationTest; -import com.hedera.mirror.common.domain.transaction.AssessedCustomFee; -import com.hedera.mirror.common.domain.transaction.CryptoTransfer; -import com.hedera.mirror.importer.domain.DomainBuilder; -import com.hedera.mirror.common.domain.entity.Entity; import com.hedera.mirror.common.domain.token.NftTransfer; import com.hedera.mirror.common.domain.token.NftTransferId; -import com.hedera.mirror.common.domain.transaction.NonFeeTransfer; import com.hedera.mirror.common.domain.token.TokenTransfer; -import com.hedera.mirror.common.domain.transaction.Transaction; +import com.hedera.mirror.common.domain.transaction.AssessedCustomFee; +import com.hedera.mirror.common.domain.transaction.CryptoTransfer; +import com.hedera.mirror.common.domain.transaction.NonFeeTransfer; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hedera.mirror.importer.EnabledIfV1; +import com.hedera.mirror.importer.IntegrationTest; import com.hedera.mirror.importer.repository.CryptoTransferRepository; import com.hedera.mirror.importer.repository.EntityRepository; import com.hedera.mirror.importer.repository.NftTransferRepository; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchInserterTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchInserterTest.java index bbdcfd36d0e..a5d3b66cf3f 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchInserterTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchInserterTest.java @@ -45,6 +45,7 @@ import org.postgresql.copy.CopyManager; import org.springframework.jdbc.core.JdbcTemplate; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.common.domain.token.TokenTransfer; @@ -54,7 +55,6 @@ import com.hedera.mirror.common.domain.transaction.Transaction; import com.hedera.mirror.importer.IntegrationTest; import com.hedera.mirror.importer.domain.AssessedCustomFeeWrapper; -import com.hedera.mirror.importer.domain.DomainBuilder; import com.hedera.mirror.importer.exception.ParserException; import com.hedera.mirror.importer.parser.CommonParserProperties; import com.hedera.mirror.importer.repository.CryptoTransferRepository; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchUpserterTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchUpserterTest.java index 64ade435257..a38dd0626d0 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchUpserterTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/BatchUpserterTest.java @@ -39,13 +39,12 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.transaction.support.TransactionOperations; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.contract.Contract; import com.hedera.mirror.common.domain.entity.Entity; import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.common.domain.schedule.Schedule; -import com.hedera.mirror.importer.IntegrationTest; -import com.hedera.mirror.importer.domain.DomainBuilder; import com.hedera.mirror.common.domain.token.Nft; import com.hedera.mirror.common.domain.token.NftId; import com.hedera.mirror.common.domain.token.NftTransfer; @@ -59,6 +58,7 @@ import com.hedera.mirror.common.domain.token.TokenSupplyTypeEnum; import com.hedera.mirror.common.domain.token.TokenTransfer; import com.hedera.mirror.common.domain.token.TokenTypeEnum; +import com.hedera.mirror.importer.IntegrationTest; import com.hedera.mirror.importer.repository.ContractRepository; import com.hedera.mirror.importer.repository.EntityRepository; import com.hedera.mirror.importer.repository.NftRepository; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/CompositeBatchPersisterTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/CompositeBatchPersisterTest.java index 521717ba98e..07b26df05f6 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/CompositeBatchPersisterTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/batch/CompositeBatchPersisterTest.java @@ -26,18 +26,15 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; - -import com.hedera.mirror.common.domain.contract.ContractResult; - -import com.hedera.mirror.importer.repository.ContractResultRepository; - import org.junit.jupiter.api.Test; import org.springframework.transaction.annotation.Transactional; -import com.hedera.mirror.importer.IntegrationTest; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.contract.Contract; -import com.hedera.mirror.importer.domain.DomainBuilder; +import com.hedera.mirror.common.domain.contract.ContractResult; +import com.hedera.mirror.importer.IntegrationTest; import com.hedera.mirror.importer.repository.ContractRepository; +import com.hedera.mirror.importer.repository.ContractResultRepository; class CompositeBatchPersisterTest extends IntegrationTest { diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index f287b1906a8..77c5cc18fce 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -20,8 +20,8 @@ * ‍ */ -import static com.hedera.mirror.importer.domain.DomainBuilder.KEY_LENGTH_ECDSA; -import static com.hedera.mirror.importer.domain.DomainBuilder.KEY_LENGTH_ED25519; +import static com.hedera.mirror.common.domain.DomainBuilder.KEY_LENGTH_ECDSA; +import static com.hedera.mirror.common.domain.DomainBuilder.KEY_LENGTH_ED25519; import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import com.google.protobuf.ByteString; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/AbstractEntityRecordItemListenerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/AbstractEntityRecordItemListenerTest.java index 0621fbe686b..3b691e133f9 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/AbstractEntityRecordItemListenerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/AbstractEntityRecordItemListenerTest.java @@ -49,6 +49,7 @@ import org.springframework.transaction.support.TransactionTemplate; import com.hedera.mirror.common.domain.DigestAlgorithm; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.StreamType; import com.hedera.mirror.common.domain.contract.Contract; import com.hedera.mirror.common.domain.entity.AbstractEntity; @@ -61,7 +62,6 @@ import com.hedera.mirror.common.domain.transaction.Transaction; import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.IntegrationTest; -import com.hedera.mirror.importer.domain.DomainBuilder; import com.hedera.mirror.importer.domain.StreamFilename; import com.hedera.mirror.importer.parser.record.RecordStreamFileListener; import com.hedera.mirror.importer.repository.ContractRepository; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListenerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListenerTest.java index cc29797c7e3..ea109d3fcb4 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListenerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/redis/RedisEntityListenerTest.java @@ -41,10 +41,9 @@ import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; +import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.common.domain.topic.StreamMessage; -import com.hedera.mirror.importer.MirrorProperties; -import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.topic.TopicMessage; import com.hedera.mirror.importer.parser.record.entity.EntityBatchCleanupEvent; import com.hedera.mirror.importer.parser.record.entity.EntityBatchSaveEvent; @@ -66,8 +65,7 @@ class RedisEntityListenerTest { @BeforeEach void setup() { redisProperties = new RedisProperties(); - entityListener = new RedisEntityListener(new MirrorProperties(), redisProperties, redisOperations, - new SimpleMeterRegistry()); + entityListener = new RedisEntityListener(redisProperties, redisOperations, new SimpleMeterRegistry()); entityListener.init(); } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/sql/SqlEntityListenerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/sql/SqlEntityListenerTest.java index 1c2cb3454a0..85d21264dbf 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/sql/SqlEntityListenerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/sql/SqlEntityListenerTest.java @@ -43,6 +43,7 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.transaction.support.TransactionTemplate; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.contract.Contract; import com.hedera.mirror.common.domain.contract.ContractLog; import com.hedera.mirror.common.domain.contract.ContractResult; @@ -74,7 +75,6 @@ import com.hedera.mirror.common.domain.transaction.TransactionType; import com.hedera.mirror.importer.IntegrationTest; import com.hedera.mirror.importer.TestUtils; -import com.hedera.mirror.importer.domain.DomainBuilder; import com.hedera.mirror.importer.repository.ContractLogRepository; import com.hedera.mirror.importer.repository.ContractRepository; import com.hedera.mirror.importer.repository.ContractResultRepository; diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AbstractRepositoryTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AbstractRepositoryTest.java index 48b84a77ffb..5971160f09d 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AbstractRepositoryTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AbstractRepositoryTest.java @@ -23,8 +23,8 @@ import javax.annotation.Resource; import org.springframework.jdbc.core.JdbcOperations; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.importer.IntegrationTest; -import com.hedera.mirror.importer.domain.DomainBuilder; public abstract class AbstractRepositoryTest extends IntegrationTest { diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookEntryRepositoryTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookEntryRepositoryTest.java index a36676d1f75..e0a7040c258 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookEntryRepositoryTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookEntryRepositoryTest.java @@ -85,11 +85,12 @@ void verifyAddressBookToEntryMapping() { private AddressBookEntry addressBookEntry(Consumer nodeAddressCustomizer, long consensusTimestamp, long nodeAccountId) { String nodeAccountIdString = String.format("0.0.%s", nodeAccountId); AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, nodeAccountId - 3)) + .consensusTimestamp(consensusTimestamp) .publicKey("rsa+public/key") .memo(nodeAccountIdString) .nodeAccountId(EntityId.of(nodeAccountIdString, EntityType.ACCOUNT)) - .nodeCertHash("nodeCertHash".getBytes()); + .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(nodeAccountId - 3); if (nodeAddressCustomizer != null) { nodeAddressCustomizer.accept(builder); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookRepositoryTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookRepositoryTest.java index 2640944e2ca..5a3e73e03cd 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookRepositoryTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookRepositoryTest.java @@ -70,12 +70,13 @@ private AddressBook addressBook(Consumer address long startConsensusTimestamp = consensusTimestamp + 1; List addressBookEntryList = new ArrayList<>(); for (int i = 0; i < nodeCount; i++) { - long id = i; - long nodeId = 3 + i; + long nodeId = i; + long nodeAccountId = 3 + i; addressBookEntryList - .add(addressBookEntry(a -> a.id(new AddressBookEntry.Id(startConsensusTimestamp, nodeId)) - .memo("0.0." + nodeId) - .nodeAccountId(EntityId.of("0.0." + nodeId, EntityType.ACCOUNT)))); + .add(addressBookEntry(a -> a.consensusTimestamp(startConsensusTimestamp) + .memo("0.0." + nodeAccountId) + .nodeId(nodeId) + .nodeAccountId(EntityId.of("0.0." + nodeAccountId, EntityType.ACCOUNT)))); } AddressBook.AddressBookBuilder builder = AddressBook.builder() @@ -94,11 +95,12 @@ private AddressBook addressBook(Consumer address private AddressBookEntry addressBookEntry(Consumer nodeAddressCustomizer) { AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(Instant.now().getEpochSecond(), 5L)) - .publicKey("rsa+public/key") + .consensusTimestamp(Instant.now().getEpochSecond()) .memo("0.0.3") .nodeAccountId(EntityId.of("0.0.5", EntityType.ACCOUNT)) - .nodeCertHash("nodeCertHash".getBytes()); + .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(5L) + .publicKey("rsa+public/key"); if (nodeAddressCustomizer != null) { nodeAddressCustomizer.accept(builder); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookServiceEndpointRepositoryTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookServiceEndpointRepositoryTest.java index 4e3e54bf1fb..30ad8beacd2 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookServiceEndpointRepositoryTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/AddressBookServiceEndpointRepositoryTest.java @@ -52,31 +52,6 @@ class AddressBookServiceEndpointRepositoryTest extends AbstractRepositoryTest { @Resource protected AddressBookRepository addressBookRepository; - @Test - void save() { - long consensusTimestamp = 1L; - AddressBookServiceEndpoint addressBookServiceEndpoint = addressBookServiceEndpointRepository.save( - addressBookServiceEndpoint(consensusTimestamp, "127.0.0.1", 443, 3)); - assertThat(addressBookServiceEndpointRepository.findById(addressBookServiceEndpoint.getId())) - .get() - .isEqualTo(addressBookServiceEndpoint); - } - - @Test - void verifySequence() { - long consensusTimestamp = 1L; - addressBookServiceEndpointRepository.save(addressBookServiceEndpoint(consensusTimestamp, "127.0.0.1", 80, 3)); - addressBookServiceEndpointRepository.save(addressBookServiceEndpoint(consensusTimestamp, "127.0.0.2", 443, 3)); - addressBookServiceEndpointRepository.save(addressBookServiceEndpoint(consensusTimestamp, "127.0.0.3", 8000, 4)); - addressBookServiceEndpointRepository.save(addressBookServiceEndpoint(consensusTimestamp, "127.0.0.4", 8443, 4)); - assertThat(addressBookServiceEndpointRepository.findAll()) - .isNotNull() - .hasSize(4) - .extracting(AddressBookServiceEndpoint::getId) - .extracting(AddressBookServiceEndpoint.Id::getPort) - .containsSequence(80, 443, 8000, 8443); - } - @Test void verifyEntryToServiceEndpointMapping() throws UnknownHostException { long consensusTimestamp = 1L; @@ -114,24 +89,26 @@ void verifyAddressBookToServiceEndpointMapping() throws UnknownHostException { } private AddressBookServiceEndpoint addressBookServiceEndpoint(long consensusTimestamp, String ip, int port, - long nodeAccountId) { - String nodeAccountIdString = String.format("0.0.%s", nodeAccountId); - return new AddressBookServiceEndpoint( - consensusTimestamp, - ip, - port, - EntityId.of(nodeAccountIdString, EntityType.ACCOUNT)); + long nodeId) { + AddressBookServiceEndpoint addressBookServiceEndpoint = new AddressBookServiceEndpoint(); + addressBookServiceEndpoint.setConsensusTimestamp(consensusTimestamp); + addressBookServiceEndpoint.setIpAddressV4(ip); + addressBookServiceEndpoint.setPort(port); + addressBookServiceEndpoint.setNodeId(nodeId); + return addressBookServiceEndpoint; } private AddressBookEntry addressBookEntry(long consensusTimestamp, long nodeAccountId, List portNums) throws UnknownHostException { + long nodeId = nodeAccountId - 3; String nodeAccountIdString = String.format("0.0.%s", nodeAccountId); EntityId nodeAccountEntityId = EntityId.of(nodeAccountIdString, EntityType.ACCOUNT); AddressBookEntry.AddressBookEntryBuilder builder = AddressBookEntry.builder() - .id(new AddressBookEntry.Id(consensusTimestamp, nodeAccountId - 3)) - .publicKey("rsa+public/key") + .consensusTimestamp(consensusTimestamp) .memo(nodeAccountIdString) .nodeAccountId(nodeAccountEntityId) - .nodeCertHash("nodeCertHash".getBytes()); + .nodeCertHash("nodeCertHash".getBytes()) + .nodeId(nodeId) + .publicKey("rsa+public/key"); if (!CollectionUtils.isEmpty(portNums)) { Set serviceEndpoints = new HashSet<>(); @@ -140,7 +117,7 @@ private AddressBookEntry addressBookEntry(long consensusTimestamp, long nodeAcco consensusTimestamp, InetAddress.getByName("127.0.0." + i).getHostAddress(), portNums.get(i), - nodeAccountId)); + nodeId)); } builder.serviceEndpoints(serviceEndpoints); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/EntityRepositoryTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/EntityRepositoryTest.java index 8e0c2b6ae87..4f8a1e53124 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/EntityRepositoryTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/repository/EntityRepositoryTest.java @@ -31,8 +31,8 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import com.hedera.mirror.common.domain.DomainBuilder; import com.hedera.mirror.common.domain.entity.Entity; -import com.hedera.mirror.importer.domain.DomainBuilder; class EntityRepositoryTest extends AbstractRepositoryTest { diff --git a/hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto b/hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto new file mode 100644 index 00000000000..52c17d75092 --- /dev/null +++ b/hedera-mirror-protobuf/src/main/proto/com/hedera/mirror/api/proto/network_service.proto @@ -0,0 +1,43 @@ +/*- + * ‌ + * Hedera Mirror Node + * ​ + * Copyright (C) 2019-2021 Hedera Hashgraph, LLC + * ​ + * 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. + * ‍ + */ + +syntax = "proto3"; + +package com.hedera.mirror.api.proto; + +option java_multiple_files = true; // Required for the reactor-grpc generator to work correctly +option java_package = "com.hedera.mirror.api.proto"; + +import "basic_types.proto"; +import "timestamp.proto"; + +// Request object to query an address book for its list of nodes +message AddressBookQuery { + .proto.FileID file_id = 1; // The ID of the address book file on the network. Can be either 0.0.101 or 0.0.102. + int32 limit = 2; // The maximum number of node addresses to receive before stopping. If not set or set to zero it will return all node addresses in the database. +} + +// Provides cross network APIs like address book queries +service NetworkService { + // Query for an address book and return its nodes. The nodes are returned in ascending order by node ID. The + // response is not guaranteed to be a byte-for-byte equivalent to the NodeAddress in the Hedera file on + // the network since it is reconstructed from a normalized database table. + rpc getNodes (AddressBookQuery) returns (stream .proto.NodeAddress); +}