From ce8ef4328b371e022d2539f758d1120f3a2d949c Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 4 Nov 2024 14:39:26 +0200 Subject: [PATCH 01/33] Implement MirrorNodeState Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 106 ++++++++++++ .../web3/state/utils/MapReadableStates.java | 109 ++++++++++++ .../web3/state/utils/MapWritableKVState.java | 86 +++++++++ .../web3/state/utils/MapWritableStates.java | 117 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 163 ++++++++++++++++++ .../state/utils/MapReadableStatesTest.java | 150 ++++++++++++++++ .../state/utils/MapWritableKVStateTest.java | 123 +++++++++++++ .../state/utils/MapWritableStatesTest.java | 120 +++++++++++++ 8 files changed, 974 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java new file mode 100644 index 0000000000..e3cdce4089 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Named +public class MirrorNodeState implements State { + private final Map> tokenReadableServiceStates = new HashMap<>(); + private final Map> contractReadableServiceStates = new HashMap<>(); + private final Map> fileReadableServiceStates = new HashMap<>(); + + private final Map readableStates = new ConcurrentHashMap<>(); + + public MirrorNodeState( + final AccountReadableKVState accountReadableKVState, + final AirdropsReadableKVState airdropsReadableKVState, + final AliasesReadableKVState aliasesReadableKVState, + final ContractBytecodeReadableKVState contractBytecodeReadableKVState, + final ContractStorageReadableKVState contractStorageReadableKVState, + final FileReadableKVState fileReadableKVState, + final NftReadableKVState nftReadableKVState, + final TokenReadableKVState tokenReadableKVState, + final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { + + tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); + tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); + tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); + tokenReadableServiceStates.put("NFTS", nftReadableKVState); + tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); + tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + + contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); + contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + + fileReadableServiceStates.put("FILES", fileReadableKVState); + } + + @Nonnull + @Override + public ReadableStates getReadableStates(@Nonnull String serviceName) { + return readableStates.computeIfAbsent(serviceName, s -> { + switch (s) { + case TokenService.NAME -> { + return new MapReadableStates(tokenReadableServiceStates); + } + case ContractService.NAME -> { + return new MapReadableStates(contractReadableServiceStates); + } + case FileService.NAME -> { + return new MapReadableStates(fileReadableServiceStates); + } + default -> { + return new MapReadableStates(Collections.emptyMap()); + } + } + }); + } + + @Nonnull + @Override + public WritableStates getWritableStates(@Nonnull String serviceName) { + return switch (serviceName) { + case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); + case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); + case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); + default -> new MapWritableStates(Collections.emptyMap()); + }; + } + + private Map getWritableStates(final Map> readableStates) { + final Map data = new HashMap<>(); + readableStates.forEach(((s, readableKVState) -> + data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); + return data; + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java new file mode 100644 index 0000000000..6b354eb681 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapReadableStates implements ReadableStates { + + private final Map states; + + public MapReadableStates(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Nonnull + @Override + public ReadableKVState get(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof ReadableKVState)) { + throw new IllegalArgumentException("State is not an instance of ReadableKVState: " + stateKey); + } + + return (ReadableKVState) state; + } + + @Nonnull + @Override + public ReadableSingletonState getSingleton(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof ReadableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of ReadableSingletonState: " + stateKey); + } + + return (ReadableSingletonState) state; + } + + @Nonnull + @Override + public ReadableQueueState getQueue(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof ReadableQueueState)) { + throw new IllegalArgumentException("State is not an instance of ReadableQueueState: " + stateKey); + } + + return (ReadableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapReadableStates that = (MapReadableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java new file mode 100644 index 0000000000..b63ca2ab1a --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +public class MapWritableKVState extends WritableKVStateBase { + + private final ReadableKVState readableKVState; + + public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { + super(stateKey); + this.readableKVState = readableKVState; + } + + // The readable state's values are immutable, hence callers would not be able + // to modify the readable state's objects. + @Override + protected V getForModifyFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Override + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + put(key, value); // put only in memory + } + + @Override + protected void removeFromDataSource(@Nonnull K key) { + remove(key); // remove only in memory + } + + @Override + protected long sizeOfDataSource() { + return readableKVState.size(); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public void commit() { + reset(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) + && Objects.equals(readableKVState, that.readableKVState); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), readableKVState); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java new file mode 100644 index 0000000000..2fa19f006d --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static java.util.Objects.requireNonNull; + +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapWritableStates implements WritableStates, CommittableWritableStates { + + private final Map states; + + public MapWritableStates(Map states) { + this.states = states; + } + + @Nonnull + @Override + public WritableKVState get(@Nonnull String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof WritableKVState)) { + throw new IllegalArgumentException("State is not an instance of WritableKVState: " + stateKey); + } + + return (WritableKVState) state; + } + + @Nonnull + @Override + public WritableSingletonState getSingleton(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof WritableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of WritableSingletonState: " + stateKey); + } + + return (WritableSingletonState) state; + } + + @Nonnull + @Override + public WritableQueueState getQueue(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof WritableQueueState)) { + throw new IllegalArgumentException("State is not an instance of WritableQueueState: " + stateKey); + } + + return (WritableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWritableStates that = (MapWritableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } + + @Override + public void commit() { + // Empty body because we don't want to persist any changes to DB. + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java new file mode 100644 index 0000000000..6bcd187535 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MirrorNodeStateTest { + + @InjectMocks + private MirrorNodeState mirrorNodeState; + + @Mock + private AccountReadableKVState accountReadableKVState; + + @Mock + private AirdropsReadableKVState airdropsReadableKVState; + + @Mock + private AliasesReadableKVState aliasesReadableKVState; + + @Mock + private ContractBytecodeReadableKVState contractBytecodeReadableKVState; + + @Mock + private ContractStorageReadableKVState contractStorageReadableKVState; + + @Mock + private FileReadableKVState fileReadableKVState; + + @Mock + private NftReadableKVState nftReadableKVState; + + @Mock + private TokenReadableKVState tokenReadableKVState; + + @Mock + private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + + @Test + void testGetReadableStatesForFileService() { + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); + assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + } + + @Test + void testGetReadableStatesForContractService() { + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + } + + @Test + void testGetReadableStatesForTokenService() { + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "ACCOUNTS", + accountReadableKVState, + "PENDING_AIRDROPS", + airdropsReadableKVState, + "ALIASES", + aliasesReadableKVState, + "NFTS", + nftReadableKVState, + "TOKENS", + tokenReadableKVState, + "TOKEN_RELS", + tokenRelationshipReadableKVState))); + } + + @Test + void testGetReadableStateForUnsupportedService() { + assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); + } + + @Test + void testGetWritableStatesForFileService() { + when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + } + + @Test + void testGetWritableStatesForContractService() { + when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); + when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + + final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "BYTECODE", + new MapWritableKVState<>( + contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + "STORAGE", + new MapWritableKVState<>( + contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + } + + @Test + void testGetWritableStatesForTokenService() { + when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); + when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); + when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); + when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); + when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); + + final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "ACCOUNTS", + new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + "PENDING_AIRDROPS", + new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + "ALIASES", + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + "NFTS", + new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + "TOKENS", + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + "TOKEN_RELS", + new MapWritableKVState<>( + tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + } + + @Test + void testGetWritableStateForUnsupportedService() { + assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java new file mode 100644 index 0000000000..dde0939492 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableStatesTest { + + private MapReadableStates states; + + @Mock + private ReadableKVState kvStateMock; + + @Mock + private ReadableSingletonState singletonStateMock; + + @Mock + private ReadableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentType() { + assertThat(states).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableStates other = new MapReadableStates(Map.of(KV_STATE_KEY, kvStateMock)); + assertThat(states).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java new file mode 100644 index 0000000000..79813f24a0 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.swirlds.state.spi.ReadableKVState; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableKVStateTest { + + private MapWritableKVState mapWritableKVState; + + @Mock + private ReadableKVState readableKVState; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + } + + @Test + void testGetForModifyFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testDataSourceSizeIsZero() { + assertThat(mapWritableKVState.sizeOfDataSource()).isZero(); + } + + @Test + void testReadFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testIterateFromDataSourceReturnsEmptyIterator() { + assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + } + + @Test + void testPutIntoDataSource() { + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + } + + @Test + void testRemoveFromDataSource() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.removeFromDataSource(accountID); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testCommit() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.commit(); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapWritableKVState).isEqualTo(mapWritableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapWritableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java new file mode 100644 index 0000000000..9ab6defe74 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableStatesTest { + + private MapWritableStates states; + + @Mock + private WritableKVState kvStateMock; + + @Mock + private WritableSingletonState singletonStateMock; + + @Mock + private WritableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } +} From 804eae1d4ff884219454ed81a5c3ab8d0084250b Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Tue, 5 Nov 2024 11:35:27 +0200 Subject: [PATCH 02/33] feat: Add schema registry component for reusable services Signed-off-by: Kristiyan Selveliev --- .../state/components/SchemaRegistryImpl.java | 223 ++++++++++++++++++ .../components/ServicesRegistryImpl.java | 61 +++++ .../components/SchemaRegistryImplTest.java | 186 +++++++++++++++ .../components/ServicesRegistryImplTest.java | 67 ++++++ 4 files changed, 537 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java new file mode 100644 index 0000000000..1277c572a9 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import static com.hedera.node.app.state.merkle.SchemaApplicationType.MIGRATION; +import static com.hedera.node.app.state.merkle.SchemaApplicationType.RESTART; +import static com.hedera.node.app.state.merkle.SchemaApplicationType.STATE_DEFINITIONS; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.mirror.web3.state.MirrorNodeState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.spi.state.FilteredReadableStates; +import com.hedera.node.app.spi.state.FilteredWritableStates; +import com.hedera.node.app.state.merkle.SchemaApplications; +import com.swirlds.config.api.Configuration; +import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.state.spi.MigrationContext; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.Schema; +import com.swirlds.state.spi.SchemaRegistry; +import com.swirlds.state.spi.WritableStates; +import com.swirlds.state.spi.info.NetworkInfo; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import lombok.Getter; + +public class SchemaRegistryImpl implements SchemaRegistry { + + public static final SemanticVersion CURRENT_VERSION = new SemanticVersion(0, 47, 0, "SNAPSHOT", ""); + + private final SchemaApplications schemaApplications; + + public SchemaRegistryImpl(@Nonnull final SchemaApplications schemaApplications) { + this.schemaApplications = schemaApplications; + } + + /** + * The ordered set of all schemas registered by the service + */ + @Getter + private final SortedSet schemas = new TreeSet<>(); + + @Override + public SchemaRegistry register(@Nonnull Schema schema) { + schemas.add(schema); + return this; + } + + @SuppressWarnings("rawtypes") + public void migrate( + @Nonnull final String serviceName, + @Nonnull final MirrorNodeState state, + @Nonnull final NetworkInfo networkInfo) { + migrate( + serviceName, + state, + CURRENT_VERSION, + networkInfo, + ConfigurationBuilder.create().build(), + new HashMap<>(), + new AtomicLong()); + } + + public void migrate( + @Nonnull final String serviceName, + @Nonnull final MirrorNodeState state, + @Nullable final SemanticVersion previousVersion, + @Nonnull final NetworkInfo networkInfo, + @Nonnull final Configuration config, + @Nonnull final Map sharedValues, + @Nonnull final AtomicLong nextEntityNum) { + if (schemas.isEmpty()) { + return; + } + + // For each schema, create the underlying raw data sources (maps, or lists) and the writable states that + // will wrap them. Then call the schema's migrate method to populate those states, and commit each of them + // to the underlying data sources. At that point, we have properly migrated the state. + final var latestVersion = schemas.getLast().getVersion(); + + for (final var schema : schemas) { + final var applications = + schemaApplications.computeApplications(previousVersion, latestVersion, schema, config); + final var readableStates = state.getReadableStates(serviceName); + final var previousStates = new FilteredReadableStates(readableStates, readableStates.stateKeys()); + final WritableStates writableStates; + final WritableStates newStates; + if (applications.contains(STATE_DEFINITIONS)) { + final var redefinedWritableStates = applyStateDefinitions(serviceName, schema, config, state); + writableStates = redefinedWritableStates.beforeStates(); + newStates = redefinedWritableStates.afterStates(); + } else { + newStates = writableStates = state.getWritableStates(serviceName); + } + final var context = newMigrationContext( + previousVersion, previousStates, newStates, config, networkInfo, nextEntityNum, sharedValues); + if (applications.contains(MIGRATION)) { + schema.migrate(context); + } + if (applications.contains(RESTART)) { + schema.restart(context); + } + if (writableStates instanceof MapWritableStates mws) { + mws.commit(); + } + // TODO uncomment + // And finally we can remove any states we need to remove + // schema.statesToRemove().forEach(stateKey -> state.removeServiceState(serviceName, stateKey)); + } + } + + public MigrationContext newMigrationContext( + @Nullable final SemanticVersion previousVersion, + @Nonnull final ReadableStates previousStates, + @Nonnull final WritableStates writableStates, + @Nonnull final Configuration config, + @Nonnull final NetworkInfo networkInfo, + @Nonnull final AtomicLong nextEntityNum, + @Nonnull final Map sharedValues) { + return new MigrationContext() { + @Override + public void copyAndReleaseOnDiskState(String stateKey) { + // No-op + } + + @Override + public SemanticVersion previousVersion() { + return previousVersion; + } + + @Nonnull + @Override + public ReadableStates previousStates() { + return previousStates; + } + + @Nonnull + @Override + public WritableStates newStates() { + return writableStates; + } + + @Nonnull + @Override + public Configuration configuration() { + return config; + } + + @Override + public NetworkInfo networkInfo() { + return networkInfo; + } + + @Override + public long newEntityNum() { + return nextEntityNum.getAndIncrement(); + } + + @Override + public Map sharedValues() { + return sharedValues; + } + }; + } + + /** + * Encapsulates the writable states before and after applying a schema's state definitions. + * + * @param beforeStates the writable states before applying the schema's state definitions + * @param afterStates the writable states after applying the schema's state definitions + */ + private record RedefinedWritableStates(WritableStates beforeStates, WritableStates afterStates) {} + + private RedefinedWritableStates applyStateDefinitions( + @Nonnull final String serviceName, + @Nonnull final Schema schema, + @Nonnull final Configuration configuration, + @Nonnull final MirrorNodeState state) { + final Map stateDataSources = new HashMap<>(); + schema.statesToCreate(configuration).forEach(def -> { + if (def.singleton()) { + stateDataSources.put(def.stateKey(), new AtomicReference<>()); + } else if (def.queue()) { + stateDataSources.put(def.stateKey(), new ConcurrentLinkedDeque<>()); + } else { + stateDataSources.put(def.stateKey(), new ConcurrentHashMap<>()); + } + }); + + // TODO uncomment + // state.addService(serviceName, stateDataSources); + + final var statesToRemove = schema.statesToRemove(); + final var writableStates = state.getWritableStates(serviceName); + final var remainingStates = new HashSet<>(writableStates.stateKeys()); + remainingStates.removeAll(statesToRemove); + final var newStates = new FilteredWritableStates(writableStates, remainingStates); + return new RedefinedWritableStates(writableStates, newStates); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java new file mode 100644 index 0000000000..1e2c2dac85 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import com.hedera.node.app.services.ServicesRegistry; +import com.hedera.node.app.state.merkle.SchemaApplications; +import com.swirlds.state.spi.Service; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +@Named +public class ServicesRegistryImpl implements ServicesRegistry { + + private final SortedSet entries; + + public ServicesRegistryImpl() { + this.entries = new TreeSet<>(); + } + + @Nonnull + @Override + public Set registrations() { + return Collections.unmodifiableSortedSet(entries); + } + + @Override + public void register(@Nonnull Service service) { + final var registry = new SchemaRegistryImpl(new SchemaApplications()); + service.registerSchemas(registry); + entries.add(new ServicesRegistryImpl.Registration(service, registry)); + } + + @Nonnull + @Override + public ServicesRegistry subRegistryFor(@Nonnull String... serviceNames) { + final var selections = Set.of(serviceNames); + final var subRegistry = new ServicesRegistryImpl(); + subRegistry.entries.addAll(entries.stream() + .filter(registration -> selections.contains(registration.serviceName())) + .toList()); + return subRegistry; + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java new file mode 100644 index 0000000000..5518331248 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import static java.util.Collections.EMPTY_MAP; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.mirror.web3.state.MirrorNodeState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.config.ConfigProviderImpl; +import com.hedera.node.app.state.merkle.SchemaApplicationType; +import com.hedera.node.app.state.merkle.SchemaApplications; +import com.hedera.pbj.runtime.Codec; +import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.MigrationContext; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.Schema; +import com.swirlds.state.spi.StateDefinition; +import com.swirlds.state.spi.info.NetworkInfo; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SchemaRegistryImplTest { + + @Mock + private MirrorNodeState mirrorNodeState; + + @Mock + private NetworkInfo networkInfo; + + @Mock + private Schema schema; + + @Mock + private MapWritableStates writableStates; + + @Mock + private ReadableStates readableStates; + + @Mock + private SchemaApplications schemaApplications; + + @Mock + private Codec mockCodec; + + private Configuration config; + + private SchemaRegistryImpl schemaRegistry; + + private final String serviceName = "testService"; + private final SemanticVersion previousVersion = new SemanticVersion(0, 46, 0, "", ""); + + @BeforeEach + void initialize() { + schemaRegistry = new SchemaRegistryImpl(schemaApplications); + config = new ConfigProviderImpl().getConfiguration(); + } + + @Test + void testRegisterSchema() { + schemaRegistry.register(schema); + SortedSet schemas = schemaRegistry.getSchemas(); + assertThat(schemas).contains(schema); + } + + @Test + void testMigrateWithNoSchemas() { + schemaRegistry.migrate(serviceName, mirrorNodeState, networkInfo); + verify(mirrorNodeState, never()).getWritableStates(any()); + } + + @Test + void testMigrateWithSingleSchema() { + when(mirrorNodeState.getWritableStates(serviceName)).thenReturn(writableStates); + when(mirrorNodeState.getReadableStates(serviceName)).thenReturn(readableStates); + when(schemaApplications.computeApplications(any(), any(), any(), any())) + .thenReturn(EnumSet.noneOf(SchemaApplicationType.class)); + + schemaRegistry.register(schema); + + schemaRegistry.migrate( + serviceName, mirrorNodeState, previousVersion, networkInfo, config, new HashMap<>(), new AtomicLong()); + verify(mirrorNodeState, times(1)).getWritableStates(serviceName); + verify(mirrorNodeState, times(1)).getReadableStates(serviceName); + verify(writableStates, times(1)).commit(); + } + + @Test + void testMigrateWithMigrations() { + when(mirrorNodeState.getWritableStates(serviceName)).thenReturn(writableStates); + when(mirrorNodeState.getReadableStates(serviceName)).thenReturn(readableStates); + when(schemaApplications.computeApplications(any(), any(), any(), any())) + .thenReturn(EnumSet.of(SchemaApplicationType.MIGRATION)); + schemaRegistry.register(schema); + schemaRegistry.migrate( + serviceName, mirrorNodeState, previousVersion, networkInfo, config, new HashMap<>(), new AtomicLong()); + + verify(schema).migrate(any()); + verify(writableStates, times(1)).commit(); + } + + @Test + void testMigrateWithStateDefinitions() { + when(mirrorNodeState.getWritableStates(serviceName)).thenReturn(writableStates); + when(mirrorNodeState.getReadableStates(serviceName)).thenReturn(readableStates); + when(schemaApplications.computeApplications(any(), any(), any(), any())) + .thenReturn(EnumSet.of(SchemaApplicationType.STATE_DEFINITIONS, SchemaApplicationType.MIGRATION)); + + StateDefinition stateDefinitionSingleton = + new StateDefinition("KEY", mockCodec, mockCodec, 123, false, true, false); + + StateDefinition stateDefinitionQueue = + new StateDefinition("KEY_QUEUE", mockCodec, mockCodec, 123, false, false, true); + + StateDefinition stateDefinition = new StateDefinition("STATE", mockCodec, mockCodec, 123, true, false, false); + + when(schema.statesToCreate(config)) + .thenReturn(Set.of(stateDefinitionSingleton, stateDefinitionQueue, stateDefinition)); + + schemaRegistry.register(schema); + schemaRegistry.migrate( + serviceName, mirrorNodeState, previousVersion, networkInfo, config, new HashMap<>(), new AtomicLong()); + verify(mirrorNodeState, times(1)).getWritableStates(serviceName); + verify(mirrorNodeState, times(1)).getReadableStates(serviceName); + verify(schema).migrate(any()); + verify(writableStates, times(1)).commit(); + // TODO uncomment + // verify(mirrorNodeState, times(1)).addService(any(), any()); + } + + @Test + void testMigrateWithRestartApplication() { + when(mirrorNodeState.getWritableStates(serviceName)).thenReturn(writableStates); + when(mirrorNodeState.getReadableStates(serviceName)).thenReturn(readableStates); + when(schemaApplications.computeApplications(any(), any(), any(), any())) + .thenReturn(EnumSet.of(SchemaApplicationType.RESTART)); + + schemaRegistry.register(schema); + schemaRegistry.migrate( + serviceName, mirrorNodeState, previousVersion, networkInfo, config, new HashMap<>(), new AtomicLong()); + + verify(schema).restart(any()); + verify(writableStates, times(1)).commit(); + } + + @Test + void testNewMigrationContext() { + MigrationContext context = schemaRegistry.newMigrationContext( + previousVersion, readableStates, writableStates, config, networkInfo, new AtomicLong(1), EMPTY_MAP); + + assertThat(context).satisfies(c -> { + assertThat(c.previousVersion()).isEqualTo(previousVersion); + assertThat(c.previousStates()).isEqualTo(readableStates); + assertThat(c.newStates()).isEqualTo(writableStates); + assertThat(c.configuration()).isEqualTo(config); + assertThat(c.networkInfo()).isEqualTo(networkInfo); + assertThat(c.newEntityNum()).isEqualTo(1); + assertThat(c.sharedValues()).isEqualTo(EMPTY_MAP); + }); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java new file mode 100644 index 0000000000..6169a25aff --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hedera.node.app.ids.EntityIdService; +import com.hedera.node.app.service.file.impl.FileServiceImpl; +import com.swirlds.state.spi.Service; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ServicesRegistryImplTest { + + private ServicesRegistryImpl servicesRegistry; + + @BeforeEach + void setUp() { + servicesRegistry = new ServicesRegistryImpl(); + } + + @Test + void testEmptyRegistrations() { + assertThat(servicesRegistry.registrations()).isEmpty(); + } + + @Test + void testRegister() { + Service service = new FileServiceImpl(); + servicesRegistry.register(service); + assertThat(servicesRegistry.registrations().size()).isEqualTo(1); + } + + @Test + void testEmptySubRegistry() { + Service service = new FileServiceImpl(); + Service service2 = new EntityIdService(); + ServicesRegistryImpl subRegistry = (ServicesRegistryImpl) + servicesRegistry.subRegistryFor(service.getServiceName(), service2.getServiceName()); + assertThat(subRegistry.registrations()).isEmpty(); + } + + @Test + void testSubRegistry() { + Service service = new FileServiceImpl(); + Service service2 = new EntityIdService(); + servicesRegistry.register(service); + servicesRegistry.register(service2); + ServicesRegistryImpl subRegistry = (ServicesRegistryImpl) + servicesRegistry.subRegistryFor(service.getServiceName(), service2.getServiceName()); + assertThat(subRegistry.registrations().size()).isEqualTo(2); + } +} From e00abba7e847bea530fcff40edcdb824e7a44a8d Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Tue, 5 Nov 2024 16:03:21 +0200 Subject: [PATCH 03/33] feat: Add service migrator similar to services Signed-off-by: Kristiyan Selveliev --- .../state/components/ServiceMigratorImpl.java | 126 ++++++++ .../components/ServiceMigratorImplTest.java | 293 ++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java new file mode 100644 index 0000000000..4f371a34e6 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.block.stream.output.StateChanges.Builder; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.mirror.web3.state.MirrorNodeState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.services.ServiceMigrator; +import com.hedera.node.app.services.ServicesRegistry; +import com.hedera.node.config.data.HederaConfig; +import com.swirlds.config.api.Configuration; +import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.state.State; +import com.swirlds.state.spi.info.NetworkInfo; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +public class ServiceMigratorImpl implements ServiceMigrator { + + public static final String NAME_OF_ENTITY_ID_SERVICE = "EntityIdService"; + public static final String NAME_OF_ENTITY_ID_SINGLETON = "ENTITY_ID"; + + @Override + public List doMigrations( + @Nonnull State state, + @Nonnull ServicesRegistry servicesRegistry, + @Nullable SoftwareVersion previousVersion, + @Nonnull SoftwareVersion currentVersion, + @Nonnull Configuration config, + @Nonnull NetworkInfo networkInfo, + @Nonnull Metrics metrics) { + requireNonNull(state); + requireNonNull(servicesRegistry); + requireNonNull(currentVersion); + requireNonNull(config); + requireNonNull(networkInfo); + requireNonNull(metrics); + + if (!(state instanceof MirrorNodeState mirrorNodeState)) { + throw new IllegalArgumentException("Can only be used with MirrorNodeState instances"); + } + + if (!(servicesRegistry instanceof ServicesRegistryImpl registry)) { + throw new IllegalArgumentException("Can only be used with ServicesRegistryImpl instances"); + } + + final AtomicLong prevEntityNum = + new AtomicLong(config.getConfigData(HederaConfig.class).firstUserEntity() - 1); + final Map sharedValues = new HashMap<>(); + final var entityIdRegistration = registry.registrations().stream() + .filter(service -> + NAME_OF_ENTITY_ID_SERVICE.equals(service.service().getServiceName())) + .findFirst() + .orElseThrow(); + if (!(entityIdRegistration.registry() instanceof SchemaRegistryImpl entityIdRegistry)) { + throw new IllegalArgumentException("Can only be used with SchemaRegistryImpl instances"); + } + final var deserializedPbjVersion = Optional.ofNullable(previousVersion) + .map(SoftwareVersion::getPbjSemanticVersion) + .orElse(null); + entityIdRegistry.migrate( + NAME_OF_ENTITY_ID_SERVICE, + mirrorNodeState, + deserializedPbjVersion, + networkInfo, + config, + sharedValues, + prevEntityNum); + registry.registrations().stream() + .filter(r -> !Objects.equals(entityIdRegistration, r)) + .forEach(registration -> { + if (!(registration.registry() instanceof SchemaRegistryImpl schemaRegistry)) { + throw new IllegalArgumentException("Can only be used with SchemaRegistryImpl instances"); + } + schemaRegistry.migrate( + registration.serviceName(), + mirrorNodeState, + deserializedPbjVersion, + networkInfo, + config, + sharedValues, + prevEntityNum); + }); + final var entityIdWritableStates = mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE); + if (!(entityIdWritableStates instanceof MapWritableStates mapWritableStates)) { + throw new IllegalArgumentException("Can only be used with MapWritableStates instances"); + } + mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON).put(new EntityNumber(prevEntityNum.get())); + mapWritableStates.commit(); + return List.of(); + } + + @Nullable + @Override + public SemanticVersion creationVersionOf(@Nonnull State state) { + if (!(state instanceof MirrorNodeState)) { + throw new IllegalArgumentException("Can only be used with MirrorNodeState instances"); + } + return null; + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java new file mode 100644 index 0000000000..1e1be3ac7a --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.components; + +import static com.hedera.mirror.web3.state.components.ServiceMigratorImpl.NAME_OF_ENTITY_ID_SERVICE; +import static com.hedera.mirror.web3.state.components.ServiceMigratorImpl.NAME_OF_ENTITY_ID_SINGLETON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.hedera.mirror.web3.state.MirrorNodeState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.config.BootstrapConfigProviderImpl; +import com.hedera.node.app.config.ConfigProviderImpl; +import com.hedera.node.app.services.ServicesRegistry; +import com.hedera.node.app.services.ServicesRegistry.Registration; +import com.hedera.node.app.version.ServicesSoftwareVersion; +import com.hedera.node.config.VersionedConfiguration; +import com.hedera.node.config.data.VersionConfig; +import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.State; +import com.swirlds.state.spi.EmptyWritableStates; +import com.swirlds.state.spi.SchemaRegistry; +import com.swirlds.state.spi.Service; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.info.NetworkInfo; +import java.util.NoSuchElementException; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ServiceMigratorImplTest { + + @Mock + private MapWritableStates mapWritableStates; + + @Mock + private Metrics metrics; + + @Mock + private MirrorNodeState mirrorNodeState; + + @Mock + private NetworkInfo networkInfo; + + @Mock + private SchemaRegistryImpl schemaRegistry; + + @Mock + private SchemaRegistry mockSchemaRegistry; + + @Mock + private ServicesRegistryImpl servicesRegistry; + + @Mock + private ServicesRegistry mockServicesRegistry; + + @Mock + private State mockState; + + @Mock + private WritableSingletonState writableSingletonState; + + private VersionedConfiguration bootstrapConfig; + + private ServiceMigratorImpl serviceMigrator; + + @BeforeEach + void initialize() { + serviceMigrator = new ServiceMigratorImpl(); + bootstrapConfig = new BootstrapConfigProviderImpl().getConfiguration(); + } + + @Test + void testCreationVersionOfWithMirrorNodeState() { + assertDoesNotThrow(() -> serviceMigrator.creationVersionOf(mirrorNodeState)); + } + + @Test + void testCreationVersionOfWithInvalidState() { + assertThrows(IllegalArgumentException.class, () -> serviceMigrator.creationVersionOf(mockState)); + } + + @Test + void doMigrations() { + final var mockServiceRegistration = mock(Registration.class); + final var mockService = mock(Service.class); + when(mockServiceRegistration.service()).thenReturn(mockService); + when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); + when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); + when(mockServiceRegistration.registry()).thenReturn(schemaRegistry); + when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mapWritableStates); + when(mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON)).thenReturn(writableSingletonState); + + serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion( + bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics); + + verify(mapWritableStates, times(1)).commit(); + } + + @Test + void doMigrationsWithMultipleRegistrations() { + Service service1 = mock(Service.class); + when(service1.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); + + Service service2 = mock(Service.class); + when(service2.getServiceName()).thenReturn("testService2"); + + SchemaRegistry registry1 = mock(SchemaRegistryImpl.class); + SchemaRegistry registry2 = mock(SchemaRegistryImpl.class); + + Registration registration1 = new Registration(service1, registry1); + Registration registration2 = new Registration(service2, registry2); + when(servicesRegistry.registrations()).thenReturn(Set.of(registration1, registration2)); + when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mapWritableStates); + when(mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON)).thenReturn(writableSingletonState); + + serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion( + bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics); + + verify(mapWritableStates, times(1)).commit(); + } + + @Test + void doMigrationsWithMultipleRegistrationsWithInvalidSchemaRegistry() { + Service service1 = mock(Service.class); + when(service1.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); + + Service service2 = mock(Service.class); + + SchemaRegistry registry1 = mock(SchemaRegistryImpl.class); + + // Invalid schema registry type + SchemaRegistry registry2 = mock(SchemaRegistry.class); + + Registration registration1 = new Registration(service1, registry1); + Registration registration2 = new Registration(service2, registry2); + when(servicesRegistry.registrations()).thenReturn(Set.of(registration1, registration2)); + var exception = assertThrows( + IllegalArgumentException.class, + () -> serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + + assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); + } + + @Test + void doMigrationsInvalidRegistrations() { + assertThrows( + NoSuchElementException.class, + () -> serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + } + + @Test + void doMigrationsInvalidState() { + var exception = assertThrows( + IllegalArgumentException.class, + () -> serviceMigrator.doMigrations( + mockState, + servicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + + assertThat(exception.getMessage()).isEqualTo("Can only be used with MirrorNodeState instances"); + } + + @Test + void doMigrationsInvalidServicesRegistry() { + var exception = assertThrows( + IllegalArgumentException.class, + () -> serviceMigrator.doMigrations( + mirrorNodeState, + mockServicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + + assertThat(exception.getMessage()).isEqualTo("Can only be used with ServicesRegistryImpl instances"); + } + + @Test + void doMigrationsInvalidSchemaRegistry() { + final var mockServiceRegistration = mock(Registration.class); + final var mockService = mock(Service.class); + when(mockServiceRegistration.service()).thenReturn(mockService); + when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); + when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); + when(mockServiceRegistration.registry()).thenReturn(mockSchemaRegistry); + var exception = assertThrows( + IllegalArgumentException.class, + () -> serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + + assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); + } + + @Test + void doMigrationsInvalidWritableStates() { + final var mockServiceRegistration = mock(Registration.class); + final var mockService = mock(Service.class); + when(mockServiceRegistration.service()).thenReturn(mockService); + when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); + when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); + when(mockServiceRegistration.registry()).thenReturn(schemaRegistry); + final var mockMapWritableStates = mock(EmptyWritableStates.class); + when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mockMapWritableStates); + + var exception = assertThrows( + IllegalArgumentException.class, + () -> serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + new ServicesSoftwareVersion(bootstrapConfig + .getConfigData(VersionConfig.class) + .servicesVersion()), + new ConfigProviderImpl().getConfiguration(), + networkInfo, + metrics)); + + assertThat(exception.getMessage()).isEqualTo("Can only be used with MapWritableStates instances"); + } +} From dda0c802f779fe3aafab256bf028523c6ab2ba59 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 4 Nov 2024 14:39:26 +0200 Subject: [PATCH 04/33] Implement MirrorNodeState Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 106 ++++++++++++ .../web3/state/utils/MapReadableStates.java | 109 ++++++++++++ .../web3/state/utils/MapWritableKVState.java | 86 +++++++++ .../web3/state/utils/MapWritableStates.java | 117 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 163 ++++++++++++++++++ .../state/utils/MapReadableStatesTest.java | 150 ++++++++++++++++ .../state/utils/MapWritableKVStateTest.java | 123 +++++++++++++ .../state/utils/MapWritableStatesTest.java | 120 +++++++++++++ 8 files changed, 974 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java new file mode 100644 index 0000000000..e3cdce4089 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Named +public class MirrorNodeState implements State { + private final Map> tokenReadableServiceStates = new HashMap<>(); + private final Map> contractReadableServiceStates = new HashMap<>(); + private final Map> fileReadableServiceStates = new HashMap<>(); + + private final Map readableStates = new ConcurrentHashMap<>(); + + public MirrorNodeState( + final AccountReadableKVState accountReadableKVState, + final AirdropsReadableKVState airdropsReadableKVState, + final AliasesReadableKVState aliasesReadableKVState, + final ContractBytecodeReadableKVState contractBytecodeReadableKVState, + final ContractStorageReadableKVState contractStorageReadableKVState, + final FileReadableKVState fileReadableKVState, + final NftReadableKVState nftReadableKVState, + final TokenReadableKVState tokenReadableKVState, + final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { + + tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); + tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); + tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); + tokenReadableServiceStates.put("NFTS", nftReadableKVState); + tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); + tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + + contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); + contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + + fileReadableServiceStates.put("FILES", fileReadableKVState); + } + + @Nonnull + @Override + public ReadableStates getReadableStates(@Nonnull String serviceName) { + return readableStates.computeIfAbsent(serviceName, s -> { + switch (s) { + case TokenService.NAME -> { + return new MapReadableStates(tokenReadableServiceStates); + } + case ContractService.NAME -> { + return new MapReadableStates(contractReadableServiceStates); + } + case FileService.NAME -> { + return new MapReadableStates(fileReadableServiceStates); + } + default -> { + return new MapReadableStates(Collections.emptyMap()); + } + } + }); + } + + @Nonnull + @Override + public WritableStates getWritableStates(@Nonnull String serviceName) { + return switch (serviceName) { + case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); + case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); + case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); + default -> new MapWritableStates(Collections.emptyMap()); + }; + } + + private Map getWritableStates(final Map> readableStates) { + final Map data = new HashMap<>(); + readableStates.forEach(((s, readableKVState) -> + data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); + return data; + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java new file mode 100644 index 0000000000..6b354eb681 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapReadableStates implements ReadableStates { + + private final Map states; + + public MapReadableStates(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Nonnull + @Override + public ReadableKVState get(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof ReadableKVState)) { + throw new IllegalArgumentException("State is not an instance of ReadableKVState: " + stateKey); + } + + return (ReadableKVState) state; + } + + @Nonnull + @Override + public ReadableSingletonState getSingleton(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof ReadableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of ReadableSingletonState: " + stateKey); + } + + return (ReadableSingletonState) state; + } + + @Nonnull + @Override + public ReadableQueueState getQueue(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof ReadableQueueState)) { + throw new IllegalArgumentException("State is not an instance of ReadableQueueState: " + stateKey); + } + + return (ReadableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapReadableStates that = (MapReadableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java new file mode 100644 index 0000000000..b63ca2ab1a --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +public class MapWritableKVState extends WritableKVStateBase { + + private final ReadableKVState readableKVState; + + public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { + super(stateKey); + this.readableKVState = readableKVState; + } + + // The readable state's values are immutable, hence callers would not be able + // to modify the readable state's objects. + @Override + protected V getForModifyFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Override + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + put(key, value); // put only in memory + } + + @Override + protected void removeFromDataSource(@Nonnull K key) { + remove(key); // remove only in memory + } + + @Override + protected long sizeOfDataSource() { + return readableKVState.size(); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public void commit() { + reset(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) + && Objects.equals(readableKVState, that.readableKVState); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), readableKVState); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java new file mode 100644 index 0000000000..2fa19f006d --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static java.util.Objects.requireNonNull; + +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapWritableStates implements WritableStates, CommittableWritableStates { + + private final Map states; + + public MapWritableStates(Map states) { + this.states = states; + } + + @Nonnull + @Override + public WritableKVState get(@Nonnull String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof WritableKVState)) { + throw new IllegalArgumentException("State is not an instance of WritableKVState: " + stateKey); + } + + return (WritableKVState) state; + } + + @Nonnull + @Override + public WritableSingletonState getSingleton(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof WritableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of WritableSingletonState: " + stateKey); + } + + return (WritableSingletonState) state; + } + + @Nonnull + @Override + public WritableQueueState getQueue(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof WritableQueueState)) { + throw new IllegalArgumentException("State is not an instance of WritableQueueState: " + stateKey); + } + + return (WritableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWritableStates that = (MapWritableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } + + @Override + public void commit() { + // Empty body because we don't want to persist any changes to DB. + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java new file mode 100644 index 0000000000..6bcd187535 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MirrorNodeStateTest { + + @InjectMocks + private MirrorNodeState mirrorNodeState; + + @Mock + private AccountReadableKVState accountReadableKVState; + + @Mock + private AirdropsReadableKVState airdropsReadableKVState; + + @Mock + private AliasesReadableKVState aliasesReadableKVState; + + @Mock + private ContractBytecodeReadableKVState contractBytecodeReadableKVState; + + @Mock + private ContractStorageReadableKVState contractStorageReadableKVState; + + @Mock + private FileReadableKVState fileReadableKVState; + + @Mock + private NftReadableKVState nftReadableKVState; + + @Mock + private TokenReadableKVState tokenReadableKVState; + + @Mock + private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + + @Test + void testGetReadableStatesForFileService() { + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); + assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + } + + @Test + void testGetReadableStatesForContractService() { + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + } + + @Test + void testGetReadableStatesForTokenService() { + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "ACCOUNTS", + accountReadableKVState, + "PENDING_AIRDROPS", + airdropsReadableKVState, + "ALIASES", + aliasesReadableKVState, + "NFTS", + nftReadableKVState, + "TOKENS", + tokenReadableKVState, + "TOKEN_RELS", + tokenRelationshipReadableKVState))); + } + + @Test + void testGetReadableStateForUnsupportedService() { + assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); + } + + @Test + void testGetWritableStatesForFileService() { + when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + } + + @Test + void testGetWritableStatesForContractService() { + when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); + when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + + final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "BYTECODE", + new MapWritableKVState<>( + contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + "STORAGE", + new MapWritableKVState<>( + contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + } + + @Test + void testGetWritableStatesForTokenService() { + when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); + when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); + when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); + when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); + when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); + + final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "ACCOUNTS", + new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + "PENDING_AIRDROPS", + new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + "ALIASES", + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + "NFTS", + new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + "TOKENS", + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + "TOKEN_RELS", + new MapWritableKVState<>( + tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + } + + @Test + void testGetWritableStateForUnsupportedService() { + assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java new file mode 100644 index 0000000000..dde0939492 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableStatesTest { + + private MapReadableStates states; + + @Mock + private ReadableKVState kvStateMock; + + @Mock + private ReadableSingletonState singletonStateMock; + + @Mock + private ReadableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentType() { + assertThat(states).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableStates other = new MapReadableStates(Map.of(KV_STATE_KEY, kvStateMock)); + assertThat(states).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java new file mode 100644 index 0000000000..79813f24a0 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.swirlds.state.spi.ReadableKVState; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableKVStateTest { + + private MapWritableKVState mapWritableKVState; + + @Mock + private ReadableKVState readableKVState; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + } + + @Test + void testGetForModifyFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testDataSourceSizeIsZero() { + assertThat(mapWritableKVState.sizeOfDataSource()).isZero(); + } + + @Test + void testReadFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testIterateFromDataSourceReturnsEmptyIterator() { + assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + } + + @Test + void testPutIntoDataSource() { + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + } + + @Test + void testRemoveFromDataSource() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.removeFromDataSource(accountID); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testCommit() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.commit(); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapWritableKVState).isEqualTo(mapWritableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapWritableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java new file mode 100644 index 0000000000..9ab6defe74 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableStatesTest { + + private MapWritableStates states; + + @Mock + private WritableKVState kvStateMock; + + @Mock + private WritableSingletonState singletonStateMock; + + @Mock + private WritableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } +} From 7b1d58d7afb27db9e06fe764b5d5d1e5e9f5012d Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:01:56 +0200 Subject: [PATCH 05/33] Add dynamic service configuration Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 113 ++++++++++-------- .../web3/state/utils/MapReadableKVState.java | 81 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 90 +++++++++++--- .../state/utils/MapReadableKVStateTest.java | 109 +++++++++++++++++ 4 files changed, 323 insertions(+), 70 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index e3cdce4089..85d1971c00 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,91 +16,98 @@ package com.hedera.mirror.web3.state; +import static java.util.Objects.requireNonNull; + +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; -import com.hedera.node.app.service.contract.ContractService; -import com.hedera.node.app.service.file.FileService; -import com.hedera.node.app.service.token.TokenService; import com.swirlds.state.State; -import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.EmptyReadableStates; +import com.swirlds.state.spi.EmptyWritableStates; import com.swirlds.state.spi.ReadableStates; import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@SuppressWarnings({"rawtypes", "unchecked"}) @Named public class MirrorNodeState implements State { - private final Map> tokenReadableServiceStates = new HashMap<>(); - private final Map> contractReadableServiceStates = new HashMap<>(); - private final Map> fileReadableServiceStates = new HashMap<>(); private final Map readableStates = new ConcurrentHashMap<>(); + // Key is Service, value is Map of state name to state datasource + private final Map> states = new ConcurrentHashMap<>(); - public MirrorNodeState( - final AccountReadableKVState accountReadableKVState, - final AirdropsReadableKVState airdropsReadableKVState, - final AliasesReadableKVState aliasesReadableKVState, - final ContractBytecodeReadableKVState contractBytecodeReadableKVState, - final ContractStorageReadableKVState contractStorageReadableKVState, - final FileReadableKVState fileReadableKVState, - final NftReadableKVState nftReadableKVState, - final TokenReadableKVState tokenReadableKVState, - final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { - - tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); - tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); - tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); - tokenReadableServiceStates.put("NFTS", nftReadableKVState); - tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); - tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map dataSources) { + final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>()); + dataSources.forEach((k, b) -> { + if (!serviceStates.containsKey(k)) { + serviceStates.put(k, b); + } + }); - contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); - contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + // Purge any readable states whose state definitions are now stale, + // since they don't include the new data sources we just added + readableStates.remove(serviceName); + return this; + } - fileReadableServiceStates.put("FILES", fileReadableKVState); + /** + * Removes the state with the given key for the service with the given name. + * + * @param serviceName the name of the service + * @param stateKey the key of the state + */ + public void removeServiceState(@NonNull final String serviceName, @NonNull final String stateKey) { + requireNonNull(serviceName); + requireNonNull(stateKey); + this.states.computeIfPresent(serviceName, (k, v) -> { + v.remove(stateKey); + readableStates.remove(serviceName); // Remove the service so that its states will be repopulated. + return v; + }); } @Nonnull @Override public ReadableStates getReadableStates(@Nonnull String serviceName) { return readableStates.computeIfAbsent(serviceName, s -> { - switch (s) { - case TokenService.NAME -> { - return new MapReadableStates(tokenReadableServiceStates); - } - case ContractService.NAME -> { - return new MapReadableStates(contractReadableServiceStates); - } - case FileService.NAME -> { - return new MapReadableStates(fileReadableServiceStates); - } - default -> { - return new MapReadableStates(Collections.emptyMap()); + final var serviceStates = this.states.get(s); + if (serviceStates == null) { + return new EmptyReadableStates(); + } + final Map states = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map map) { + states.put(stateName, new MapReadableKVState(stateName, map)); } } + return new MapReadableStates(states); }); } @Nonnull @Override public WritableStates getWritableStates(@Nonnull String serviceName) { - return switch (serviceName) { - case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); - case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); - case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); - default -> new MapWritableStates(Collections.emptyMap()); - }; - } + final var serviceStates = states.get(serviceName); + if (serviceStates == null) { + return new EmptyWritableStates(); + } - private Map getWritableStates(final Map> readableStates) { - final Map data = new HashMap<>(); - readableStates.forEach(((s, readableKVState) -> - data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); - return data; + final Map data = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map) { + final var readableState = getReadableStates(serviceName).get(stateName); + data.put(stateName, new MapWritableKVState<>(stateName, readableState)); + } + } + return new MapWritableStates(data); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java new file mode 100644 index 0000000000..3d37e99211 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023-2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * A simple implementation of {@link ReadableKVState} backed by a + * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by + * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy + * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in + * some strange case, or in some other way work with the backing map directly. + * + * @param The key type + * @param The value type + */ +public class MapReadableKVState extends ReadableKVStateBase { + /** Represents the backing storage for this state */ + private final Map backingStore; + + /** + * Create an instance using the given map as the backing store. This is useful when you want to + * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public MapReadableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return backingStore.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return backingStore.keySet().iterator(); + } + + @Override + public long size() { + return backingStore.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapReadableKVState that = (MapReadableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), backingStore); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 6bcd187535..67686501d1 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -19,13 +19,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.when; +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -65,10 +69,54 @@ class MirrorNodeStateTest { @Mock private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + @BeforeEach + void setup() { + final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map contractStateData = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final Map tokenStateData = new HashMap<>(Map.of( + "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), + "ALIASES", Map.of("ALIASES", aliasesReadableKVState), + "NFTS", Map.of("NFTS", nftReadableKVState), + "TOKENS", Map.of("TOKENS", tokenReadableKVState), + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + + // Add service using the mock data source + mirrorNodeState = mirrorNodeState + .addService(FileService.NAME, fileStateData) + .addService(ContractService.NAME, contractStateData) + .addService(TokenService.NAME, tokenStateData); + } + + @Test + void testAddService() { + assertThat(mirrorNodeState.getReadableStates("NEW").contains("FILES")).isFalse(); + final var newState = + mirrorNodeState.addService("NEW", new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState)))); + assertThat(newState.getReadableStates("NEW").contains("FILES")).isTrue(); + } + + @Test + void testRemoveService() { + final var testStates = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final var newState = mirrorNodeState.addService("NEW", testStates); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isTrue(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + newState.removeServiceState("NEW", "BYTECODE"); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isFalse(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + } + @Test void testGetReadableStatesForFileService() { final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); - assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(new ConcurrentHashMap<>( + Map.of("FILES", new MapReadableKVState("FILES", Map.of("FILES", fileReadableKVState)))))); } @Test @@ -76,7 +124,10 @@ void testGetReadableStatesForContractService() { final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( - "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + "BYTECODE", + new MapReadableKVState("BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState)), + "STORAGE", + new MapReadableKVState("STORAGE", Map.of("STORAGE", contractStorageReadableKVState))))); } @Test @@ -85,17 +136,17 @@ void testGetReadableStatesForTokenService() { assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( "ACCOUNTS", - accountReadableKVState, + new MapReadableKVState("ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState)), "PENDING_AIRDROPS", - airdropsReadableKVState, + new MapReadableKVState("PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState)), "ALIASES", - aliasesReadableKVState, + new MapReadableKVState("ALIASES", Map.of("ALIASES", aliasesReadableKVState)), "NFTS", - nftReadableKVState, + new MapReadableKVState("NFTS", Map.of("NFTS", nftReadableKVState)), "TOKENS", - tokenReadableKVState, + new MapReadableKVState("TOKENS", Map.of("TOKENS", tokenReadableKVState)), "TOKEN_RELS", - tokenRelationshipReadableKVState))); + new MapReadableKVState("TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))))); } @Test @@ -108,9 +159,11 @@ void testGetWritableStatesForFileService() { when(fileReadableKVState.getStateKey()).thenReturn("FILES"); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( - "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + "FILES", + new MapWritableKVState<>(fileReadableKVState.getStateKey(), readableStates.get("FILES"))))); } @Test @@ -119,14 +172,15 @@ void testGetWritableStatesForContractService() { when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", new MapWritableKVState<>( - contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + contractBytecodeReadableKVState.getStateKey(), readableStates.get("BYTECODE")), "STORAGE", new MapWritableKVState<>( - contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + contractStorageReadableKVState.getStateKey(), readableStates.get("STORAGE"))))); } @Test @@ -139,21 +193,23 @@ void testGetWritableStatesForTokenService() { when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + new MapWritableKVState<>(accountReadableKVState.getStateKey(), readableStates.get("ACCOUNTS")), "PENDING_AIRDROPS", - new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + new MapWritableKVState<>( + airdropsReadableKVState.getStateKey(), readableStates.get("PENDING_AIRDROPS")), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), readableStates.get("ALIASES")), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + new MapWritableKVState<>(nftReadableKVState.getStateKey(), readableStates.get("NFTS")), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), readableStates.get("TOKENS")), "TOKEN_RELS", new MapWritableKVState<>( - tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + tokenRelationshipReadableKVState.getStateKey(), readableStates.get("TOKEN_RELS"))))); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java new file mode 100644 index 0000000000..fdc51f25cd --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableKVStateTest { + + private MapReadableKVState mapReadableKVState; + + private Map accountMap; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + accountMap = Map.of(accountID, account); + mapReadableKVState = new MapReadableKVState<>("ACCOUNTS", accountMap); + } + + @Test + void testReadFromDataSource() { + assertThat(mapReadableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testReadFromDataSourceNotExisting() { + assertThat(mapReadableKVState.readFromDataSource( + AccountID.newBuilder().accountNum(1L).build())) + .isNull(); + } + + @Test + void testIterateFromDataSource() { + assertThat(mapReadableKVState.iterateFromDataSource().hasNext()).isTrue(); + assertThat(mapReadableKVState.iterateFromDataSource().next()).isEqualTo(accountID); + } + + @Test + void testSize() { + assertThat(mapReadableKVState.size()).isEqualTo(1L); + final var accountID1 = AccountID.newBuilder().accountNum(1L).build(); + final var accountID2 = AccountID.newBuilder().accountNum(2L).build(); + final var mapReadableKVStateBigger = new MapReadableKVState<>( + "ACCOUNTS", + Map.of( + accountID1, + Account.newBuilder().accountId(accountID1).build(), + accountID2, + Account.newBuilder().accountId(accountID2).build())); + assertThat(mapReadableKVStateBigger.size()).isEqualTo(2L); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapReadableKVState).isEqualTo(mapReadableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapReadableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); + assertThat(mapReadableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).hasSameHashCodeAs(other); + } +} From dd72bd072450390c862983937d0865337934a277 Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Tue, 5 Nov 2024 16:27:45 +0200 Subject: [PATCH 06/33] fix: Uncomment needed lines in schema registry after state changes Signed-off-by: Kristiyan Selveliev --- .../mirror/web3/state/components/SchemaRegistryImpl.java | 7 +++---- .../web3/state/components/SchemaRegistryImplTest.java | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java index 1277c572a9..1af477e4e2 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java @@ -126,9 +126,9 @@ public void migrate( if (writableStates instanceof MapWritableStates mws) { mws.commit(); } - // TODO uncomment + // And finally we can remove any states we need to remove - // schema.statesToRemove().forEach(stateKey -> state.removeServiceState(serviceName, stateKey)); + schema.statesToRemove().forEach(stateKey -> state.removeServiceState(serviceName, stateKey)); } } @@ -210,8 +210,7 @@ private RedefinedWritableStates applyStateDefinitions( } }); - // TODO uncomment - // state.addService(serviceName, stateDataSources); + state.addService(serviceName, stateDataSources); final var statesToRemove = schema.statesToRemove(); final var writableStates = state.getWritableStates(serviceName); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java index 5518331248..d5e76c5f45 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java @@ -149,8 +149,7 @@ void testMigrateWithStateDefinitions() { verify(mirrorNodeState, times(1)).getReadableStates(serviceName); verify(schema).migrate(any()); verify(writableStates, times(1)).commit(); - // TODO uncomment - // verify(mirrorNodeState, times(1)).addService(any(), any()); + verify(mirrorNodeState, times(1)).addService(any(), any()); } @Test From c2702b7ad2697a0161ae88e9bafec3783ef2c22f Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Wed, 6 Nov 2024 16:31:49 +0200 Subject: [PATCH 07/33] fix: Refactor migrator and networkInfo for successful state initialization Signed-off-by: Kristiyan Selveliev --- .../state/components/NetworkInfoImpl.java | 2 +- .../state/components/ServiceMigratorImpl.java | 57 ++++--------- .../state/components/NetworkInfoImplTest.java | 3 +- .../components/ServiceMigratorImplTest.java | 84 +------------------ 4 files changed, 20 insertions(+), 126 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/NetworkInfoImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/NetworkInfoImpl.java index eaa545279c..944a237dbd 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/NetworkInfoImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/NetworkInfoImpl.java @@ -50,7 +50,7 @@ public SelfNodeInfo selfNodeInfo() { @Nonnull @Override public List addressBook() { - throw new UnsupportedOperationException("Address book is not supported."); + return List.of(mockSelfNodeInfo()); } @Nullable diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java index 4f371a34e6..e180479ea7 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java @@ -20,9 +20,7 @@ import com.hedera.hapi.block.stream.output.StateChanges.Builder; import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.mirror.web3.state.MirrorNodeState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.hedera.node.app.services.ServiceMigrator; import com.hedera.node.app.services.ServicesRegistry; import com.hedera.node.config.data.HederaConfig; @@ -36,15 +34,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; public class ServiceMigratorImpl implements ServiceMigrator { - public static final String NAME_OF_ENTITY_ID_SERVICE = "EntityIdService"; - public static final String NAME_OF_ENTITY_ID_SINGLETON = "ENTITY_ID"; - @Override public List doMigrations( @Nonnull State state, @@ -72,46 +66,23 @@ public List doMigrations( final AtomicLong prevEntityNum = new AtomicLong(config.getConfigData(HederaConfig.class).firstUserEntity() - 1); final Map sharedValues = new HashMap<>(); - final var entityIdRegistration = registry.registrations().stream() - .filter(service -> - NAME_OF_ENTITY_ID_SERVICE.equals(service.service().getServiceName())) - .findFirst() - .orElseThrow(); - if (!(entityIdRegistration.registry() instanceof SchemaRegistryImpl entityIdRegistry)) { - throw new IllegalArgumentException("Can only be used with SchemaRegistryImpl instances"); - } final var deserializedPbjVersion = Optional.ofNullable(previousVersion) .map(SoftwareVersion::getPbjSemanticVersion) .orElse(null); - entityIdRegistry.migrate( - NAME_OF_ENTITY_ID_SERVICE, - mirrorNodeState, - deserializedPbjVersion, - networkInfo, - config, - sharedValues, - prevEntityNum); - registry.registrations().stream() - .filter(r -> !Objects.equals(entityIdRegistration, r)) - .forEach(registration -> { - if (!(registration.registry() instanceof SchemaRegistryImpl schemaRegistry)) { - throw new IllegalArgumentException("Can only be used with SchemaRegistryImpl instances"); - } - schemaRegistry.migrate( - registration.serviceName(), - mirrorNodeState, - deserializedPbjVersion, - networkInfo, - config, - sharedValues, - prevEntityNum); - }); - final var entityIdWritableStates = mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE); - if (!(entityIdWritableStates instanceof MapWritableStates mapWritableStates)) { - throw new IllegalArgumentException("Can only be used with MapWritableStates instances"); - } - mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON).put(new EntityNumber(prevEntityNum.get())); - mapWritableStates.commit(); + + registry.registrations().stream().forEach(registration -> { + if (!(registration.registry() instanceof SchemaRegistryImpl schemaRegistry)) { + throw new IllegalArgumentException("Can only be used with SchemaRegistryImpl instances"); + } + schemaRegistry.migrate( + registration.serviceName(), + mirrorNodeState, + deserializedPbjVersion, + networkInfo, + config, + sharedValues, + prevEntityNum); + }); return List.of(); } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/NetworkInfoImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/NetworkInfoImplTest.java index 9b7c8dfd11..135a8069c4 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/NetworkInfoImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/NetworkInfoImplTest.java @@ -55,8 +55,7 @@ void testSelfNodeInfo() { @Test void testAddressBook() { - final var exception = assertThrows(UnsupportedOperationException.class, () -> networkInfoImpl.addressBook()); - assertThat(exception.getMessage()).isEqualTo("Address book is not supported."); + assertThat(networkInfoImpl.addressBook()).isNotEmpty(); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java index 1e1be3ac7a..eced784614 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java @@ -16,18 +16,13 @@ package com.hedera.mirror.web3.state.components; -import static com.hedera.mirror.web3.state.components.ServiceMigratorImpl.NAME_OF_ENTITY_ID_SERVICE; -import static com.hedera.mirror.web3.state.components.ServiceMigratorImpl.NAME_OF_ENTITY_ID_SINGLETON; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.hedera.mirror.web3.state.MirrorNodeState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.services.ServicesRegistry; @@ -37,12 +32,9 @@ import com.hedera.node.config.data.VersionConfig; import com.swirlds.metrics.api.Metrics; import com.swirlds.state.State; -import com.swirlds.state.spi.EmptyWritableStates; import com.swirlds.state.spi.SchemaRegistry; import com.swirlds.state.spi.Service; -import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.info.NetworkInfo; -import java.util.NoSuchElementException; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -53,9 +45,6 @@ @ExtendWith(MockitoExtension.class) class ServiceMigratorImplTest { - @Mock - private MapWritableStates mapWritableStates; - @Mock private Metrics metrics; @@ -80,9 +69,6 @@ class ServiceMigratorImplTest { @Mock private State mockState; - @Mock - private WritableSingletonState writableSingletonState; - private VersionedConfiguration bootstrapConfig; private ServiceMigratorImpl serviceMigrator; @@ -106,15 +92,10 @@ void testCreationVersionOfWithInvalidState() { @Test void doMigrations() { final var mockServiceRegistration = mock(Registration.class); - final var mockService = mock(Service.class); - when(mockServiceRegistration.service()).thenReturn(mockService); - when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); when(mockServiceRegistration.registry()).thenReturn(schemaRegistry); - when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mapWritableStates); - when(mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON)).thenReturn(writableSingletonState); - serviceMigrator.doMigrations( + assertDoesNotThrow(() -> serviceMigrator.doMigrations( mirrorNodeState, servicesRegistry, null, @@ -122,16 +103,12 @@ void doMigrations() { bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion()), new ConfigProviderImpl().getConfiguration(), networkInfo, - metrics); - - verify(mapWritableStates, times(1)).commit(); + metrics)); } @Test void doMigrationsWithMultipleRegistrations() { Service service1 = mock(Service.class); - when(service1.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); - Service service2 = mock(Service.class); when(service2.getServiceName()).thenReturn("testService2"); @@ -141,10 +118,7 @@ void doMigrationsWithMultipleRegistrations() { Registration registration1 = new Registration(service1, registry1); Registration registration2 = new Registration(service2, registry2); when(servicesRegistry.registrations()).thenReturn(Set.of(registration1, registration2)); - when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mapWritableStates); - when(mapWritableStates.getSingleton(NAME_OF_ENTITY_ID_SINGLETON)).thenReturn(writableSingletonState); - - serviceMigrator.doMigrations( + assertDoesNotThrow(() -> serviceMigrator.doMigrations( mirrorNodeState, servicesRegistry, null, @@ -152,16 +126,12 @@ void doMigrationsWithMultipleRegistrations() { bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion()), new ConfigProviderImpl().getConfiguration(), networkInfo, - metrics); - - verify(mapWritableStates, times(1)).commit(); + metrics)); } @Test void doMigrationsWithMultipleRegistrationsWithInvalidSchemaRegistry() { Service service1 = mock(Service.class); - when(service1.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); - Service service2 = mock(Service.class); SchemaRegistry registry1 = mock(SchemaRegistryImpl.class); @@ -188,22 +158,6 @@ void doMigrationsWithMultipleRegistrationsWithInvalidSchemaRegistry() { assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); } - @Test - void doMigrationsInvalidRegistrations() { - assertThrows( - NoSuchElementException.class, - () -> serviceMigrator.doMigrations( - mirrorNodeState, - servicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); - } - @Test void doMigrationsInvalidState() { var exception = assertThrows( @@ -243,9 +197,6 @@ void doMigrationsInvalidServicesRegistry() { @Test void doMigrationsInvalidSchemaRegistry() { final var mockServiceRegistration = mock(Registration.class); - final var mockService = mock(Service.class); - when(mockServiceRegistration.service()).thenReturn(mockService); - when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); when(mockServiceRegistration.registry()).thenReturn(mockSchemaRegistry); var exception = assertThrows( @@ -263,31 +214,4 @@ void doMigrationsInvalidSchemaRegistry() { assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); } - - @Test - void doMigrationsInvalidWritableStates() { - final var mockServiceRegistration = mock(Registration.class); - final var mockService = mock(Service.class); - when(mockServiceRegistration.service()).thenReturn(mockService); - when(mockService.getServiceName()).thenReturn(NAME_OF_ENTITY_ID_SERVICE); - when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); - when(mockServiceRegistration.registry()).thenReturn(schemaRegistry); - final var mockMapWritableStates = mock(EmptyWritableStates.class); - when(mirrorNodeState.getWritableStates(NAME_OF_ENTITY_ID_SERVICE)).thenReturn(mockMapWritableStates); - - var exception = assertThrows( - IllegalArgumentException.class, - () -> serviceMigrator.doMigrations( - mirrorNodeState, - servicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); - - assertThat(exception.getMessage()).isEqualTo("Can only be used with MapWritableStates instances"); - } } From 5dded46d5e2f9417680242275acfd01993cec6b7 Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Wed, 6 Nov 2024 17:02:04 +0200 Subject: [PATCH 08/33] fix: Addressing review comments Signed-off-by: Kristiyan Selveliev --- .../state/components/SchemaRegistryImpl.java | 22 +++++++++---------- .../state/components/ServiceMigratorImpl.java | 2 ++ .../components/ServicesRegistryImpl.java | 6 +---- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java index 1af477e4e2..0449f86f10 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java @@ -46,17 +46,15 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import lombok.Getter; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor public class SchemaRegistryImpl implements SchemaRegistry { public static final SemanticVersion CURRENT_VERSION = new SemanticVersion(0, 47, 0, "SNAPSHOT", ""); private final SchemaApplications schemaApplications; - public SchemaRegistryImpl(@Nonnull final SchemaApplications schemaApplications) { - this.schemaApplications = schemaApplications; - } - /** * The ordered set of all schemas registered by the service */ @@ -186,14 +184,6 @@ public Map sharedValues() { }; } - /** - * Encapsulates the writable states before and after applying a schema's state definitions. - * - * @param beforeStates the writable states before applying the schema's state definitions - * @param afterStates the writable states after applying the schema's state definitions - */ - private record RedefinedWritableStates(WritableStates beforeStates, WritableStates afterStates) {} - private RedefinedWritableStates applyStateDefinitions( @Nonnull final String serviceName, @Nonnull final Schema schema, @@ -219,4 +209,12 @@ private RedefinedWritableStates applyStateDefinitions( final var newStates = new FilteredWritableStates(writableStates, remainingStates); return new RedefinedWritableStates(writableStates, newStates); } + + /** + * Encapsulates the writable states before and after applying a schema's state definitions. + * + * @param beforeStates the writable states before applying the schema's state definitions + * @param afterStates the writable states after applying the schema's state definitions + */ + private record RedefinedWritableStates(WritableStates beforeStates, WritableStates afterStates) {} } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java index e180479ea7..77b4b0c01e 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServiceMigratorImpl.java @@ -31,12 +31,14 @@ import com.swirlds.state.spi.info.NetworkInfo; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.inject.Named; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +@Named public class ServiceMigratorImpl implements ServiceMigrator { @Override diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java index 1e2c2dac85..28967ac643 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/ServicesRegistryImpl.java @@ -29,11 +29,7 @@ @Named public class ServicesRegistryImpl implements ServicesRegistry { - private final SortedSet entries; - - public ServicesRegistryImpl() { - this.entries = new TreeSet<>(); - } + private final SortedSet entries = new TreeSet<>(); @Nonnull @Override From cceb9e565dcd0a399571942d83cbc62814b92a32 Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Fri, 8 Nov 2024 17:15:11 +0200 Subject: [PATCH 09/33] fix: Addressing review comments Signed-off-by: Kristiyan Selveliev --- .../web3/state/components/SchemaRegistryImpl.java | 1 + .../state/components/SchemaRegistryImplTest.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java index 0449f86f10..bbcb1acce4 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java @@ -63,6 +63,7 @@ public class SchemaRegistryImpl implements SchemaRegistry { @Override public SchemaRegistry register(@Nonnull Schema schema) { + schemas.remove(schema); schemas.add(schema); return this; } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java index d5e76c5f45..d00ba1d1b0 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java @@ -18,7 +18,11 @@ import static java.util.Collections.EMPTY_MAP; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.state.MirrorNodeState; @@ -47,6 +51,9 @@ @ExtendWith(MockitoExtension.class) class SchemaRegistryImplTest { + private final String serviceName = "testService"; + private final SemanticVersion previousVersion = new SemanticVersion(0, 46, 0, "", ""); + @Mock private MirrorNodeState mirrorNodeState; @@ -69,12 +76,8 @@ class SchemaRegistryImplTest { private Codec mockCodec; private Configuration config; - private SchemaRegistryImpl schemaRegistry; - private final String serviceName = "testService"; - private final SemanticVersion previousVersion = new SemanticVersion(0, 46, 0, "", ""); - @BeforeEach void initialize() { schemaRegistry = new SchemaRegistryImpl(schemaApplications); From b38d57d7dc4d39ef014bf9f2d45d8a3087b60ba3 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 4 Nov 2024 14:39:26 +0200 Subject: [PATCH 10/33] Implement MirrorNodeState Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 106 ++++++++++++ .../web3/state/utils/MapReadableStates.java | 109 ++++++++++++ .../web3/state/utils/MapWritableKVState.java | 86 +++++++++ .../web3/state/utils/MapWritableStates.java | 117 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 163 ++++++++++++++++++ .../state/utils/MapReadableStatesTest.java | 150 ++++++++++++++++ .../state/utils/MapWritableKVStateTest.java | 123 +++++++++++++ .../state/utils/MapWritableStatesTest.java | 120 +++++++++++++ 8 files changed, 974 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java new file mode 100644 index 0000000000..e3cdce4089 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Named +public class MirrorNodeState implements State { + private final Map> tokenReadableServiceStates = new HashMap<>(); + private final Map> contractReadableServiceStates = new HashMap<>(); + private final Map> fileReadableServiceStates = new HashMap<>(); + + private final Map readableStates = new ConcurrentHashMap<>(); + + public MirrorNodeState( + final AccountReadableKVState accountReadableKVState, + final AirdropsReadableKVState airdropsReadableKVState, + final AliasesReadableKVState aliasesReadableKVState, + final ContractBytecodeReadableKVState contractBytecodeReadableKVState, + final ContractStorageReadableKVState contractStorageReadableKVState, + final FileReadableKVState fileReadableKVState, + final NftReadableKVState nftReadableKVState, + final TokenReadableKVState tokenReadableKVState, + final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { + + tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); + tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); + tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); + tokenReadableServiceStates.put("NFTS", nftReadableKVState); + tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); + tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + + contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); + contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + + fileReadableServiceStates.put("FILES", fileReadableKVState); + } + + @Nonnull + @Override + public ReadableStates getReadableStates(@Nonnull String serviceName) { + return readableStates.computeIfAbsent(serviceName, s -> { + switch (s) { + case TokenService.NAME -> { + return new MapReadableStates(tokenReadableServiceStates); + } + case ContractService.NAME -> { + return new MapReadableStates(contractReadableServiceStates); + } + case FileService.NAME -> { + return new MapReadableStates(fileReadableServiceStates); + } + default -> { + return new MapReadableStates(Collections.emptyMap()); + } + } + }); + } + + @Nonnull + @Override + public WritableStates getWritableStates(@Nonnull String serviceName) { + return switch (serviceName) { + case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); + case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); + case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); + default -> new MapWritableStates(Collections.emptyMap()); + }; + } + + private Map getWritableStates(final Map> readableStates) { + final Map data = new HashMap<>(); + readableStates.forEach(((s, readableKVState) -> + data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); + return data; + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java new file mode 100644 index 0000000000..6b354eb681 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapReadableStates implements ReadableStates { + + private final Map states; + + public MapReadableStates(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Nonnull + @Override + public ReadableKVState get(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof ReadableKVState)) { + throw new IllegalArgumentException("State is not an instance of ReadableKVState: " + stateKey); + } + + return (ReadableKVState) state; + } + + @Nonnull + @Override + public ReadableSingletonState getSingleton(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof ReadableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of ReadableSingletonState: " + stateKey); + } + + return (ReadableSingletonState) state; + } + + @Nonnull + @Override + public ReadableQueueState getQueue(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof ReadableQueueState)) { + throw new IllegalArgumentException("State is not an instance of ReadableQueueState: " + stateKey); + } + + return (ReadableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapReadableStates that = (MapReadableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java new file mode 100644 index 0000000000..b63ca2ab1a --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +public class MapWritableKVState extends WritableKVStateBase { + + private final ReadableKVState readableKVState; + + public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { + super(stateKey); + this.readableKVState = readableKVState; + } + + // The readable state's values are immutable, hence callers would not be able + // to modify the readable state's objects. + @Override + protected V getForModifyFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Override + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + put(key, value); // put only in memory + } + + @Override + protected void removeFromDataSource(@Nonnull K key) { + remove(key); // remove only in memory + } + + @Override + protected long sizeOfDataSource() { + return readableKVState.size(); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public void commit() { + reset(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) + && Objects.equals(readableKVState, that.readableKVState); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), readableKVState); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java new file mode 100644 index 0000000000..2fa19f006d --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static java.util.Objects.requireNonNull; + +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapWritableStates implements WritableStates, CommittableWritableStates { + + private final Map states; + + public MapWritableStates(Map states) { + this.states = states; + } + + @Nonnull + @Override + public WritableKVState get(@Nonnull String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof WritableKVState)) { + throw new IllegalArgumentException("State is not an instance of WritableKVState: " + stateKey); + } + + return (WritableKVState) state; + } + + @Nonnull + @Override + public WritableSingletonState getSingleton(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof WritableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of WritableSingletonState: " + stateKey); + } + + return (WritableSingletonState) state; + } + + @Nonnull + @Override + public WritableQueueState getQueue(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof WritableQueueState)) { + throw new IllegalArgumentException("State is not an instance of WritableQueueState: " + stateKey); + } + + return (WritableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWritableStates that = (MapWritableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } + + @Override + public void commit() { + // Empty body because we don't want to persist any changes to DB. + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java new file mode 100644 index 0000000000..6bcd187535 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MirrorNodeStateTest { + + @InjectMocks + private MirrorNodeState mirrorNodeState; + + @Mock + private AccountReadableKVState accountReadableKVState; + + @Mock + private AirdropsReadableKVState airdropsReadableKVState; + + @Mock + private AliasesReadableKVState aliasesReadableKVState; + + @Mock + private ContractBytecodeReadableKVState contractBytecodeReadableKVState; + + @Mock + private ContractStorageReadableKVState contractStorageReadableKVState; + + @Mock + private FileReadableKVState fileReadableKVState; + + @Mock + private NftReadableKVState nftReadableKVState; + + @Mock + private TokenReadableKVState tokenReadableKVState; + + @Mock + private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + + @Test + void testGetReadableStatesForFileService() { + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); + assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + } + + @Test + void testGetReadableStatesForContractService() { + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + } + + @Test + void testGetReadableStatesForTokenService() { + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "ACCOUNTS", + accountReadableKVState, + "PENDING_AIRDROPS", + airdropsReadableKVState, + "ALIASES", + aliasesReadableKVState, + "NFTS", + nftReadableKVState, + "TOKENS", + tokenReadableKVState, + "TOKEN_RELS", + tokenRelationshipReadableKVState))); + } + + @Test + void testGetReadableStateForUnsupportedService() { + assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); + } + + @Test + void testGetWritableStatesForFileService() { + when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + } + + @Test + void testGetWritableStatesForContractService() { + when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); + when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + + final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "BYTECODE", + new MapWritableKVState<>( + contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + "STORAGE", + new MapWritableKVState<>( + contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + } + + @Test + void testGetWritableStatesForTokenService() { + when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); + when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); + when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); + when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); + when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); + + final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "ACCOUNTS", + new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + "PENDING_AIRDROPS", + new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + "ALIASES", + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + "NFTS", + new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + "TOKENS", + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + "TOKEN_RELS", + new MapWritableKVState<>( + tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + } + + @Test + void testGetWritableStateForUnsupportedService() { + assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java new file mode 100644 index 0000000000..dde0939492 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableStatesTest { + + private MapReadableStates states; + + @Mock + private ReadableKVState kvStateMock; + + @Mock + private ReadableSingletonState singletonStateMock; + + @Mock + private ReadableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentType() { + assertThat(states).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableStates other = new MapReadableStates(Map.of(KV_STATE_KEY, kvStateMock)); + assertThat(states).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java new file mode 100644 index 0000000000..79813f24a0 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.swirlds.state.spi.ReadableKVState; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableKVStateTest { + + private MapWritableKVState mapWritableKVState; + + @Mock + private ReadableKVState readableKVState; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + } + + @Test + void testGetForModifyFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testDataSourceSizeIsZero() { + assertThat(mapWritableKVState.sizeOfDataSource()).isZero(); + } + + @Test + void testReadFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testIterateFromDataSourceReturnsEmptyIterator() { + assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + } + + @Test + void testPutIntoDataSource() { + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + } + + @Test + void testRemoveFromDataSource() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.removeFromDataSource(accountID); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testCommit() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.commit(); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapWritableKVState).isEqualTo(mapWritableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapWritableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java new file mode 100644 index 0000000000..9ab6defe74 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableStatesTest { + + private MapWritableStates states; + + @Mock + private WritableKVState kvStateMock; + + @Mock + private WritableSingletonState singletonStateMock; + + @Mock + private WritableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } +} From f1e940e02545350e9e9b6c235767fc8d466c67ed Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:01:56 +0200 Subject: [PATCH 11/33] Add dynamic service configuration Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 113 ++++++++++-------- .../web3/state/utils/MapReadableKVState.java | 81 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 90 +++++++++++--- .../state/utils/MapReadableKVStateTest.java | 109 +++++++++++++++++ 4 files changed, 323 insertions(+), 70 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index e3cdce4089..85d1971c00 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,91 +16,98 @@ package com.hedera.mirror.web3.state; +import static java.util.Objects.requireNonNull; + +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; -import com.hedera.node.app.service.contract.ContractService; -import com.hedera.node.app.service.file.FileService; -import com.hedera.node.app.service.token.TokenService; import com.swirlds.state.State; -import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.EmptyReadableStates; +import com.swirlds.state.spi.EmptyWritableStates; import com.swirlds.state.spi.ReadableStates; import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@SuppressWarnings({"rawtypes", "unchecked"}) @Named public class MirrorNodeState implements State { - private final Map> tokenReadableServiceStates = new HashMap<>(); - private final Map> contractReadableServiceStates = new HashMap<>(); - private final Map> fileReadableServiceStates = new HashMap<>(); private final Map readableStates = new ConcurrentHashMap<>(); + // Key is Service, value is Map of state name to state datasource + private final Map> states = new ConcurrentHashMap<>(); - public MirrorNodeState( - final AccountReadableKVState accountReadableKVState, - final AirdropsReadableKVState airdropsReadableKVState, - final AliasesReadableKVState aliasesReadableKVState, - final ContractBytecodeReadableKVState contractBytecodeReadableKVState, - final ContractStorageReadableKVState contractStorageReadableKVState, - final FileReadableKVState fileReadableKVState, - final NftReadableKVState nftReadableKVState, - final TokenReadableKVState tokenReadableKVState, - final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { - - tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); - tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); - tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); - tokenReadableServiceStates.put("NFTS", nftReadableKVState); - tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); - tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map dataSources) { + final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>()); + dataSources.forEach((k, b) -> { + if (!serviceStates.containsKey(k)) { + serviceStates.put(k, b); + } + }); - contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); - contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + // Purge any readable states whose state definitions are now stale, + // since they don't include the new data sources we just added + readableStates.remove(serviceName); + return this; + } - fileReadableServiceStates.put("FILES", fileReadableKVState); + /** + * Removes the state with the given key for the service with the given name. + * + * @param serviceName the name of the service + * @param stateKey the key of the state + */ + public void removeServiceState(@NonNull final String serviceName, @NonNull final String stateKey) { + requireNonNull(serviceName); + requireNonNull(stateKey); + this.states.computeIfPresent(serviceName, (k, v) -> { + v.remove(stateKey); + readableStates.remove(serviceName); // Remove the service so that its states will be repopulated. + return v; + }); } @Nonnull @Override public ReadableStates getReadableStates(@Nonnull String serviceName) { return readableStates.computeIfAbsent(serviceName, s -> { - switch (s) { - case TokenService.NAME -> { - return new MapReadableStates(tokenReadableServiceStates); - } - case ContractService.NAME -> { - return new MapReadableStates(contractReadableServiceStates); - } - case FileService.NAME -> { - return new MapReadableStates(fileReadableServiceStates); - } - default -> { - return new MapReadableStates(Collections.emptyMap()); + final var serviceStates = this.states.get(s); + if (serviceStates == null) { + return new EmptyReadableStates(); + } + final Map states = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map map) { + states.put(stateName, new MapReadableKVState(stateName, map)); } } + return new MapReadableStates(states); }); } @Nonnull @Override public WritableStates getWritableStates(@Nonnull String serviceName) { - return switch (serviceName) { - case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); - case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); - case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); - default -> new MapWritableStates(Collections.emptyMap()); - }; - } + final var serviceStates = states.get(serviceName); + if (serviceStates == null) { + return new EmptyWritableStates(); + } - private Map getWritableStates(final Map> readableStates) { - final Map data = new HashMap<>(); - readableStates.forEach(((s, readableKVState) -> - data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); - return data; + final Map data = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map) { + final var readableState = getReadableStates(serviceName).get(stateName); + data.put(stateName, new MapWritableKVState<>(stateName, readableState)); + } + } + return new MapWritableStates(data); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java new file mode 100644 index 0000000000..3d37e99211 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023-2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * A simple implementation of {@link ReadableKVState} backed by a + * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by + * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy + * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in + * some strange case, or in some other way work with the backing map directly. + * + * @param The key type + * @param The value type + */ +public class MapReadableKVState extends ReadableKVStateBase { + /** Represents the backing storage for this state */ + private final Map backingStore; + + /** + * Create an instance using the given map as the backing store. This is useful when you want to + * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public MapReadableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return backingStore.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return backingStore.keySet().iterator(); + } + + @Override + public long size() { + return backingStore.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapReadableKVState that = (MapReadableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), backingStore); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 6bcd187535..67686501d1 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -19,13 +19,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.when; +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -65,10 +69,54 @@ class MirrorNodeStateTest { @Mock private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + @BeforeEach + void setup() { + final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map contractStateData = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final Map tokenStateData = new HashMap<>(Map.of( + "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), + "ALIASES", Map.of("ALIASES", aliasesReadableKVState), + "NFTS", Map.of("NFTS", nftReadableKVState), + "TOKENS", Map.of("TOKENS", tokenReadableKVState), + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + + // Add service using the mock data source + mirrorNodeState = mirrorNodeState + .addService(FileService.NAME, fileStateData) + .addService(ContractService.NAME, contractStateData) + .addService(TokenService.NAME, tokenStateData); + } + + @Test + void testAddService() { + assertThat(mirrorNodeState.getReadableStates("NEW").contains("FILES")).isFalse(); + final var newState = + mirrorNodeState.addService("NEW", new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState)))); + assertThat(newState.getReadableStates("NEW").contains("FILES")).isTrue(); + } + + @Test + void testRemoveService() { + final var testStates = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final var newState = mirrorNodeState.addService("NEW", testStates); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isTrue(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + newState.removeServiceState("NEW", "BYTECODE"); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isFalse(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + } + @Test void testGetReadableStatesForFileService() { final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); - assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(new ConcurrentHashMap<>( + Map.of("FILES", new MapReadableKVState("FILES", Map.of("FILES", fileReadableKVState)))))); } @Test @@ -76,7 +124,10 @@ void testGetReadableStatesForContractService() { final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( - "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + "BYTECODE", + new MapReadableKVState("BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState)), + "STORAGE", + new MapReadableKVState("STORAGE", Map.of("STORAGE", contractStorageReadableKVState))))); } @Test @@ -85,17 +136,17 @@ void testGetReadableStatesForTokenService() { assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( "ACCOUNTS", - accountReadableKVState, + new MapReadableKVState("ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState)), "PENDING_AIRDROPS", - airdropsReadableKVState, + new MapReadableKVState("PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState)), "ALIASES", - aliasesReadableKVState, + new MapReadableKVState("ALIASES", Map.of("ALIASES", aliasesReadableKVState)), "NFTS", - nftReadableKVState, + new MapReadableKVState("NFTS", Map.of("NFTS", nftReadableKVState)), "TOKENS", - tokenReadableKVState, + new MapReadableKVState("TOKENS", Map.of("TOKENS", tokenReadableKVState)), "TOKEN_RELS", - tokenRelationshipReadableKVState))); + new MapReadableKVState("TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))))); } @Test @@ -108,9 +159,11 @@ void testGetWritableStatesForFileService() { when(fileReadableKVState.getStateKey()).thenReturn("FILES"); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( - "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + "FILES", + new MapWritableKVState<>(fileReadableKVState.getStateKey(), readableStates.get("FILES"))))); } @Test @@ -119,14 +172,15 @@ void testGetWritableStatesForContractService() { when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", new MapWritableKVState<>( - contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + contractBytecodeReadableKVState.getStateKey(), readableStates.get("BYTECODE")), "STORAGE", new MapWritableKVState<>( - contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + contractStorageReadableKVState.getStateKey(), readableStates.get("STORAGE"))))); } @Test @@ -139,21 +193,23 @@ void testGetWritableStatesForTokenService() { when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + new MapWritableKVState<>(accountReadableKVState.getStateKey(), readableStates.get("ACCOUNTS")), "PENDING_AIRDROPS", - new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + new MapWritableKVState<>( + airdropsReadableKVState.getStateKey(), readableStates.get("PENDING_AIRDROPS")), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), readableStates.get("ALIASES")), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + new MapWritableKVState<>(nftReadableKVState.getStateKey(), readableStates.get("NFTS")), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), readableStates.get("TOKENS")), "TOKEN_RELS", new MapWritableKVState<>( - tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + tokenRelationshipReadableKVState.getStateKey(), readableStates.get("TOKEN_RELS"))))); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java new file mode 100644 index 0000000000..fdc51f25cd --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableKVStateTest { + + private MapReadableKVState mapReadableKVState; + + private Map accountMap; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + accountMap = Map.of(accountID, account); + mapReadableKVState = new MapReadableKVState<>("ACCOUNTS", accountMap); + } + + @Test + void testReadFromDataSource() { + assertThat(mapReadableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testReadFromDataSourceNotExisting() { + assertThat(mapReadableKVState.readFromDataSource( + AccountID.newBuilder().accountNum(1L).build())) + .isNull(); + } + + @Test + void testIterateFromDataSource() { + assertThat(mapReadableKVState.iterateFromDataSource().hasNext()).isTrue(); + assertThat(mapReadableKVState.iterateFromDataSource().next()).isEqualTo(accountID); + } + + @Test + void testSize() { + assertThat(mapReadableKVState.size()).isEqualTo(1L); + final var accountID1 = AccountID.newBuilder().accountNum(1L).build(); + final var accountID2 = AccountID.newBuilder().accountNum(2L).build(); + final var mapReadableKVStateBigger = new MapReadableKVState<>( + "ACCOUNTS", + Map.of( + accountID1, + Account.newBuilder().accountId(accountID1).build(), + accountID2, + Account.newBuilder().accountId(accountID2).build())); + assertThat(mapReadableKVStateBigger.size()).isEqualTo(2L); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapReadableKVState).isEqualTo(mapReadableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapReadableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); + assertThat(mapReadableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).hasSameHashCodeAs(other); + } +} From ea49673df9f3b6202aa02c13fad66dbabaeb3c7a Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:21:51 +0200 Subject: [PATCH 12/33] Fix code smell Signed-off-by: Bilyana Gospodinova --- .../java/com/hedera/mirror/web3/state/MirrorNodeState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 85d1971c00..92e3ee78b7 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -79,15 +79,15 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { if (serviceStates == null) { return new EmptyReadableStates(); } - final Map states = new ConcurrentHashMap<>(); + final Map data = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); if (state instanceof Map map) { - states.put(stateName, new MapReadableKVState(stateName, map)); + data.put(stateName, new MapReadableKVState(stateName, map)); } } - return new MapReadableStates(states); + return new MapReadableStates(data); }); } From 2b598bfde65bdac6fe465245cc8d97503aea9d3c Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:51:04 +0200 Subject: [PATCH 13/33] Increase code coverage Signed-off-by: Bilyana Gospodinova --- .../state/utils/MapReadableKVStateTest.java | 14 +++++++++++- .../state/utils/MapReadableStatesTest.java | 5 +++++ .../state/utils/MapWritableKVStateTest.java | 16 +++++++++++++- .../state/utils/MapWritableStatesTest.java | 22 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java index fdc51f25cd..eb4f27a2bb 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java @@ -89,6 +89,11 @@ void testEqualsDifferentType() { assertThat(mapReadableKVState).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(mapReadableKVState).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); @@ -96,11 +101,18 @@ void testEqualsSameValues() { } @Test - void testEqualsDifferentValues() { + void testEqualsDifferentKeys() { MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); assertThat(mapReadableKVState).isNotEqualTo(other); } + @Test + void testEqualsDifferentValues() { + final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMapOther); + assertThat(mapReadableKVState).isNotEqualTo(other); + } + @Test void testHashCode() { MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java index dde0939492..60984b5c9b 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -128,6 +128,11 @@ void testEqualsDifferentType() { assertThat(states).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(states).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapReadableStates other = new MapReadableStates( diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 79813f24a0..039edeed85 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -23,6 +23,7 @@ import com.hedera.hapi.node.state.token.Account; import com.swirlds.state.spi.ReadableKVState; import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -103,6 +104,11 @@ void testEqualsDifferentType() { assertThat(mapWritableKVState).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(mapWritableKVState).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); @@ -110,11 +116,19 @@ void testEqualsSameValues() { } @Test - void testEqualsDifferentValues() { + void testEqualsDifferentKeys() { MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); assertThat(mapWritableKVState).isNotEqualTo(other); } + @Test + void testEqualsDifferentValues() { + final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); + final var readableKVStateOther = new MapReadableKVState<>("ACCOUNTS", accountMapOther); + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVStateOther); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + @Test void testHashCode() { MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java index 9ab6defe74..931c0d67bd 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -117,4 +117,26 @@ void testStateKeysReturnsUnmodifiableSet() { Set keys = states.stateKeys(); assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentClass() { + assertThat(states).isNotEqualTo("other"); + } + + @Test + void testEqualsWithNull() { + assertThat(states).isNotEqualTo(null); + } + + @Test + void testHashCode() { + MapWritableStates other = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } } From f0d9d343b902a930a8b059cafacd9c73d8dca1e0 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Fri, 8 Nov 2024 11:09:14 +0200 Subject: [PATCH 14/33] sync with services more Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 166 +++++++++++++++--- .../state/utils/ListReadableQueueState.java | 56 ++++++ .../state/utils/ListWritableQueueState.java | 57 ++++++ .../web3/state/utils/MapWritableKVState.java | 71 ++++---- .../web3/state/utils/MapWritableStates.java | 34 +++- .../web3/state/MirrorNodeStateTest.java | 26 ++- .../state/utils/MapWritableKVStateTest.java | 35 +--- 7 files changed, 338 insertions(+), 107 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 92e3ee78b7..9b75c47261 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,30 +16,49 @@ package com.hedera.mirror.web3.state; +import static com.swirlds.state.StateChangeListener.StateType.MAP; +import static com.swirlds.state.StateChangeListener.StateType.QUEUE; +import static com.swirlds.state.StateChangeListener.StateType.SINGLETON; import static java.util.Objects.requireNonNull; +import com.hedera.mirror.web3.state.utils.ListReadableQueueState; +import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.swirlds.state.State; -import com.swirlds.state.spi.EmptyReadableStates; +import com.swirlds.state.StateChangeListener; import com.swirlds.state.spi.EmptyWritableStates; +import com.swirlds.state.spi.KVChangeListener; +import com.swirlds.state.spi.QueueChangeListener; +import com.swirlds.state.spi.ReadableSingletonStateBase; import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableQueueStateBase; +import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import jakarta.annotation.Nonnull; import jakarta.inject.Named; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; @SuppressWarnings({"rawtypes", "unchecked"}) @Named public class MirrorNodeState implements State { private final Map readableStates = new ConcurrentHashMap<>(); + private final Map writableStates = new ConcurrentHashMap<>(); + // Key is Service, value is Map of state name to state datasource private final Map> states = new ConcurrentHashMap<>(); + private final List listeners = new ArrayList<>(); public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map dataSources) { final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>()); @@ -52,6 +71,7 @@ public MirrorNodeState addService(@NonNull final String serviceName, @NonNull fi // Purge any readable states whose state definitions are now stale, // since they don't include the new data sources we just added readableStates.remove(serviceName); + writableStates.remove(serviceName); return this; } @@ -66,7 +86,6 @@ public void removeServiceState(@NonNull final String serviceName, @NonNull final requireNonNull(stateKey); this.states.computeIfPresent(serviceName, (k, v) -> { v.remove(stateKey); - readableStates.remove(serviceName); // Remove the service so that its states will be repopulated. return v; }); } @@ -77,37 +96,142 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { return readableStates.computeIfAbsent(serviceName, s -> { final var serviceStates = this.states.get(s); if (serviceStates == null) { - return new EmptyReadableStates(); + return new MapReadableStates(new HashMap<>()); } - final Map data = new ConcurrentHashMap<>(); + final Map states = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); - if (state instanceof Map map) { - data.put(stateName, new MapReadableKVState(stateName, map)); + if (state instanceof Queue queue) { + states.put(stateName, new ListReadableQueueState(stateName, queue)); + } else if (state instanceof Map map) { + states.put(stateName, new MapReadableKVState(stateName, map)); + } else if (state instanceof AtomicReference ref) { + states.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); } } - return new MapReadableStates(data); + return new MapReadableStates(states); }); } @Nonnull @Override public WritableStates getWritableStates(@Nonnull String serviceName) { - final var serviceStates = states.get(serviceName); - if (serviceStates == null) { - return new EmptyWritableStates(); - } - - final Map data = new ConcurrentHashMap<>(); - for (final var entry : serviceStates.entrySet()) { - final var stateName = entry.getKey(); - final var state = entry.getValue(); - if (state instanceof Map) { - final var readableState = getReadableStates(serviceName).get(stateName); - data.put(stateName, new MapWritableKVState<>(stateName, readableState)); + return writableStates.computeIfAbsent(serviceName, s -> { + final var serviceStates = states.get(s); + if (serviceStates == null) { + return new EmptyWritableStates(); + } + final Map data = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Queue queue) { + data.put( + stateName, + withAnyRegisteredListeners(serviceName, new ListWritableQueueState<>(stateName, queue))); + } else if (state instanceof Map map) { + data.put( + stateName, + withAnyRegisteredListeners(serviceName, new MapWritableKVState<>(stateName, map))); + } else if (state instanceof AtomicReference ref) { + data.put(stateName, withAnyRegisteredListeners(serviceName, stateName, ref)); + } + } + return new MapWritableStates(data, () -> readableStates.remove(serviceName)); + }); + } + + @Override + public void registerCommitListener(@NonNull final StateChangeListener listener) { + requireNonNull(listener); + listeners.add(listener); + } + + @Override + public void unregisterCommitListener(@NonNull final StateChangeListener listener) { + requireNonNull(listener); + listeners.remove(listener); + } + + public void commit() { + writableStates.values().forEach(writableStates -> { + if (writableStates instanceof MapWritableStates mapWritableStates) { + mapWritableStates.commit(); + } + }); + } + + private WritableSingletonStateBase withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final String stateKey, @NonNull final AtomicReference ref) { + final var state = new WritableSingletonStateBase<>(stateKey, ref::get, ref::set); + listeners.forEach(listener -> { + if (listener.stateTypes().contains(SINGLETON)) { + registerSingletonListener(serviceName, state, listener); } - } - return new MapWritableStates(data); + }); + return state; + } + + private MapWritableKVState withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final MapWritableKVState state) { + listeners.forEach(listener -> { + if (listener.stateTypes().contains(MAP)) { + registerKVListener(serviceName, state, listener); + } + }); + return state; + } + + private ListWritableQueueState withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final ListWritableQueueState state) { + listeners.forEach(listener -> { + if (listener.stateTypes().contains(QUEUE)) { + registerQueueListener(serviceName, state, listener); + } + }); + return state; + } + + private void registerSingletonListener( + @NonNull final String serviceName, + @NonNull final WritableSingletonStateBase singletonState, + @NonNull final StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, singletonState.getStateKey()); + singletonState.registerListener(value -> listener.singletonUpdateChange(stateId, value)); + } + + private void registerQueueListener( + @NonNull final String serviceName, + @NonNull final WritableQueueStateBase queueState, + @NonNull final StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, queueState.getStateKey()); + queueState.registerListener(new QueueChangeListener<>() { + @Override + public void queuePushChange(@NonNull final V value) { + listener.queuePushChange(stateId, value); + } + + @Override + public void queuePopChange() { + listener.queuePopChange(stateId); + } + }); + } + + private void registerKVListener( + @NonNull final String serviceName, WritableKVStateBase state, StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, state.getStateKey()); + state.registerListener(new KVChangeListener<>() { + @Override + public void mapUpdateChange(@NonNull final K key, @NonNull final V value) { + listener.mapUpdateChange(stateId, key, value); + } + + @Override + public void mapDeleteChange(@NonNull final K key) { + listener.mapDeleteChange(stateId, key); + } + }); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java new file mode 100644 index 0000000000..1a2633aafe --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableQueueStateBase; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; + +public class ListReadableQueueState extends ReadableQueueStateBase { + + /** Represents the backing storage for this state */ + private final Queue backingStore; + + /** + * Create an instance using the given Queue as the backing store. This is useful when you want to + * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public ListReadableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Nullable + @Override + protected E peekOnDataSource() { + return backingStore.peek(); + } + + @NonNull + @Override + protected Iterator iterateOnDataSource() { + return backingStore.iterator(); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java new file mode 100644 index 0000000000..26c50e0381 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.WritableQueueStateBase; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; + +public class ListWritableQueueState extends WritableQueueStateBase { + /** Represents the backing storage for this state */ + private final Queue backingStore; + + /** + * Create an instance using the given Queue as the backing store. This is useful when you want to + * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public ListWritableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Override + protected void addToDataSource(@Nonnull E element) { + backingStore.add(element); + } + + @Override + protected void removeFromDataSource() { + backingStore.remove(); + } + + @Nonnull + @Override + protected Iterator iterateOnDataSource() { + return backingStore.iterator(); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java index b63ca2ab1a..2674bf0cd7 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -16,71 +16,72 @@ package com.hedera.mirror.web3.state.utils; -import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.WritableKVStateBase; import jakarta.annotation.Nonnull; -import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Objects; public class MapWritableKVState extends WritableKVStateBase { - private final ReadableKVState readableKVState; + private final Map backingStore; - public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { - super(stateKey); - this.readableKVState = readableKVState; - } - - // The readable state's values are immutable, hence callers would not be able - // to modify the readable state's objects. - @Override - protected V getForModifyFromDataSource(@Nonnull K key) { - return readableKVState.get(key); + /** + * Create an instance using a HashMap as the backing store. + * + * @param stateKey The state key for this state + */ + public MapWritableKVState(@Nonnull final String stateKey) { + this(stateKey, new HashMap<>()); } - @Override - protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { - put(key, value); // put only in memory + /** + * Create an instance using the given map as the backing store. This is useful when you want to + * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public MapWritableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); } @Override - protected void removeFromDataSource(@Nonnull K key) { - remove(key); // remove only in memory + protected V readFromDataSource(@Nonnull K key) { + return backingStore.get(key); } + @Nonnull @Override - protected long sizeOfDataSource() { - return readableKVState.size(); + protected Iterator iterateFromDataSource() { + return backingStore.keySet().iterator(); } @Override - protected V readFromDataSource(@Nonnull K key) { - return readableKVState.get(key); + protected V getForModifyFromDataSource(@Nonnull K key) { + return backingStore.get(key); } - @Nonnull @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + backingStore.put(key, value); } @Override - public void commit() { - reset(); + protected void removeFromDataSource(@Nonnull K key) { + backingStore.remove(key); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MapWritableKVState that = (MapWritableKVState) o; - return Objects.equals(getStateKey(), that.getStateKey()) - && Objects.equals(readableKVState, that.readableKVState); + public long sizeOfDataSource() { + return backingStore.size(); } @Override - public int hashCode() { - return Objects.hash(getStateKey(), readableKVState); + public String toString() { + return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 2fa19f006d..00a3f58449 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -20,22 +20,34 @@ import com.swirlds.state.spi.CommittableWritableStates; import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableKVStateBase; import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableQueueStateBase; import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; -@SuppressWarnings("unchecked") +@SuppressWarnings({"rawtypes", "unchecked"}) public class MapWritableStates implements WritableStates, CommittableWritableStates { private final Map states; - public MapWritableStates(Map states) { - this.states = states; + @Nullable + private final Runnable onCommit; + + public MapWritableStates(@Nonnull final Map states) { + this(states, null); + } + + public MapWritableStates(@Nonnull final Map states, @Nullable final Runnable onCommit) { + this.states = requireNonNull(states); + this.onCommit = onCommit; } @Nonnull @@ -112,6 +124,20 @@ public int hashCode() { @Override public void commit() { - // Empty body because we don't want to persist any changes to DB. + states.values().forEach(state -> { + if (state instanceof WritableKVStateBase kv) { + kv.commit(); + } else if (state instanceof WritableSingletonStateBase singleton) { + singleton.commit(); + } else if (state instanceof WritableQueueStateBase queue) { + queue.commit(); + } else { + throw new IllegalStateException( + "Unknown state type " + state.getClass().getName()); + } + }); + if (onCommit != null) { + onCommit.run(); + } } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 67686501d1..8f5601aca0 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -161,9 +161,8 @@ void testGetWritableStatesForFileService() { final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) - .isEqualTo(new MapWritableStates(Map.of( - "FILES", - new MapWritableKVState<>(fileReadableKVState.getStateKey(), readableStates.get("FILES"))))); + .isEqualTo(new MapWritableStates( + Map.of("FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey())))); } @Test @@ -172,15 +171,12 @@ void testGetWritableStatesForContractService() { when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", - new MapWritableKVState<>( - contractBytecodeReadableKVState.getStateKey(), readableStates.get("BYTECODE")), + new MapWritableKVState<>(contractBytecodeReadableKVState.getStateKey()), "STORAGE", - new MapWritableKVState<>( - contractStorageReadableKVState.getStateKey(), readableStates.get("STORAGE"))))); + new MapWritableKVState<>(contractStorageReadableKVState.getStateKey())))); } @Test @@ -197,19 +193,17 @@ void testGetWritableStatesForTokenService() { assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey(), readableStates.get("ACCOUNTS")), + new MapWritableKVState<>(accountReadableKVState.getStateKey()), "PENDING_AIRDROPS", - new MapWritableKVState<>( - airdropsReadableKVState.getStateKey(), readableStates.get("PENDING_AIRDROPS")), + new MapWritableKVState<>(airdropsReadableKVState.getStateKey()), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), readableStates.get("ALIASES")), + new MapWritableKVState<>(aliasesReadableKVState.getStateKey()), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey(), readableStates.get("NFTS")), + new MapWritableKVState<>(nftReadableKVState.getStateKey()), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey(), readableStates.get("TOKENS")), + new MapWritableKVState<>(tokenReadableKVState.getStateKey()), "TOKEN_RELS", - new MapWritableKVState<>( - tokenRelationshipReadableKVState.getStateKey(), readableStates.get("TOKEN_RELS"))))); + new MapWritableKVState<>(tokenRelationshipReadableKVState.getStateKey())))); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 039edeed85..f7c657b863 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -21,7 +21,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; -import com.swirlds.state.spi.ReadableKVState; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -36,7 +35,7 @@ class MapWritableKVStateTest { private MapWritableKVState mapWritableKVState; @Mock - private ReadableKVState readableKVState; + private Map backingStore; @Mock private AccountID accountID; @@ -46,12 +45,12 @@ class MapWritableKVStateTest { @BeforeEach void setup() { - mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); } @Test void testGetForModifyFromDataSourceReturnsCorrectValue() { - when(readableKVState.get(accountID)).thenReturn(account); + when(backingStore.get(accountID)).thenReturn(account); assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); } @@ -62,7 +61,7 @@ void testDataSourceSizeIsZero() { @Test void testReadFromDataSourceReturnsCorrectValue() { - when(readableKVState.get(accountID)).thenReturn(account); + when(backingStore.get(accountID)).thenReturn(account); assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); } @@ -108,30 +107,4 @@ void testEqualsDifferentType() { void testEqualsWithNull() { assertThat(mapWritableKVState).isNotEqualTo(null); } - - @Test - void testEqualsSameValues() { - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); - assertThat(mapWritableKVState).isEqualTo(other); - } - - @Test - void testEqualsDifferentKeys() { - MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); - assertThat(mapWritableKVState).isNotEqualTo(other); - } - - @Test - void testEqualsDifferentValues() { - final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); - final var readableKVStateOther = new MapReadableKVState<>("ACCOUNTS", accountMapOther); - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVStateOther); - assertThat(mapWritableKVState).isNotEqualTo(other); - } - - @Test - void testHashCode() { - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); - assertThat(mapWritableKVState).hasSameHashCodeAs(other); - } } From cd96150a174b7214461ff99647e9595fa4845795 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 11:31:46 +0200 Subject: [PATCH 15/33] Update the state as in example Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 31 +++ .../web3/state/utils/MapReadableKVState.java | 5 + .../web3/state/utils/MapWritableKVState.java | 23 +- .../web3/state/utils/MapWritableStates.java | 5 + .../web3/state/MirrorNodeStateTest.java | 218 ++++++++++++++---- 5 files changed, 232 insertions(+), 50 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 9b75c47261..76cd12ef9a 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -21,6 +21,7 @@ import static com.swirlds.state.StateChangeListener.StateType.SINGLETON; import static java.util.Objects.requireNonNull; +import com.google.common.annotations.VisibleForTesting; import com.hedera.mirror.web3.state.utils.ListReadableQueueState; import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; @@ -45,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @@ -86,6 +88,10 @@ public void removeServiceState(@NonNull final String serviceName, @NonNull final requireNonNull(stateKey); this.states.computeIfPresent(serviceName, (k, v) -> { v.remove(stateKey); + // Purge any readable states whose state definitions are now stale, + // since they still include the data sources we just removed + readableStates.remove(serviceName); + writableStates.remove(serviceName); return v; }); } @@ -234,4 +240,29 @@ public void mapDeleteChange(@NonNull final K key) { } }); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MirrorNodeState that = (MirrorNodeState) o; + return Objects.equals(readableStates, that.readableStates) + && Objects.equals(writableStates, that.writableStates) + && Objects.equals(states, that.states) + && Objects.equals(listeners, that.listeners); + } + + @Override + public int hashCode() { + return Objects.hash(readableStates, writableStates, states, listeners); + } + + @VisibleForTesting + void setWritableStates(final Map writableStates) { + this.writableStates.putAll(writableStates); + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java index 3d37e99211..c8b8acb455 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -78,4 +78,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getStateKey(), backingStore); } + + @Override + public String toString() { + return "MapReadableKVState{" + "backingStore=" + backingStore + '}'; + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java index 2674bf0cd7..bd0c365703 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -18,7 +18,6 @@ import com.swirlds.state.spi.WritableKVStateBase; import jakarta.annotation.Nonnull; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; @@ -27,15 +26,6 @@ public class MapWritableKVState extends WritableKVStateBase { private final Map backingStore; - /** - * Create an instance using a HashMap as the backing store. - * - * @param stateKey The state key for this state - */ - public MapWritableKVState(@Nonnull final String stateKey) { - this(stateKey, new HashMap<>()); - } - /** * Create an instance using the given map as the backing store. This is useful when you want to * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw @@ -84,4 +74,17 @@ public long sizeOfDataSource() { public String toString() { return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), backingStore); + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 00a3f58449..22e12f26c3 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -140,4 +140,9 @@ public void commit() { onCommit.run(); } } + + @Override + public String toString() { + return "MapWritableStates{" + "states=" + states + '}'; + } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 8f5601aca0..40cceb246a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -17,18 +17,28 @@ package com.hedera.mirror.web3.state; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.StateChangeListener; +import com.swirlds.state.StateChangeListener.StateType; +import com.swirlds.state.spi.WritableStates; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -69,25 +79,12 @@ class MirrorNodeStateTest { @Mock private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + @Mock + private StateChangeListener listener; + @BeforeEach void setup() { - final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); - final Map contractStateData = new HashMap<>(Map.of( - "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), - "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); - final Map tokenStateData = new HashMap<>(Map.of( - "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), - "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), - "ALIASES", Map.of("ALIASES", aliasesReadableKVState), - "NFTS", Map.of("NFTS", nftReadableKVState), - "TOKENS", Map.of("TOKENS", tokenReadableKVState), - "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); - - // Add service using the mock data source - mirrorNodeState = mirrorNodeState - .addService(FileService.NAME, fileStateData) - .addService(ContractService.NAME, contractStateData) - .addService(TokenService.NAME, tokenStateData); + mirrorNodeState = buildTestState(); } @Test @@ -116,7 +113,7 @@ void testGetReadableStatesForFileService() { final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(new ConcurrentHashMap<>( - Map.of("FILES", new MapReadableKVState("FILES", Map.of("FILES", fileReadableKVState)))))); + Map.of("FILES", new MapReadableKVState<>("FILES", Map.of("FILES", fileReadableKVState)))))); } @Test @@ -154,60 +151,201 @@ void testGetReadableStateForUnsupportedService() { assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); } + @Test + void testGetReadableStatesWithSingleton() { + final var stateWithSingleton = new MirrorNodeState(); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", new AtomicReference<>(1L))); + final var readableStates = stateWithSingleton.getReadableStates(EntityIdService.NAME); + assertThat(readableStates.contains("EntityId")).isTrue(); + assertThat(readableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetReadableStatesWithQueue() { + final var stateWithQueue = new MirrorNodeState(); + stateWithQueue.addService( + EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); + final var readableStates = stateWithQueue.getReadableStates(EntityIdService.NAME); + assertThat(readableStates.contains("EntityId")).isTrue(); + assertThat(readableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + @Test void testGetWritableStatesForFileService() { - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates( + Map.of("FILES", new MapWritableKVState<>("FILES", Map.of("FILES", fileReadableKVState))))); + } + + @Test + void testGetWritableStatesForFileServiceWithListeners() { + when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP)); + mirrorNodeState.registerCommitListener(listener); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates( - Map.of("FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey())))); + Map.of("FILES", new MapWritableKVState<>("FILES", Map.of("FILES", fileReadableKVState))))); } @Test void testGetWritableStatesForContractService() { - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); - final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", - new MapWritableKVState<>(contractBytecodeReadableKVState.getStateKey()), + new MapWritableKVState<>("BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState)), "STORAGE", - new MapWritableKVState<>(contractStorageReadableKVState.getStateKey())))); + new MapWritableKVState<>("STORAGE", Map.of("STORAGE", contractStorageReadableKVState))))); } @Test void testGetWritableStatesForTokenService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); - when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); - when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); - final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey()), + new MapWritableKVState<>("ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState)), "PENDING_AIRDROPS", - new MapWritableKVState<>(airdropsReadableKVState.getStateKey()), + new MapWritableKVState<>( + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState)), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey()), + new MapWritableKVState<>("ALIASES", Map.of("ALIASES", aliasesReadableKVState)), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey()), + new MapWritableKVState<>("NFTS", Map.of("NFTS", nftReadableKVState)), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey()), + new MapWritableKVState<>("TOKENS", Map.of("TOKENS", tokenReadableKVState)), "TOKEN_RELS", - new MapWritableKVState<>(tokenRelationshipReadableKVState.getStateKey())))); + new MapWritableKVState<>( + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))))); } @Test void testGetWritableStateForUnsupportedService() { assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); } + + @Test + void testGetWritableStatesWithSingleton() { + final var stateWithSingleton = new MirrorNodeState(); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", new AtomicReference<>(1L))); + final var writableStates = stateWithSingleton.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetWritableStatesWithSingletonWithListeners() { + final var stateWithSingleton = new MirrorNodeState(); + final var ref = new AtomicReference<>(1L); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", ref)); + when(listener.stateTypes()).thenReturn(Set.of(StateType.SINGLETON)); + stateWithSingleton.registerCommitListener(listener); + + final var writableStates = stateWithSingleton.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetWritableStatesWithQueue() { + final var stateWithQueue = new MirrorNodeState(); + stateWithQueue.addService( + EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); + final var writableStates = stateWithQueue.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + + @Test + void testGetWritableStatesWithQueueWithListeners() { + final var stateWithQueue = new MirrorNodeState(); + final var queue = new ConcurrentLinkedDeque<>(Set.of("value")); + stateWithQueue.addService(EntityIdService.NAME, Map.of("EntityId", queue)); + when(listener.stateTypes()).thenReturn(Set.of(StateType.QUEUE)); + stateWithQueue.registerCommitListener(listener); + + final var writableStates = stateWithQueue.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + + @Test + void testRegisterCommitListener() { + final var state1 = new MirrorNodeState(); + final var state2 = new MirrorNodeState(); + assertThat(state1).isEqualTo(state2); + state1.registerCommitListener(listener); + assertThat(state1).isNotEqualTo(state2); + } + + @Test + void testUnregisterCommitListener() { + final var state1 = new MirrorNodeState(); + final var state2 = new MirrorNodeState(); + assertThat(state1).isEqualTo(state2); + state1.registerCommitListener(listener); + assertThat(state1).isNotEqualTo(state2); + state1.unregisterCommitListener(listener); + assertThat(state1).isEqualTo(state2); + } + + @Test + void testCommit() { + final var state = new MirrorNodeState(); + final var mockMapWritableState = mock(MapWritableStates.class); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mockMapWritableState); + state.setWritableStates(writableStates); + state.commit(); + verify(mockMapWritableState, times(1)).commit(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mirrorNodeState).isEqualTo(mirrorNodeState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mirrorNodeState).isNotEqualTo("someString"); + } + + @Test + void testEqualsWithNull() { + assertThat(mirrorNodeState).isNotEqualTo(null); + } + + @Test + void testEqualsSameValues() { + final var other = buildTestState(); + assertThat(mirrorNodeState).isEqualTo(other); + } + + @Test + void testHashCode() { + final var other = buildTestState(); + assertThat(mirrorNodeState).hasSameHashCodeAs(other); + } + + private MirrorNodeState buildTestState() { + final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map contractStateData = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final Map tokenStateData = new HashMap<>(Map.of( + "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), + "ALIASES", Map.of("ALIASES", aliasesReadableKVState), + "NFTS", Map.of("NFTS", nftReadableKVState), + "TOKENS", Map.of("TOKENS", tokenReadableKVState), + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + + // Add service using the mock data source + return new MirrorNodeState() + .addService(FileService.NAME, fileStateData) + .addService(ContractService.NAME, contractStateData) + .addService(TokenService.NAME, tokenStateData); + } } From 2fede4f1ee0e52c3ae58e3d942c32abf5515a93a Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 12:03:22 +0200 Subject: [PATCH 16/33] Fix code smells and improve coverage Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 14 ++-- .../web3/state/utils/MapWritableStates.java | 13 ++-- .../utils/ListReadableQueueStateTest.java | 52 +++++++++++++++ .../utils/ListWritableQueueStateTest.java | 64 +++++++++++++++++++ 4 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 76cd12ef9a..266cd614db 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -104,19 +104,19 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { if (serviceStates == null) { return new MapReadableStates(new HashMap<>()); } - final Map states = new ConcurrentHashMap<>(); + final Map data = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); if (state instanceof Queue queue) { - states.put(stateName, new ListReadableQueueState(stateName, queue)); + data.put(stateName, new ListReadableQueueState(stateName, queue)); } else if (state instanceof Map map) { - states.put(stateName, new MapReadableKVState(stateName, map)); + data.put(stateName, new MapReadableKVState(stateName, map)); } else if (state instanceof AtomicReference ref) { - states.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); + data.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); } } - return new MapReadableStates(states); + return new MapReadableStates(data); }); } @@ -161,8 +161,8 @@ public void unregisterCommitListener(@NonNull final StateChangeListener listener } public void commit() { - writableStates.values().forEach(writableStates -> { - if (writableStates instanceof MapWritableStates mapWritableStates) { + writableStates.values().forEach(writableStatesValue -> { + if (writableStatesValue instanceof MapWritableStates mapWritableStates) { mapWritableStates.commit(); } }); diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 22e12f26c3..16f7c61275 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -125,14 +125,11 @@ public int hashCode() { @Override public void commit() { states.values().forEach(state -> { - if (state instanceof WritableKVStateBase kv) { - kv.commit(); - } else if (state instanceof WritableSingletonStateBase singleton) { - singleton.commit(); - } else if (state instanceof WritableQueueStateBase queue) { - queue.commit(); - } else { - throw new IllegalStateException( + switch (state) { + case WritableKVStateBase kv -> kv.commit(); + case WritableSingletonStateBase singleton -> singleton.commit(); + case WritableQueueStateBase queue -> queue.commit(); + default -> throw new IllegalStateException( "Unknown state type " + state.getClass().getName()); } }); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java new file mode 100644 index 0000000000..665b84c061 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Iterator; +import java.util.Queue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings({"unchecked"}) +@ExtendWith(MockitoExtension.class) +class ListReadableQueueStateTest { + + @Mock + private Queue backingStore; + + @Test + void testIterateOnDataSource() { + final var iterator = mock(Iterator.class); + when(backingStore.iterator()).thenReturn(iterator); + final var queueState = new ListReadableQueueState<>("KEY", backingStore); + assertThat(queueState.iterateOnDataSource()).isEqualTo(iterator); + } + + @Test + void testPeekOnDataSource() { + final var firstElem = new Object(); + when((backingStore.peek())).thenReturn(firstElem); + final var queueState = new ListReadableQueueState<>("KEY", backingStore); + assertThat(queueState.peekOnDataSource()).isEqualTo(firstElem); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java new file mode 100644 index 0000000000..b843233437 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings({"unchecked"}) +@ExtendWith(MockitoExtension.class) +class ListWritableQueueStateTest { + + private Queue backingStore; + + @BeforeEach + void setup() { + backingStore = new ConcurrentLinkedDeque<>(); + } + + @Test + void testAddToDatasource() { + final var elem = new Object(); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + queueState.addToDataSource(elem); + assertThat(backingStore.contains(elem)).isTrue(); + } + + @Test + void testRemoveFromDatasource() { + final var elem = new Object(); + backingStore.add(elem); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + queueState.removeFromDataSource(); + assertThat(backingStore.isEmpty()).isTrue(); + } + + @Test + void testIterateOnDataSource() { + final var elem = new Object(); + backingStore.add(elem); + final var iterator = backingStore.iterator(); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + assertThat(queueState.iterateOnDataSource().next()).isEqualTo(iterator.next()); + } +} From afcf416b74c01e82fc25d9d116fef542ed4dd972 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 14:03:31 +0200 Subject: [PATCH 17/33] Fix code smells Signed-off-by: Bilyana Gospodinova --- .../state/utils/AbstractMapReadableState.java | 44 +++++++++++++++++++ .../web3/state/utils/MapReadableStates.java | 20 +-------- .../web3/state/utils/MapWritableStates.java | 19 +------- .../utils/ListWritableQueueStateTest.java | 6 +-- .../state/utils/MapWritableKVStateTest.java | 27 ++++++++---- 5 files changed, 69 insertions(+), 47 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java new file mode 100644 index 0000000000..39f413c352 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +abstract class AbstractMapReadableState implements ReadableStates { + + protected final Map states; + + public AbstractMapReadableState(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java index 6b354eb681..b2b37423db 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -19,20 +19,15 @@ import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableQueueState; import com.swirlds.state.spi.ReadableSingletonState; -import com.swirlds.state.spi.ReadableStates; import jakarta.annotation.Nonnull; -import java.util.Collections; import java.util.Map; import java.util.Objects; -import java.util.Set; @SuppressWarnings("unchecked") -public class MapReadableStates implements ReadableStates { - - private final Map states; +public class MapReadableStates extends AbstractMapReadableState { public MapReadableStates(@Nonnull final Map states) { - this.states = Objects.requireNonNull(states); + super(states); } @Nonnull @@ -79,17 +74,6 @@ public ReadableQueueState getQueue(@Nonnull String stateKey) { return (ReadableQueueState) state; } - @Override - public boolean contains(@Nonnull String stateKey) { - return states.containsKey(stateKey); - } - - @Nonnull - @Override - public Set stateKeys() { - return Collections.unmodifiableSet(states.keySet()); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 16f7c61275..df5397a375 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -28,15 +28,11 @@ import com.swirlds.state.spi.WritableStates; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import java.util.Collections; import java.util.Map; import java.util.Objects; -import java.util.Set; @SuppressWarnings({"rawtypes", "unchecked"}) -public class MapWritableStates implements WritableStates, CommittableWritableStates { - - private final Map states; +public class MapWritableStates extends AbstractMapReadableState implements WritableStates, CommittableWritableStates { @Nullable private final Runnable onCommit; @@ -46,7 +42,7 @@ public MapWritableStates(@Nonnull final Map states) { } public MapWritableStates(@Nonnull final Map states, @Nullable final Runnable onCommit) { - this.states = requireNonNull(states); + super(states); this.onCommit = onCommit; } @@ -94,17 +90,6 @@ public WritableQueueState getQueue(@Nonnull final String stateKey) { return (WritableQueueState) state; } - @Override - public boolean contains(@Nonnull String stateKey) { - return states.containsKey(stateKey); - } - - @Nonnull - @Override - public Set stateKeys() { - return Collections.unmodifiableSet(states.keySet()); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java index b843233437..0d65b48a36 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java @@ -16,7 +16,7 @@ package com.hedera.mirror.web3.state.utils; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedDeque; @@ -41,7 +41,7 @@ void testAddToDatasource() { final var elem = new Object(); final var queueState = new ListWritableQueueState<>("KEY", backingStore); queueState.addToDataSource(elem); - assertThat(backingStore.contains(elem)).isTrue(); + assertThat(backingStore).contains(elem); } @Test @@ -50,7 +50,7 @@ void testRemoveFromDatasource() { backingStore.add(elem); final var queueState = new ListWritableQueueState<>("KEY", backingStore); queueState.removeFromDataSource(); - assertThat(backingStore.isEmpty()).isTrue(); + assertThat(backingStore).isEmpty(); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index f7c657b863..614d7fe380 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -21,7 +21,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,30 +67,39 @@ void testReadFromDataSourceReturnsCorrectValue() { @Test void testIterateFromDataSourceReturnsEmptyIterator() { - assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.iterateFromDataSource().next()) + .isEqualTo(backingStore.keySet().iterator().next()); } @Test void testPutIntoDataSource() { - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); + assertThat(backingStore.get(accountID)).isEqualTo(account); } @Test void testRemoveFromDataSource() { - mapWritableKVState.putIntoDataSource(accountID, account); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + backingStore.put(accountID, account); assertThat(mapWritableKVState.contains(accountID)).isTrue(); mapWritableKVState.removeFromDataSource(accountID); - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + assertThat(backingStore.get(accountID)).isNull(); } @Test void testCommit() { - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + mapWritableKVState.put(accountID, account); + assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isFalse(); mapWritableKVState.commit(); - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isTrue(); } @Test From afbf84ac65c9f67e915c6c0cf0d324904c8216db Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 4 Nov 2024 14:39:26 +0200 Subject: [PATCH 18/33] Implement MirrorNodeState Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 106 ++++++++++++ .../web3/state/utils/MapReadableStates.java | 109 ++++++++++++ .../web3/state/utils/MapWritableKVState.java | 86 +++++++++ .../web3/state/utils/MapWritableStates.java | 117 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 163 ++++++++++++++++++ .../state/utils/MapReadableStatesTest.java | 150 ++++++++++++++++ .../state/utils/MapWritableKVStateTest.java | 123 +++++++++++++ .../state/utils/MapWritableStatesTest.java | 120 +++++++++++++ 8 files changed, 974 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java new file mode 100644 index 0000000000..e3cdce4089 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Named +public class MirrorNodeState implements State { + private final Map> tokenReadableServiceStates = new HashMap<>(); + private final Map> contractReadableServiceStates = new HashMap<>(); + private final Map> fileReadableServiceStates = new HashMap<>(); + + private final Map readableStates = new ConcurrentHashMap<>(); + + public MirrorNodeState( + final AccountReadableKVState accountReadableKVState, + final AirdropsReadableKVState airdropsReadableKVState, + final AliasesReadableKVState aliasesReadableKVState, + final ContractBytecodeReadableKVState contractBytecodeReadableKVState, + final ContractStorageReadableKVState contractStorageReadableKVState, + final FileReadableKVState fileReadableKVState, + final NftReadableKVState nftReadableKVState, + final TokenReadableKVState tokenReadableKVState, + final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { + + tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); + tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); + tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); + tokenReadableServiceStates.put("NFTS", nftReadableKVState); + tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); + tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + + contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); + contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + + fileReadableServiceStates.put("FILES", fileReadableKVState); + } + + @Nonnull + @Override + public ReadableStates getReadableStates(@Nonnull String serviceName) { + return readableStates.computeIfAbsent(serviceName, s -> { + switch (s) { + case TokenService.NAME -> { + return new MapReadableStates(tokenReadableServiceStates); + } + case ContractService.NAME -> { + return new MapReadableStates(contractReadableServiceStates); + } + case FileService.NAME -> { + return new MapReadableStates(fileReadableServiceStates); + } + default -> { + return new MapReadableStates(Collections.emptyMap()); + } + } + }); + } + + @Nonnull + @Override + public WritableStates getWritableStates(@Nonnull String serviceName) { + return switch (serviceName) { + case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); + case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); + case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); + default -> new MapWritableStates(Collections.emptyMap()); + }; + } + + private Map getWritableStates(final Map> readableStates) { + final Map data = new HashMap<>(); + readableStates.forEach(((s, readableKVState) -> + data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); + return data; + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java new file mode 100644 index 0000000000..6b354eb681 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapReadableStates implements ReadableStates { + + private final Map states; + + public MapReadableStates(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Nonnull + @Override + public ReadableKVState get(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof ReadableKVState)) { + throw new IllegalArgumentException("State is not an instance of ReadableKVState: " + stateKey); + } + + return (ReadableKVState) state; + } + + @Nonnull + @Override + public ReadableSingletonState getSingleton(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof ReadableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of ReadableSingletonState: " + stateKey); + } + + return (ReadableSingletonState) state; + } + + @Nonnull + @Override + public ReadableQueueState getQueue(@Nonnull String stateKey) { + final var state = states.get(Objects.requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof ReadableQueueState)) { + throw new IllegalArgumentException("State is not an instance of ReadableQueueState: " + stateKey); + } + + return (ReadableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapReadableStates that = (MapReadableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java new file mode 100644 index 0000000000..b63ca2ab1a --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.WritableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; + +public class MapWritableKVState extends WritableKVStateBase { + + private final ReadableKVState readableKVState; + + public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { + super(stateKey); + this.readableKVState = readableKVState; + } + + // The readable state's values are immutable, hence callers would not be able + // to modify the readable state's objects. + @Override + protected V getForModifyFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Override + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + put(key, value); // put only in memory + } + + @Override + protected void removeFromDataSource(@Nonnull K key) { + remove(key); // remove only in memory + } + + @Override + protected long sizeOfDataSource() { + return readableKVState.size(); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return readableKVState.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public void commit() { + reset(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) + && Objects.equals(readableKVState, that.readableKVState); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), readableKVState); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java new file mode 100644 index 0000000000..2fa19f006d --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static java.util.Objects.requireNonNull; + +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@SuppressWarnings("unchecked") +public class MapWritableStates implements WritableStates, CommittableWritableStates { + + private final Map states; + + public MapWritableStates(Map states) { + this.states = states; + } + + @Nonnull + @Override + public WritableKVState get(@Nonnull String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); + } + if (!(state instanceof WritableKVState)) { + throw new IllegalArgumentException("State is not an instance of WritableKVState: " + stateKey); + } + + return (WritableKVState) state; + } + + @Nonnull + @Override + public WritableSingletonState getSingleton(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); + } + + if (!(state instanceof WritableSingletonState)) { + throw new IllegalArgumentException("State is not an instance of WritableSingletonState: " + stateKey); + } + + return (WritableSingletonState) state; + } + + @Nonnull + @Override + public WritableQueueState getQueue(@Nonnull final String stateKey) { + final var state = states.get(requireNonNull(stateKey)); + if (state == null) { + throw new IllegalArgumentException("Unknown queue state key: " + stateKey); + } + + if (!(state instanceof WritableQueueState)) { + throw new IllegalArgumentException("State is not an instance of WritableQueueState: " + stateKey); + } + + return (WritableQueueState) state; + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWritableStates that = (MapWritableStates) o; + return Objects.equals(states, that.states); + } + + @Override + public int hashCode() { + return Objects.hash(states); + } + + @Override + public void commit() { + // Empty body because we don't want to persist any changes to DB. + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java new file mode 100644 index 0000000000..6bcd187535 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.mirror.web3.state.utils.MapReadableStates; +import com.hedera.mirror.web3.state.utils.MapWritableKVState; +import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.file.FileService; +import com.hedera.node.app.service.token.TokenService; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MirrorNodeStateTest { + + @InjectMocks + private MirrorNodeState mirrorNodeState; + + @Mock + private AccountReadableKVState accountReadableKVState; + + @Mock + private AirdropsReadableKVState airdropsReadableKVState; + + @Mock + private AliasesReadableKVState aliasesReadableKVState; + + @Mock + private ContractBytecodeReadableKVState contractBytecodeReadableKVState; + + @Mock + private ContractStorageReadableKVState contractStorageReadableKVState; + + @Mock + private FileReadableKVState fileReadableKVState; + + @Mock + private NftReadableKVState nftReadableKVState; + + @Mock + private TokenReadableKVState tokenReadableKVState; + + @Mock + private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + + @Test + void testGetReadableStatesForFileService() { + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); + assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + } + + @Test + void testGetReadableStatesForContractService() { + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + } + + @Test + void testGetReadableStatesForTokenService() { + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of( + "ACCOUNTS", + accountReadableKVState, + "PENDING_AIRDROPS", + airdropsReadableKVState, + "ALIASES", + aliasesReadableKVState, + "NFTS", + nftReadableKVState, + "TOKENS", + tokenReadableKVState, + "TOKEN_RELS", + tokenRelationshipReadableKVState))); + } + + @Test + void testGetReadableStateForUnsupportedService() { + assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); + } + + @Test + void testGetWritableStatesForFileService() { + when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + } + + @Test + void testGetWritableStatesForContractService() { + when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); + when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + + final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "BYTECODE", + new MapWritableKVState<>( + contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + "STORAGE", + new MapWritableKVState<>( + contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + } + + @Test + void testGetWritableStatesForTokenService() { + when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); + when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); + when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); + when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); + when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); + + final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates(Map.of( + "ACCOUNTS", + new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + "PENDING_AIRDROPS", + new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + "ALIASES", + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + "NFTS", + new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + "TOKENS", + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + "TOKEN_RELS", + new MapWritableKVState<>( + tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + } + + @Test + void testGetWritableStateForUnsupportedService() { + assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java new file mode 100644 index 0000000000..dde0939492 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableQueueState; +import com.swirlds.state.spi.ReadableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableStatesTest { + + private MapReadableStates states; + + @Mock + private ReadableKVState kvStateMock; + + @Mock + private ReadableSingletonState singletonStateMock; + + @Mock + private ReadableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentType() { + assertThat(states).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableStates other = new MapReadableStates(Map.of(KV_STATE_KEY, kvStateMock)); + assertThat(states).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableStates other = new MapReadableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java new file mode 100644 index 0000000000..79813f24a0 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import com.swirlds.state.spi.ReadableKVState; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableKVStateTest { + + private MapWritableKVState mapWritableKVState; + + @Mock + private ReadableKVState readableKVState; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + } + + @Test + void testGetForModifyFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testDataSourceSizeIsZero() { + assertThat(mapWritableKVState.sizeOfDataSource()).isZero(); + } + + @Test + void testReadFromDataSourceReturnsCorrectValue() { + when(readableKVState.get(accountID)).thenReturn(account); + assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testIterateFromDataSourceReturnsEmptyIterator() { + assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + } + + @Test + void testPutIntoDataSource() { + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + } + + @Test + void testRemoveFromDataSource() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.removeFromDataSource(accountID); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testCommit() { + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.contains(accountID)).isTrue(); + mapWritableKVState.commit(); + assertThat(mapWritableKVState.contains(accountID)).isFalse(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapWritableKVState).isEqualTo(mapWritableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapWritableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + assertThat(mapWritableKVState).hasSameHashCodeAs(other); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java new file mode 100644 index 0000000000..9ab6defe74 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableSingletonState; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapWritableStatesTest { + + private MapWritableStates states; + + @Mock + private WritableKVState kvStateMock; + + @Mock + private WritableSingletonState singletonStateMock; + + @Mock + private WritableQueueState queueStateMock; + + private static final String KV_STATE_KEY = "kvState"; + private static final String SINGLETON_KEY = "singleton"; + private static final String QUEUE_KEY = "queue"; + + @BeforeEach + void setup() { + states = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + } + + @Test + void testGetState() { + assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); + } + + @Test + void testGetStateNotFound() { + assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetStateNotCorrectType() { + assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonState() { + assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); + } + + @Test + void testGetSingletonStateNotFound() { + assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetSingletonStateNotCorrectType() { + assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueState() { + assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); + } + + @Test + void testGetQueueStateNotFound() { + assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGetQueueStateNotCorrectType() { + assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testContains() { + assertThat(states.contains(KV_STATE_KEY)).isTrue(); + assertThat(states.contains(SINGLETON_KEY)).isTrue(); + assertThat(states.contains(QUEUE_KEY)).isTrue(); + assertThat(states.contains("unknown")).isFalse(); + } + + @Test + void testStateKeysReturnsCorrectSet() { + assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); + } + + @Test + void testStateKeysReturnsUnmodifiableSet() { + Set keys = states.stateKeys(); + assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); + } +} From 90e20a160716fc79877bf3699767690faca4dd0a Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:01:56 +0200 Subject: [PATCH 19/33] Add dynamic service configuration Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 113 ++++++++++-------- .../web3/state/utils/MapReadableKVState.java | 81 +++++++++++++ .../web3/state/MirrorNodeStateTest.java | 90 +++++++++++--- .../state/utils/MapReadableKVStateTest.java | 109 +++++++++++++++++ 4 files changed, 323 insertions(+), 70 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index e3cdce4089..85d1971c00 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,91 +16,98 @@ package com.hedera.mirror.web3.state; +import static java.util.Objects.requireNonNull; + +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; -import com.hedera.node.app.service.contract.ContractService; -import com.hedera.node.app.service.file.FileService; -import com.hedera.node.app.service.token.TokenService; import com.swirlds.state.State; -import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.EmptyReadableStates; +import com.swirlds.state.spi.EmptyWritableStates; import com.swirlds.state.spi.ReadableStates; import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@SuppressWarnings({"rawtypes", "unchecked"}) @Named public class MirrorNodeState implements State { - private final Map> tokenReadableServiceStates = new HashMap<>(); - private final Map> contractReadableServiceStates = new HashMap<>(); - private final Map> fileReadableServiceStates = new HashMap<>(); private final Map readableStates = new ConcurrentHashMap<>(); + // Key is Service, value is Map of state name to state datasource + private final Map> states = new ConcurrentHashMap<>(); - public MirrorNodeState( - final AccountReadableKVState accountReadableKVState, - final AirdropsReadableKVState airdropsReadableKVState, - final AliasesReadableKVState aliasesReadableKVState, - final ContractBytecodeReadableKVState contractBytecodeReadableKVState, - final ContractStorageReadableKVState contractStorageReadableKVState, - final FileReadableKVState fileReadableKVState, - final NftReadableKVState nftReadableKVState, - final TokenReadableKVState tokenReadableKVState, - final TokenRelationshipReadableKVState tokenRelationshipReadableKVState) { - - tokenReadableServiceStates.put("ACCOUNTS", accountReadableKVState); - tokenReadableServiceStates.put("PENDING_AIRDROPS", airdropsReadableKVState); - tokenReadableServiceStates.put("ALIASES", aliasesReadableKVState); - tokenReadableServiceStates.put("NFTS", nftReadableKVState); - tokenReadableServiceStates.put("TOKENS", tokenReadableKVState); - tokenReadableServiceStates.put("TOKEN_RELS", tokenRelationshipReadableKVState); + public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map dataSources) { + final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>()); + dataSources.forEach((k, b) -> { + if (!serviceStates.containsKey(k)) { + serviceStates.put(k, b); + } + }); - contractReadableServiceStates.put("BYTECODE", contractBytecodeReadableKVState); - contractReadableServiceStates.put("STORAGE", contractStorageReadableKVState); + // Purge any readable states whose state definitions are now stale, + // since they don't include the new data sources we just added + readableStates.remove(serviceName); + return this; + } - fileReadableServiceStates.put("FILES", fileReadableKVState); + /** + * Removes the state with the given key for the service with the given name. + * + * @param serviceName the name of the service + * @param stateKey the key of the state + */ + public void removeServiceState(@NonNull final String serviceName, @NonNull final String stateKey) { + requireNonNull(serviceName); + requireNonNull(stateKey); + this.states.computeIfPresent(serviceName, (k, v) -> { + v.remove(stateKey); + readableStates.remove(serviceName); // Remove the service so that its states will be repopulated. + return v; + }); } @Nonnull @Override public ReadableStates getReadableStates(@Nonnull String serviceName) { return readableStates.computeIfAbsent(serviceName, s -> { - switch (s) { - case TokenService.NAME -> { - return new MapReadableStates(tokenReadableServiceStates); - } - case ContractService.NAME -> { - return new MapReadableStates(contractReadableServiceStates); - } - case FileService.NAME -> { - return new MapReadableStates(fileReadableServiceStates); - } - default -> { - return new MapReadableStates(Collections.emptyMap()); + final var serviceStates = this.states.get(s); + if (serviceStates == null) { + return new EmptyReadableStates(); + } + final Map states = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map map) { + states.put(stateName, new MapReadableKVState(stateName, map)); } } + return new MapReadableStates(states); }); } @Nonnull @Override public WritableStates getWritableStates(@Nonnull String serviceName) { - return switch (serviceName) { - case TokenService.NAME -> new MapWritableStates(getWritableStates(tokenReadableServiceStates)); - case ContractService.NAME -> new MapWritableStates(getWritableStates(contractReadableServiceStates)); - case FileService.NAME -> new MapWritableStates(getWritableStates(fileReadableServiceStates)); - default -> new MapWritableStates(Collections.emptyMap()); - }; - } + final var serviceStates = states.get(serviceName); + if (serviceStates == null) { + return new EmptyWritableStates(); + } - private Map getWritableStates(final Map> readableStates) { - final Map data = new HashMap<>(); - readableStates.forEach(((s, readableKVState) -> - data.put(s, new MapWritableKVState<>(readableKVState.getStateKey(), readableKVState)))); - return data; + final Map data = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Map) { + final var readableState = getReadableStates(serviceName).get(stateName); + data.put(stateName, new MapWritableKVState<>(stateName, readableState)); + } + } + return new MapWritableStates(data); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java new file mode 100644 index 0000000000..3d37e99211 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023-2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableKVStateBase; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * A simple implementation of {@link ReadableKVState} backed by a + * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by + * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy + * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in + * some strange case, or in some other way work with the backing map directly. + * + * @param The key type + * @param The value type + */ +public class MapReadableKVState extends ReadableKVStateBase { + /** Represents the backing storage for this state */ + private final Map backingStore; + + /** + * Create an instance using the given map as the backing store. This is useful when you want to + * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public MapReadableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Override + protected V readFromDataSource(@Nonnull K key) { + return backingStore.get(key); + } + + @Nonnull + @Override + protected Iterator iterateFromDataSource() { + return backingStore.keySet().iterator(); + } + + @Override + public long size() { + return backingStore.size(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapReadableKVState that = (MapReadableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), backingStore); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 6bcd187535..67686501d1 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -19,13 +19,17 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.when; +import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -65,10 +69,54 @@ class MirrorNodeStateTest { @Mock private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + @BeforeEach + void setup() { + final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map contractStateData = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final Map tokenStateData = new HashMap<>(Map.of( + "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), + "ALIASES", Map.of("ALIASES", aliasesReadableKVState), + "NFTS", Map.of("NFTS", nftReadableKVState), + "TOKENS", Map.of("TOKENS", tokenReadableKVState), + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + + // Add service using the mock data source + mirrorNodeState = mirrorNodeState + .addService(FileService.NAME, fileStateData) + .addService(ContractService.NAME, contractStateData) + .addService(TokenService.NAME, tokenStateData); + } + + @Test + void testAddService() { + assertThat(mirrorNodeState.getReadableStates("NEW").contains("FILES")).isFalse(); + final var newState = + mirrorNodeState.addService("NEW", new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState)))); + assertThat(newState.getReadableStates("NEW").contains("FILES")).isTrue(); + } + + @Test + void testRemoveService() { + final var testStates = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final var newState = mirrorNodeState.addService("NEW", testStates); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isTrue(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + newState.removeServiceState("NEW", "BYTECODE"); + assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isFalse(); + assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + } + @Test void testGetReadableStatesForFileService() { final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); - assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(new ConcurrentHashMap<>( + Map.of("FILES", new MapReadableKVState("FILES", Map.of("FILES", fileReadableKVState)))))); } @Test @@ -76,7 +124,10 @@ void testGetReadableStatesForContractService() { final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( - "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + "BYTECODE", + new MapReadableKVState("BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState)), + "STORAGE", + new MapReadableKVState("STORAGE", Map.of("STORAGE", contractStorageReadableKVState))))); } @Test @@ -85,17 +136,17 @@ void testGetReadableStatesForTokenService() { assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( "ACCOUNTS", - accountReadableKVState, + new MapReadableKVState("ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState)), "PENDING_AIRDROPS", - airdropsReadableKVState, + new MapReadableKVState("PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState)), "ALIASES", - aliasesReadableKVState, + new MapReadableKVState("ALIASES", Map.of("ALIASES", aliasesReadableKVState)), "NFTS", - nftReadableKVState, + new MapReadableKVState("NFTS", Map.of("NFTS", nftReadableKVState)), "TOKENS", - tokenReadableKVState, + new MapReadableKVState("TOKENS", Map.of("TOKENS", tokenReadableKVState)), "TOKEN_RELS", - tokenRelationshipReadableKVState))); + new MapReadableKVState("TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))))); } @Test @@ -108,9 +159,11 @@ void testGetWritableStatesForFileService() { when(fileReadableKVState.getStateKey()).thenReturn("FILES"); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( - "FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey(), fileReadableKVState)))); + "FILES", + new MapWritableKVState<>(fileReadableKVState.getStateKey(), readableStates.get("FILES"))))); } @Test @@ -119,14 +172,15 @@ void testGetWritableStatesForContractService() { when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", new MapWritableKVState<>( - contractBytecodeReadableKVState.getStateKey(), contractBytecodeReadableKVState), + contractBytecodeReadableKVState.getStateKey(), readableStates.get("BYTECODE")), "STORAGE", new MapWritableKVState<>( - contractStorageReadableKVState.getStateKey(), contractStorageReadableKVState)))); + contractStorageReadableKVState.getStateKey(), readableStates.get("STORAGE"))))); } @Test @@ -139,21 +193,23 @@ void testGetWritableStatesForTokenService() { when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); + final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey(), accountReadableKVState), + new MapWritableKVState<>(accountReadableKVState.getStateKey(), readableStates.get("ACCOUNTS")), "PENDING_AIRDROPS", - new MapWritableKVState<>(airdropsReadableKVState.getStateKey(), airdropsReadableKVState), + new MapWritableKVState<>( + airdropsReadableKVState.getStateKey(), readableStates.get("PENDING_AIRDROPS")), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), aliasesReadableKVState), + new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), readableStates.get("ALIASES")), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey(), nftReadableKVState), + new MapWritableKVState<>(nftReadableKVState.getStateKey(), readableStates.get("NFTS")), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey(), tokenReadableKVState), + new MapWritableKVState<>(tokenReadableKVState.getStateKey(), readableStates.get("TOKENS")), "TOKEN_RELS", new MapWritableKVState<>( - tokenRelationshipReadableKVState.getStateKey(), tokenRelationshipReadableKVState)))); + tokenRelationshipReadableKVState.getStateKey(), readableStates.get("TOKEN_RELS"))))); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java new file mode 100644 index 0000000000..fdc51f25cd --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.token.Account; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MapReadableKVStateTest { + + private MapReadableKVState mapReadableKVState; + + private Map accountMap; + + @Mock + private AccountID accountID; + + @Mock + private Account account; + + @BeforeEach + void setup() { + accountMap = Map.of(accountID, account); + mapReadableKVState = new MapReadableKVState<>("ACCOUNTS", accountMap); + } + + @Test + void testReadFromDataSource() { + assertThat(mapReadableKVState.readFromDataSource(accountID)).isEqualTo(account); + } + + @Test + void testReadFromDataSourceNotExisting() { + assertThat(mapReadableKVState.readFromDataSource( + AccountID.newBuilder().accountNum(1L).build())) + .isNull(); + } + + @Test + void testIterateFromDataSource() { + assertThat(mapReadableKVState.iterateFromDataSource().hasNext()).isTrue(); + assertThat(mapReadableKVState.iterateFromDataSource().next()).isEqualTo(accountID); + } + + @Test + void testSize() { + assertThat(mapReadableKVState.size()).isEqualTo(1L); + final var accountID1 = AccountID.newBuilder().accountNum(1L).build(); + final var accountID2 = AccountID.newBuilder().accountNum(2L).build(); + final var mapReadableKVStateBigger = new MapReadableKVState<>( + "ACCOUNTS", + Map.of( + accountID1, + Account.newBuilder().accountId(accountID1).build(), + accountID2, + Account.newBuilder().accountId(accountID2).build())); + assertThat(mapReadableKVStateBigger.size()).isEqualTo(2L); + } + + @Test + void testEqualsSameInstance() { + assertThat(mapReadableKVState).isEqualTo(mapReadableKVState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mapReadableKVState).isNotEqualTo("someString"); + } + + @Test + void testEqualsSameValues() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).isEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); + assertThat(mapReadableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + assertThat(mapReadableKVState).hasSameHashCodeAs(other); + } +} From 7917a71a3a855d3515337a43038fbca73f9dd74b Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:21:51 +0200 Subject: [PATCH 20/33] Fix code smell Signed-off-by: Bilyana Gospodinova --- .../java/com/hedera/mirror/web3/state/MirrorNodeState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 85d1971c00..92e3ee78b7 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -79,15 +79,15 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { if (serviceStates == null) { return new EmptyReadableStates(); } - final Map states = new ConcurrentHashMap<>(); + final Map data = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); if (state instanceof Map map) { - states.put(stateName, new MapReadableKVState(stateName, map)); + data.put(stateName, new MapReadableKVState(stateName, map)); } } - return new MapReadableStates(states); + return new MapReadableStates(data); }); } From 8c730bb801c4a6e05f8d7d2043aabdd47d886ef6 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 5 Nov 2024 16:51:04 +0200 Subject: [PATCH 21/33] Increase code coverage Signed-off-by: Bilyana Gospodinova --- .../state/utils/MapReadableKVStateTest.java | 14 +++++++++++- .../state/utils/MapReadableStatesTest.java | 5 +++++ .../state/utils/MapWritableKVStateTest.java | 16 +++++++++++++- .../state/utils/MapWritableStatesTest.java | 22 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java index fdc51f25cd..eb4f27a2bb 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java @@ -89,6 +89,11 @@ void testEqualsDifferentType() { assertThat(mapReadableKVState).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(mapReadableKVState).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); @@ -96,11 +101,18 @@ void testEqualsSameValues() { } @Test - void testEqualsDifferentValues() { + void testEqualsDifferentKeys() { MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); assertThat(mapReadableKVState).isNotEqualTo(other); } + @Test + void testEqualsDifferentValues() { + final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); + MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMapOther); + assertThat(mapReadableKVState).isNotEqualTo(other); + } + @Test void testHashCode() { MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java index dde0939492..60984b5c9b 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java @@ -128,6 +128,11 @@ void testEqualsDifferentType() { assertThat(states).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(states).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapReadableStates other = new MapReadableStates( diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 79813f24a0..039edeed85 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -23,6 +23,7 @@ import com.hedera.hapi.node.state.token.Account; import com.swirlds.state.spi.ReadableKVState; import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -103,6 +104,11 @@ void testEqualsDifferentType() { assertThat(mapWritableKVState).isNotEqualTo("someString"); } + @Test + void testEqualsWithNull() { + assertThat(mapWritableKVState).isNotEqualTo(null); + } + @Test void testEqualsSameValues() { MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); @@ -110,11 +116,19 @@ void testEqualsSameValues() { } @Test - void testEqualsDifferentValues() { + void testEqualsDifferentKeys() { MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); assertThat(mapWritableKVState).isNotEqualTo(other); } + @Test + void testEqualsDifferentValues() { + final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); + final var readableKVStateOther = new MapReadableKVState<>("ACCOUNTS", accountMapOther); + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVStateOther); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + @Test void testHashCode() { MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java index 9ab6defe74..931c0d67bd 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -117,4 +117,26 @@ void testStateKeysReturnsUnmodifiableSet() { Set keys = states.stateKeys(); assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); } + + @Test + void testEqualsSameInstance() { + assertThat(states).isEqualTo(states); + } + + @Test + void testEqualsDifferentClass() { + assertThat(states).isNotEqualTo("other"); + } + + @Test + void testEqualsWithNull() { + assertThat(states).isNotEqualTo(null); + } + + @Test + void testHashCode() { + MapWritableStates other = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); + assertThat(states).hasSameHashCodeAs(other); + } } From d7b3da2d50cefa11ec86d40c7201f83a292c5bfb Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Fri, 8 Nov 2024 11:09:14 +0200 Subject: [PATCH 22/33] sync with services more Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 166 +++++++++++++++--- .../state/utils/ListReadableQueueState.java | 56 ++++++ .../state/utils/ListWritableQueueState.java | 57 ++++++ .../web3/state/utils/MapWritableKVState.java | 71 ++++---- .../web3/state/utils/MapWritableStates.java | 34 +++- .../web3/state/MirrorNodeStateTest.java | 26 ++- .../state/utils/MapWritableKVStateTest.java | 35 +--- 7 files changed, 338 insertions(+), 107 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 92e3ee78b7..9b75c47261 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,30 +16,49 @@ package com.hedera.mirror.web3.state; +import static com.swirlds.state.StateChangeListener.StateType.MAP; +import static com.swirlds.state.StateChangeListener.StateType.QUEUE; +import static com.swirlds.state.StateChangeListener.StateType.SINGLETON; import static java.util.Objects.requireNonNull; +import com.hedera.mirror.web3.state.utils.ListReadableQueueState; +import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; import com.swirlds.state.State; -import com.swirlds.state.spi.EmptyReadableStates; +import com.swirlds.state.StateChangeListener; import com.swirlds.state.spi.EmptyWritableStates; +import com.swirlds.state.spi.KVChangeListener; +import com.swirlds.state.spi.QueueChangeListener; +import com.swirlds.state.spi.ReadableSingletonStateBase; import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableQueueStateBase; +import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import jakarta.annotation.Nonnull; import jakarta.inject.Named; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; @SuppressWarnings({"rawtypes", "unchecked"}) @Named public class MirrorNodeState implements State { private final Map readableStates = new ConcurrentHashMap<>(); + private final Map writableStates = new ConcurrentHashMap<>(); + // Key is Service, value is Map of state name to state datasource private final Map> states = new ConcurrentHashMap<>(); + private final List listeners = new ArrayList<>(); public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map dataSources) { final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>()); @@ -52,6 +71,7 @@ public MirrorNodeState addService(@NonNull final String serviceName, @NonNull fi // Purge any readable states whose state definitions are now stale, // since they don't include the new data sources we just added readableStates.remove(serviceName); + writableStates.remove(serviceName); return this; } @@ -66,7 +86,6 @@ public void removeServiceState(@NonNull final String serviceName, @NonNull final requireNonNull(stateKey); this.states.computeIfPresent(serviceName, (k, v) -> { v.remove(stateKey); - readableStates.remove(serviceName); // Remove the service so that its states will be repopulated. return v; }); } @@ -77,37 +96,142 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { return readableStates.computeIfAbsent(serviceName, s -> { final var serviceStates = this.states.get(s); if (serviceStates == null) { - return new EmptyReadableStates(); + return new MapReadableStates(new HashMap<>()); } - final Map data = new ConcurrentHashMap<>(); + final Map states = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); - if (state instanceof Map map) { - data.put(stateName, new MapReadableKVState(stateName, map)); + if (state instanceof Queue queue) { + states.put(stateName, new ListReadableQueueState(stateName, queue)); + } else if (state instanceof Map map) { + states.put(stateName, new MapReadableKVState(stateName, map)); + } else if (state instanceof AtomicReference ref) { + states.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); } } - return new MapReadableStates(data); + return new MapReadableStates(states); }); } @Nonnull @Override public WritableStates getWritableStates(@Nonnull String serviceName) { - final var serviceStates = states.get(serviceName); - if (serviceStates == null) { - return new EmptyWritableStates(); - } - - final Map data = new ConcurrentHashMap<>(); - for (final var entry : serviceStates.entrySet()) { - final var stateName = entry.getKey(); - final var state = entry.getValue(); - if (state instanceof Map) { - final var readableState = getReadableStates(serviceName).get(stateName); - data.put(stateName, new MapWritableKVState<>(stateName, readableState)); + return writableStates.computeIfAbsent(serviceName, s -> { + final var serviceStates = states.get(s); + if (serviceStates == null) { + return new EmptyWritableStates(); + } + final Map data = new ConcurrentHashMap<>(); + for (final var entry : serviceStates.entrySet()) { + final var stateName = entry.getKey(); + final var state = entry.getValue(); + if (state instanceof Queue queue) { + data.put( + stateName, + withAnyRegisteredListeners(serviceName, new ListWritableQueueState<>(stateName, queue))); + } else if (state instanceof Map map) { + data.put( + stateName, + withAnyRegisteredListeners(serviceName, new MapWritableKVState<>(stateName, map))); + } else if (state instanceof AtomicReference ref) { + data.put(stateName, withAnyRegisteredListeners(serviceName, stateName, ref)); + } + } + return new MapWritableStates(data, () -> readableStates.remove(serviceName)); + }); + } + + @Override + public void registerCommitListener(@NonNull final StateChangeListener listener) { + requireNonNull(listener); + listeners.add(listener); + } + + @Override + public void unregisterCommitListener(@NonNull final StateChangeListener listener) { + requireNonNull(listener); + listeners.remove(listener); + } + + public void commit() { + writableStates.values().forEach(writableStates -> { + if (writableStates instanceof MapWritableStates mapWritableStates) { + mapWritableStates.commit(); + } + }); + } + + private WritableSingletonStateBase withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final String stateKey, @NonNull final AtomicReference ref) { + final var state = new WritableSingletonStateBase<>(stateKey, ref::get, ref::set); + listeners.forEach(listener -> { + if (listener.stateTypes().contains(SINGLETON)) { + registerSingletonListener(serviceName, state, listener); } - } - return new MapWritableStates(data); + }); + return state; + } + + private MapWritableKVState withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final MapWritableKVState state) { + listeners.forEach(listener -> { + if (listener.stateTypes().contains(MAP)) { + registerKVListener(serviceName, state, listener); + } + }); + return state; + } + + private ListWritableQueueState withAnyRegisteredListeners( + @NonNull final String serviceName, @NonNull final ListWritableQueueState state) { + listeners.forEach(listener -> { + if (listener.stateTypes().contains(QUEUE)) { + registerQueueListener(serviceName, state, listener); + } + }); + return state; + } + + private void registerSingletonListener( + @NonNull final String serviceName, + @NonNull final WritableSingletonStateBase singletonState, + @NonNull final StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, singletonState.getStateKey()); + singletonState.registerListener(value -> listener.singletonUpdateChange(stateId, value)); + } + + private void registerQueueListener( + @NonNull final String serviceName, + @NonNull final WritableQueueStateBase queueState, + @NonNull final StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, queueState.getStateKey()); + queueState.registerListener(new QueueChangeListener<>() { + @Override + public void queuePushChange(@NonNull final V value) { + listener.queuePushChange(stateId, value); + } + + @Override + public void queuePopChange() { + listener.queuePopChange(stateId); + } + }); + } + + private void registerKVListener( + @NonNull final String serviceName, WritableKVStateBase state, StateChangeListener listener) { + final var stateId = listener.stateIdFor(serviceName, state.getStateKey()); + state.registerListener(new KVChangeListener<>() { + @Override + public void mapUpdateChange(@NonNull final K key, @NonNull final V value) { + listener.mapUpdateChange(stateId, key, value); + } + + @Override + public void mapDeleteChange(@NonNull final K key) { + listener.mapDeleteChange(stateId, key); + } + }); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java new file mode 100644 index 0000000000..1a2633aafe --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableQueueStateBase; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; + +public class ListReadableQueueState extends ReadableQueueStateBase { + + /** Represents the backing storage for this state */ + private final Queue backingStore; + + /** + * Create an instance using the given Queue as the backing store. This is useful when you want to + * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public ListReadableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Nullable + @Override + protected E peekOnDataSource() { + return backingStore.peek(); + } + + @NonNull + @Override + protected Iterator iterateOnDataSource() { + return backingStore.iterator(); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java new file mode 100644 index 0000000000..26c50e0381 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.WritableQueueStateBase; +import jakarta.annotation.Nonnull; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; + +public class ListWritableQueueState extends WritableQueueStateBase { + /** Represents the backing storage for this state */ + private final Queue backingStore; + + /** + * Create an instance using the given Queue as the backing store. This is useful when you want to + * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public ListWritableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); + } + + @Override + protected void addToDataSource(@Nonnull E element) { + backingStore.add(element); + } + + @Override + protected void removeFromDataSource() { + backingStore.remove(); + } + + @Nonnull + @Override + protected Iterator iterateOnDataSource() { + return backingStore.iterator(); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java index b63ca2ab1a..2674bf0cd7 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -16,71 +16,72 @@ package com.hedera.mirror.web3.state.utils; -import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.WritableKVStateBase; import jakarta.annotation.Nonnull; -import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Objects; public class MapWritableKVState extends WritableKVStateBase { - private final ReadableKVState readableKVState; + private final Map backingStore; - public MapWritableKVState(@Nonnull String stateKey, @Nonnull ReadableKVState readableKVState) { - super(stateKey); - this.readableKVState = readableKVState; - } - - // The readable state's values are immutable, hence callers would not be able - // to modify the readable state's objects. - @Override - protected V getForModifyFromDataSource(@Nonnull K key) { - return readableKVState.get(key); + /** + * Create an instance using a HashMap as the backing store. + * + * @param stateKey The state key for this state + */ + public MapWritableKVState(@Nonnull final String stateKey) { + this(stateKey, new HashMap<>()); } - @Override - protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { - put(key, value); // put only in memory + /** + * Create an instance using the given map as the backing store. This is useful when you want to + * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw + * exceptions when certain keys are accessed, etc. + * + * @param stateKey The state key for this state + * @param backingStore The backing store to use + */ + public MapWritableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { + super(stateKey); + this.backingStore = Objects.requireNonNull(backingStore); } @Override - protected void removeFromDataSource(@Nonnull K key) { - remove(key); // remove only in memory + protected V readFromDataSource(@Nonnull K key) { + return backingStore.get(key); } + @Nonnull @Override - protected long sizeOfDataSource() { - return readableKVState.size(); + protected Iterator iterateFromDataSource() { + return backingStore.keySet().iterator(); } @Override - protected V readFromDataSource(@Nonnull K key) { - return readableKVState.get(key); + protected V getForModifyFromDataSource(@Nonnull K key) { + return backingStore.get(key); } - @Nonnull @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); + protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { + backingStore.put(key, value); } @Override - public void commit() { - reset(); + protected void removeFromDataSource(@Nonnull K key) { + backingStore.remove(key); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MapWritableKVState that = (MapWritableKVState) o; - return Objects.equals(getStateKey(), that.getStateKey()) - && Objects.equals(readableKVState, that.readableKVState); + public long sizeOfDataSource() { + return backingStore.size(); } @Override - public int hashCode() { - return Objects.hash(getStateKey(), readableKVState); + public String toString() { + return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 2fa19f006d..00a3f58449 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -20,22 +20,34 @@ import com.swirlds.state.spi.CommittableWritableStates; import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableKVStateBase; import com.swirlds.state.spi.WritableQueueState; +import com.swirlds.state.spi.WritableQueueStateBase; import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; -@SuppressWarnings("unchecked") +@SuppressWarnings({"rawtypes", "unchecked"}) public class MapWritableStates implements WritableStates, CommittableWritableStates { private final Map states; - public MapWritableStates(Map states) { - this.states = states; + @Nullable + private final Runnable onCommit; + + public MapWritableStates(@Nonnull final Map states) { + this(states, null); + } + + public MapWritableStates(@Nonnull final Map states, @Nullable final Runnable onCommit) { + this.states = requireNonNull(states); + this.onCommit = onCommit; } @Nonnull @@ -112,6 +124,20 @@ public int hashCode() { @Override public void commit() { - // Empty body because we don't want to persist any changes to DB. + states.values().forEach(state -> { + if (state instanceof WritableKVStateBase kv) { + kv.commit(); + } else if (state instanceof WritableSingletonStateBase singleton) { + singleton.commit(); + } else if (state instanceof WritableQueueStateBase queue) { + queue.commit(); + } else { + throw new IllegalStateException( + "Unknown state type " + state.getClass().getName()); + } + }); + if (onCommit != null) { + onCommit.run(); + } } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 67686501d1..8f5601aca0 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -161,9 +161,8 @@ void testGetWritableStatesForFileService() { final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) - .isEqualTo(new MapWritableStates(Map.of( - "FILES", - new MapWritableKVState<>(fileReadableKVState.getStateKey(), readableStates.get("FILES"))))); + .isEqualTo(new MapWritableStates( + Map.of("FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey())))); } @Test @@ -172,15 +171,12 @@ void testGetWritableStatesForContractService() { when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", - new MapWritableKVState<>( - contractBytecodeReadableKVState.getStateKey(), readableStates.get("BYTECODE")), + new MapWritableKVState<>(contractBytecodeReadableKVState.getStateKey()), "STORAGE", - new MapWritableKVState<>( - contractStorageReadableKVState.getStateKey(), readableStates.get("STORAGE"))))); + new MapWritableKVState<>(contractStorageReadableKVState.getStateKey())))); } @Test @@ -197,19 +193,17 @@ void testGetWritableStatesForTokenService() { assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey(), readableStates.get("ACCOUNTS")), + new MapWritableKVState<>(accountReadableKVState.getStateKey()), "PENDING_AIRDROPS", - new MapWritableKVState<>( - airdropsReadableKVState.getStateKey(), readableStates.get("PENDING_AIRDROPS")), + new MapWritableKVState<>(airdropsReadableKVState.getStateKey()), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey(), readableStates.get("ALIASES")), + new MapWritableKVState<>(aliasesReadableKVState.getStateKey()), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey(), readableStates.get("NFTS")), + new MapWritableKVState<>(nftReadableKVState.getStateKey()), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey(), readableStates.get("TOKENS")), + new MapWritableKVState<>(tokenReadableKVState.getStateKey()), "TOKEN_RELS", - new MapWritableKVState<>( - tokenRelationshipReadableKVState.getStateKey(), readableStates.get("TOKEN_RELS"))))); + new MapWritableKVState<>(tokenRelationshipReadableKVState.getStateKey())))); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 039edeed85..f7c657b863 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -21,7 +21,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; -import com.swirlds.state.spi.ReadableKVState; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -36,7 +35,7 @@ class MapWritableKVStateTest { private MapWritableKVState mapWritableKVState; @Mock - private ReadableKVState readableKVState; + private Map backingStore; @Mock private AccountID accountID; @@ -46,12 +45,12 @@ class MapWritableKVStateTest { @BeforeEach void setup() { - mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); } @Test void testGetForModifyFromDataSourceReturnsCorrectValue() { - when(readableKVState.get(accountID)).thenReturn(account); + when(backingStore.get(accountID)).thenReturn(account); assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); } @@ -62,7 +61,7 @@ void testDataSourceSizeIsZero() { @Test void testReadFromDataSourceReturnsCorrectValue() { - when(readableKVState.get(accountID)).thenReturn(account); + when(backingStore.get(accountID)).thenReturn(account); assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); } @@ -108,30 +107,4 @@ void testEqualsDifferentType() { void testEqualsWithNull() { assertThat(mapWritableKVState).isNotEqualTo(null); } - - @Test - void testEqualsSameValues() { - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); - assertThat(mapWritableKVState).isEqualTo(other); - } - - @Test - void testEqualsDifferentKeys() { - MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); - assertThat(mapWritableKVState).isNotEqualTo(other); - } - - @Test - void testEqualsDifferentValues() { - final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); - final var readableKVStateOther = new MapReadableKVState<>("ACCOUNTS", accountMapOther); - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVStateOther); - assertThat(mapWritableKVState).isNotEqualTo(other); - } - - @Test - void testHashCode() { - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); - assertThat(mapWritableKVState).hasSameHashCodeAs(other); - } } From 8fe36b18b3047cca91c233e6880013615eea005e Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 11:31:46 +0200 Subject: [PATCH 23/33] Update the state as in example Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 31 +++ .../web3/state/utils/MapReadableKVState.java | 5 + .../web3/state/utils/MapWritableKVState.java | 23 +- .../web3/state/utils/MapWritableStates.java | 5 + .../web3/state/MirrorNodeStateTest.java | 218 ++++++++++++++---- 5 files changed, 232 insertions(+), 50 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 9b75c47261..76cd12ef9a 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -21,6 +21,7 @@ import static com.swirlds.state.StateChangeListener.StateType.SINGLETON; import static java.util.Objects.requireNonNull; +import com.google.common.annotations.VisibleForTesting; import com.hedera.mirror.web3.state.utils.ListReadableQueueState; import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; @@ -45,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @@ -86,6 +88,10 @@ public void removeServiceState(@NonNull final String serviceName, @NonNull final requireNonNull(stateKey); this.states.computeIfPresent(serviceName, (k, v) -> { v.remove(stateKey); + // Purge any readable states whose state definitions are now stale, + // since they still include the data sources we just removed + readableStates.remove(serviceName); + writableStates.remove(serviceName); return v; }); } @@ -234,4 +240,29 @@ public void mapDeleteChange(@NonNull final K key) { } }); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MirrorNodeState that = (MirrorNodeState) o; + return Objects.equals(readableStates, that.readableStates) + && Objects.equals(writableStates, that.writableStates) + && Objects.equals(states, that.states) + && Objects.equals(listeners, that.listeners); + } + + @Override + public int hashCode() { + return Objects.hash(readableStates, writableStates, states, listeners); + } + + @VisibleForTesting + void setWritableStates(final Map writableStates) { + this.writableStates.putAll(writableStates); + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java index 3d37e99211..c8b8acb455 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -78,4 +78,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getStateKey(), backingStore); } + + @Override + public String toString() { + return "MapReadableKVState{" + "backingStore=" + backingStore + '}'; + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java index 2674bf0cd7..bd0c365703 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -18,7 +18,6 @@ import com.swirlds.state.spi.WritableKVStateBase; import jakarta.annotation.Nonnull; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; @@ -27,15 +26,6 @@ public class MapWritableKVState extends WritableKVStateBase { private final Map backingStore; - /** - * Create an instance using a HashMap as the backing store. - * - * @param stateKey The state key for this state - */ - public MapWritableKVState(@Nonnull final String stateKey) { - this(stateKey, new HashMap<>()); - } - /** * Create an instance using the given map as the backing store. This is useful when you want to * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw @@ -84,4 +74,17 @@ public long sizeOfDataSource() { public String toString() { return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapWritableKVState that = (MapWritableKVState) o; + return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); + } + + @Override + public int hashCode() { + return Objects.hash(getStateKey(), backingStore); + } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 00a3f58449..22e12f26c3 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -140,4 +140,9 @@ public void commit() { onCommit.run(); } } + + @Override + public String toString() { + return "MapWritableStates{" + "states=" + states + '}'; + } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 8f5601aca0..40cceb246a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -17,18 +17,28 @@ package com.hedera.mirror.web3.state; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import com.swirlds.state.StateChangeListener; +import com.swirlds.state.StateChangeListener.StateType; +import com.swirlds.state.spi.WritableStates; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -69,25 +79,12 @@ class MirrorNodeStateTest { @Mock private TokenRelationshipReadableKVState tokenRelationshipReadableKVState; + @Mock + private StateChangeListener listener; + @BeforeEach void setup() { - final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); - final Map contractStateData = new HashMap<>(Map.of( - "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), - "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); - final Map tokenStateData = new HashMap<>(Map.of( - "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), - "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), - "ALIASES", Map.of("ALIASES", aliasesReadableKVState), - "NFTS", Map.of("NFTS", nftReadableKVState), - "TOKENS", Map.of("TOKENS", tokenReadableKVState), - "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); - - // Add service using the mock data source - mirrorNodeState = mirrorNodeState - .addService(FileService.NAME, fileStateData) - .addService(ContractService.NAME, contractStateData) - .addService(TokenService.NAME, tokenStateData); + mirrorNodeState = buildTestState(); } @Test @@ -116,7 +113,7 @@ void testGetReadableStatesForFileService() { final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(new ConcurrentHashMap<>( - Map.of("FILES", new MapReadableKVState("FILES", Map.of("FILES", fileReadableKVState)))))); + Map.of("FILES", new MapReadableKVState<>("FILES", Map.of("FILES", fileReadableKVState)))))); } @Test @@ -154,60 +151,201 @@ void testGetReadableStateForUnsupportedService() { assertThat(mirrorNodeState.getReadableStates("").size()).isZero(); } + @Test + void testGetReadableStatesWithSingleton() { + final var stateWithSingleton = new MirrorNodeState(); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", new AtomicReference<>(1L))); + final var readableStates = stateWithSingleton.getReadableStates(EntityIdService.NAME); + assertThat(readableStates.contains("EntityId")).isTrue(); + assertThat(readableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetReadableStatesWithQueue() { + final var stateWithQueue = new MirrorNodeState(); + stateWithQueue.addService( + EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); + final var readableStates = stateWithQueue.getReadableStates(EntityIdService.NAME); + assertThat(readableStates.contains("EntityId")).isTrue(); + assertThat(readableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + @Test void testGetWritableStatesForFileService() { - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); + assertThat(writableStates) + .isEqualTo(new MapWritableStates( + Map.of("FILES", new MapWritableKVState<>("FILES", Map.of("FILES", fileReadableKVState))))); + } + + @Test + void testGetWritableStatesForFileServiceWithListeners() { + when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP)); + mirrorNodeState.registerCommitListener(listener); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates( - Map.of("FILES", new MapWritableKVState<>(fileReadableKVState.getStateKey())))); + Map.of("FILES", new MapWritableKVState<>("FILES", Map.of("FILES", fileReadableKVState))))); } @Test void testGetWritableStatesForContractService() { - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); - final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "BYTECODE", - new MapWritableKVState<>(contractBytecodeReadableKVState.getStateKey()), + new MapWritableKVState<>("BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState)), "STORAGE", - new MapWritableKVState<>(contractStorageReadableKVState.getStateKey())))); + new MapWritableKVState<>("STORAGE", Map.of("STORAGE", contractStorageReadableKVState))))); } @Test void testGetWritableStatesForTokenService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); - when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); - when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); - final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); - final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( "ACCOUNTS", - new MapWritableKVState<>(accountReadableKVState.getStateKey()), + new MapWritableKVState<>("ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState)), "PENDING_AIRDROPS", - new MapWritableKVState<>(airdropsReadableKVState.getStateKey()), + new MapWritableKVState<>( + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState)), "ALIASES", - new MapWritableKVState<>(aliasesReadableKVState.getStateKey()), + new MapWritableKVState<>("ALIASES", Map.of("ALIASES", aliasesReadableKVState)), "NFTS", - new MapWritableKVState<>(nftReadableKVState.getStateKey()), + new MapWritableKVState<>("NFTS", Map.of("NFTS", nftReadableKVState)), "TOKENS", - new MapWritableKVState<>(tokenReadableKVState.getStateKey()), + new MapWritableKVState<>("TOKENS", Map.of("TOKENS", tokenReadableKVState)), "TOKEN_RELS", - new MapWritableKVState<>(tokenRelationshipReadableKVState.getStateKey())))); + new MapWritableKVState<>( + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))))); } @Test void testGetWritableStateForUnsupportedService() { assertThat(mirrorNodeState.getWritableStates("").size()).isZero(); } + + @Test + void testGetWritableStatesWithSingleton() { + final var stateWithSingleton = new MirrorNodeState(); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", new AtomicReference<>(1L))); + final var writableStates = stateWithSingleton.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetWritableStatesWithSingletonWithListeners() { + final var stateWithSingleton = new MirrorNodeState(); + final var ref = new AtomicReference<>(1L); + stateWithSingleton.addService(EntityIdService.NAME, Map.of("EntityId", ref)); + when(listener.stateTypes()).thenReturn(Set.of(StateType.SINGLETON)); + stateWithSingleton.registerCommitListener(listener); + + final var writableStates = stateWithSingleton.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getSingleton("EntityId").get()).isEqualTo(1L); + } + + @Test + void testGetWritableStatesWithQueue() { + final var stateWithQueue = new MirrorNodeState(); + stateWithQueue.addService( + EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); + final var writableStates = stateWithQueue.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + + @Test + void testGetWritableStatesWithQueueWithListeners() { + final var stateWithQueue = new MirrorNodeState(); + final var queue = new ConcurrentLinkedDeque<>(Set.of("value")); + stateWithQueue.addService(EntityIdService.NAME, Map.of("EntityId", queue)); + when(listener.stateTypes()).thenReturn(Set.of(StateType.QUEUE)); + stateWithQueue.registerCommitListener(listener); + + final var writableStates = stateWithQueue.getWritableStates(EntityIdService.NAME); + assertThat(writableStates.contains("EntityId")).isTrue(); + assertThat(writableStates.getQueue("EntityId").peek()).isEqualTo("value"); + } + + @Test + void testRegisterCommitListener() { + final var state1 = new MirrorNodeState(); + final var state2 = new MirrorNodeState(); + assertThat(state1).isEqualTo(state2); + state1.registerCommitListener(listener); + assertThat(state1).isNotEqualTo(state2); + } + + @Test + void testUnregisterCommitListener() { + final var state1 = new MirrorNodeState(); + final var state2 = new MirrorNodeState(); + assertThat(state1).isEqualTo(state2); + state1.registerCommitListener(listener); + assertThat(state1).isNotEqualTo(state2); + state1.unregisterCommitListener(listener); + assertThat(state1).isEqualTo(state2); + } + + @Test + void testCommit() { + final var state = new MirrorNodeState(); + final var mockMapWritableState = mock(MapWritableStates.class); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mockMapWritableState); + state.setWritableStates(writableStates); + state.commit(); + verify(mockMapWritableState, times(1)).commit(); + } + + @Test + void testEqualsSameInstance() { + assertThat(mirrorNodeState).isEqualTo(mirrorNodeState); + } + + @Test + void testEqualsDifferentType() { + assertThat(mirrorNodeState).isNotEqualTo("someString"); + } + + @Test + void testEqualsWithNull() { + assertThat(mirrorNodeState).isNotEqualTo(null); + } + + @Test + void testEqualsSameValues() { + final var other = buildTestState(); + assertThat(mirrorNodeState).isEqualTo(other); + } + + @Test + void testHashCode() { + final var other = buildTestState(); + assertThat(mirrorNodeState).hasSameHashCodeAs(other); + } + + private MirrorNodeState buildTestState() { + final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map contractStateData = new HashMap<>(Map.of( + "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), + "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + final Map tokenStateData = new HashMap<>(Map.of( + "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), + "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), + "ALIASES", Map.of("ALIASES", aliasesReadableKVState), + "NFTS", Map.of("NFTS", nftReadableKVState), + "TOKENS", Map.of("TOKENS", tokenReadableKVState), + "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + + // Add service using the mock data source + return new MirrorNodeState() + .addService(FileService.NAME, fileStateData) + .addService(ContractService.NAME, contractStateData) + .addService(TokenService.NAME, tokenStateData); + } } From 532e2913289f1a38255fa10b7dc52199d19fa151 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 12:03:22 +0200 Subject: [PATCH 24/33] Fix code smells and improve coverage Signed-off-by: Bilyana Gospodinova --- .../mirror/web3/state/MirrorNodeState.java | 14 ++-- .../web3/state/utils/MapWritableStates.java | 13 ++-- .../utils/ListReadableQueueStateTest.java | 52 +++++++++++++++ .../utils/ListWritableQueueStateTest.java | 64 +++++++++++++++++++ 4 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 76cd12ef9a..266cd614db 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -104,19 +104,19 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) { if (serviceStates == null) { return new MapReadableStates(new HashMap<>()); } - final Map states = new ConcurrentHashMap<>(); + final Map data = new ConcurrentHashMap<>(); for (final var entry : serviceStates.entrySet()) { final var stateName = entry.getKey(); final var state = entry.getValue(); if (state instanceof Queue queue) { - states.put(stateName, new ListReadableQueueState(stateName, queue)); + data.put(stateName, new ListReadableQueueState(stateName, queue)); } else if (state instanceof Map map) { - states.put(stateName, new MapReadableKVState(stateName, map)); + data.put(stateName, new MapReadableKVState(stateName, map)); } else if (state instanceof AtomicReference ref) { - states.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); + data.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get)); } } - return new MapReadableStates(states); + return new MapReadableStates(data); }); } @@ -161,8 +161,8 @@ public void unregisterCommitListener(@NonNull final StateChangeListener listener } public void commit() { - writableStates.values().forEach(writableStates -> { - if (writableStates instanceof MapWritableStates mapWritableStates) { + writableStates.values().forEach(writableStatesValue -> { + if (writableStatesValue instanceof MapWritableStates mapWritableStates) { mapWritableStates.commit(); } }); diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 22e12f26c3..16f7c61275 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -125,14 +125,11 @@ public int hashCode() { @Override public void commit() { states.values().forEach(state -> { - if (state instanceof WritableKVStateBase kv) { - kv.commit(); - } else if (state instanceof WritableSingletonStateBase singleton) { - singleton.commit(); - } else if (state instanceof WritableQueueStateBase queue) { - queue.commit(); - } else { - throw new IllegalStateException( + switch (state) { + case WritableKVStateBase kv -> kv.commit(); + case WritableSingletonStateBase singleton -> singleton.commit(); + case WritableQueueStateBase queue -> queue.commit(); + default -> throw new IllegalStateException( "Unknown state type " + state.getClass().getName()); } }); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java new file mode 100644 index 0000000000..665b84c061 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Iterator; +import java.util.Queue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings({"unchecked"}) +@ExtendWith(MockitoExtension.class) +class ListReadableQueueStateTest { + + @Mock + private Queue backingStore; + + @Test + void testIterateOnDataSource() { + final var iterator = mock(Iterator.class); + when(backingStore.iterator()).thenReturn(iterator); + final var queueState = new ListReadableQueueState<>("KEY", backingStore); + assertThat(queueState.iterateOnDataSource()).isEqualTo(iterator); + } + + @Test + void testPeekOnDataSource() { + final var firstElem = new Object(); + when((backingStore.peek())).thenReturn(firstElem); + final var queueState = new ListReadableQueueState<>("KEY", backingStore); + assertThat(queueState.peekOnDataSource()).isEqualTo(firstElem); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java new file mode 100644 index 0000000000..b843233437 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings({"unchecked"}) +@ExtendWith(MockitoExtension.class) +class ListWritableQueueStateTest { + + private Queue backingStore; + + @BeforeEach + void setup() { + backingStore = new ConcurrentLinkedDeque<>(); + } + + @Test + void testAddToDatasource() { + final var elem = new Object(); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + queueState.addToDataSource(elem); + assertThat(backingStore.contains(elem)).isTrue(); + } + + @Test + void testRemoveFromDatasource() { + final var elem = new Object(); + backingStore.add(elem); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + queueState.removeFromDataSource(); + assertThat(backingStore.isEmpty()).isTrue(); + } + + @Test + void testIterateOnDataSource() { + final var elem = new Object(); + backingStore.add(elem); + final var iterator = backingStore.iterator(); + final var queueState = new ListWritableQueueState<>("KEY", backingStore); + assertThat(queueState.iterateOnDataSource().next()).isEqualTo(iterator.next()); + } +} From 5116cbaec40d5893c899de15851d0549227b43e4 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 14:03:31 +0200 Subject: [PATCH 25/33] Fix code smells Signed-off-by: Bilyana Gospodinova --- .../state/utils/AbstractMapReadableState.java | 44 +++++++++++++++++++ .../web3/state/utils/MapReadableStates.java | 20 +-------- .../web3/state/utils/MapWritableStates.java | 19 +------- .../utils/ListWritableQueueStateTest.java | 6 +-- .../state/utils/MapWritableKVStateTest.java | 27 ++++++++---- 5 files changed, 69 insertions(+), 47 deletions(-) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java new file mode 100644 index 0000000000..39f413c352 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.hedera.mirror.web3.state.utils; + +import com.swirlds.state.spi.ReadableStates; +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +abstract class AbstractMapReadableState implements ReadableStates { + + protected final Map states; + + public AbstractMapReadableState(@Nonnull final Map states) { + this.states = Objects.requireNonNull(states); + } + + @Override + public boolean contains(@Nonnull String stateKey) { + return states.containsKey(stateKey); + } + + @Nonnull + @Override + public Set stateKeys() { + return Collections.unmodifiableSet(states.keySet()); + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java index 6b354eb681..b2b37423db 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java @@ -19,20 +19,15 @@ import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableQueueState; import com.swirlds.state.spi.ReadableSingletonState; -import com.swirlds.state.spi.ReadableStates; import jakarta.annotation.Nonnull; -import java.util.Collections; import java.util.Map; import java.util.Objects; -import java.util.Set; @SuppressWarnings("unchecked") -public class MapReadableStates implements ReadableStates { - - private final Map states; +public class MapReadableStates extends AbstractMapReadableState { public MapReadableStates(@Nonnull final Map states) { - this.states = Objects.requireNonNull(states); + super(states); } @Nonnull @@ -79,17 +74,6 @@ public ReadableQueueState getQueue(@Nonnull String stateKey) { return (ReadableQueueState) state; } - @Override - public boolean contains(@Nonnull String stateKey) { - return states.containsKey(stateKey); - } - - @Nonnull - @Override - public Set stateKeys() { - return Collections.unmodifiableSet(states.keySet()); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index 16f7c61275..df5397a375 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -28,15 +28,11 @@ import com.swirlds.state.spi.WritableStates; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import java.util.Collections; import java.util.Map; import java.util.Objects; -import java.util.Set; @SuppressWarnings({"rawtypes", "unchecked"}) -public class MapWritableStates implements WritableStates, CommittableWritableStates { - - private final Map states; +public class MapWritableStates extends AbstractMapReadableState implements WritableStates, CommittableWritableStates { @Nullable private final Runnable onCommit; @@ -46,7 +42,7 @@ public MapWritableStates(@Nonnull final Map states) { } public MapWritableStates(@Nonnull final Map states, @Nullable final Runnable onCommit) { - this.states = requireNonNull(states); + super(states); this.onCommit = onCommit; } @@ -94,17 +90,6 @@ public WritableQueueState getQueue(@Nonnull final String stateKey) { return (WritableQueueState) state; } - @Override - public boolean contains(@Nonnull String stateKey) { - return states.containsKey(stateKey); - } - - @Nonnull - @Override - public Set stateKeys() { - return Collections.unmodifiableSet(states.keySet()); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java index b843233437..0d65b48a36 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java @@ -16,7 +16,7 @@ package com.hedera.mirror.web3.state.utils; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedDeque; @@ -41,7 +41,7 @@ void testAddToDatasource() { final var elem = new Object(); final var queueState = new ListWritableQueueState<>("KEY", backingStore); queueState.addToDataSource(elem); - assertThat(backingStore.contains(elem)).isTrue(); + assertThat(backingStore).contains(elem); } @Test @@ -50,7 +50,7 @@ void testRemoveFromDatasource() { backingStore.add(elem); final var queueState = new ListWritableQueueState<>("KEY", backingStore); queueState.removeFromDataSource(); - assertThat(backingStore.isEmpty()).isTrue(); + assertThat(backingStore).isEmpty(); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index f7c657b863..614d7fe380 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -21,7 +21,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,30 +67,39 @@ void testReadFromDataSourceReturnsCorrectValue() { @Test void testIterateFromDataSourceReturnsEmptyIterator() { - assertThat(mapWritableKVState.iterateFromDataSource()).isEqualTo(Collections.emptyIterator()); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + mapWritableKVState.putIntoDataSource(accountID, account); + assertThat(mapWritableKVState.iterateFromDataSource().next()) + .isEqualTo(backingStore.keySet().iterator().next()); } @Test void testPutIntoDataSource() { - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); + assertThat(backingStore.get(accountID)).isEqualTo(account); } @Test void testRemoveFromDataSource() { - mapWritableKVState.putIntoDataSource(accountID, account); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + backingStore.put(accountID, account); assertThat(mapWritableKVState.contains(accountID)).isTrue(); mapWritableKVState.removeFromDataSource(accountID); - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + assertThat(backingStore.get(accountID)).isNull(); } @Test void testCommit() { - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); + final var backingStore = new HashMap(); + final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); + mapWritableKVState.put(accountID, account); + assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isFalse(); mapWritableKVState.commit(); - assertThat(mapWritableKVState.contains(accountID)).isFalse(); + assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isTrue(); } @Test From 49cdcfbac46cf738d377056ef761b4172be99aa3 Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 14:47:21 +0200 Subject: [PATCH 26/33] Fix PR comments Signed-off-by: Bilyana Gospodinova --- .../state/utils/AbstractMapReadableState.java | 2 +- .../state/utils/ListReadableQueueState.java | 5 ++- .../state/utils/MapWritableKVStateTest.java | 34 +++++++++---------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java index 39f413c352..1f84b8a574 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java @@ -27,7 +27,7 @@ abstract class AbstractMapReadableState implements ReadableStates { protected final Map states; - public AbstractMapReadableState(@Nonnull final Map states) { + protected AbstractMapReadableState(@Nonnull final Map states) { this.states = Objects.requireNonNull(states); } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java index 1a2633aafe..21ed7d1291 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java @@ -17,9 +17,8 @@ package com.hedera.mirror.web3.state.utils; import com.swirlds.state.spi.ReadableQueueStateBase; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.Iterator; import java.util.Objects; import java.util.Queue; @@ -48,7 +47,7 @@ protected E peekOnDataSource() { return backingStore.peek(); } - @NonNull + @Nonnull @Override protected Iterator iterateOnDataSource() { return backingStore.iterator(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 614d7fe380..00a624d4f8 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -16,7 +16,7 @@ package com.hedera.mirror.web3.state.utils; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.AccountID; @@ -76,30 +76,30 @@ void testIterateFromDataSourceReturnsEmptyIterator() { @Test void testPutIntoDataSource() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(backingStore.get(accountID)).isEqualTo(account); + final var map = new HashMap(); + final var kvState = new MapWritableKVState<>("ACCOUNTS", map); + kvState.putIntoDataSource(accountID, account); + assertThat(map).containsEntry(accountID, account); } @Test void testRemoveFromDataSource() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - backingStore.put(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); - mapWritableKVState.removeFromDataSource(accountID); - assertThat(backingStore.get(accountID)).isNull(); + final var map = new HashMap(); + final var kvState = new MapWritableKVState<>("ACCOUNTS", map); + map.put(accountID, account); + assertThat(kvState.contains(accountID)).isTrue(); + kvState.removeFromDataSource(accountID); + assertThat(map.get(accountID)).isNull(); } @Test void testCommit() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.put(accountID, account); - assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isFalse(); - mapWritableKVState.commit(); - assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isTrue(); + final var map = new HashMap(); + final var kvState = new MapWritableKVState<>("ACCOUNTS", map); + kvState.put(accountID, account); + assertThat(kvState.modifiedKeys()).isNotEmpty(); + kvState.commit(); + assertThat(kvState.modifiedKeys()).isEmpty(); } @Test From bdefa110bc2dec53c784264f3d92be2f5c129b4a Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Mon, 11 Nov 2024 15:29:58 +0200 Subject: [PATCH 27/33] Improve coverage Signed-off-by: Bilyana Gospodinova --- .../web3/state/utils/MapReadableKVState.java | 5 --- .../web3/state/utils/MapWritableKVState.java | 5 --- .../web3/state/utils/MapWritableStates.java | 5 --- .../state/utils/MapWritableKVStateTest.java | 29 ++++++++++++++--- .../state/utils/MapWritableStatesTest.java | 32 +++++++++++++++---- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java index c8b8acb455..3d37e99211 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java @@ -78,9 +78,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getStateKey(), backingStore); } - - @Override - public String toString() { - return "MapReadableKVState{" + "backingStore=" + backingStore + '}'; - } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java index bd0c365703..f331def8f1 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java @@ -70,11 +70,6 @@ public long sizeOfDataSource() { return backingStore.size(); } - @Override - public String toString() { - return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java index df5397a375..d9567f7cac 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java @@ -122,9 +122,4 @@ public void commit() { onCommit.run(); } } - - @Override - public String toString() { - return "MapWritableStates{" + "states=" + states + '}'; - } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java index 00a624d4f8..722b270be8 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java @@ -67,11 +67,11 @@ void testReadFromDataSourceReturnsCorrectValue() { @Test void testIterateFromDataSourceReturnsEmptyIterator() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.iterateFromDataSource().next()) - .isEqualTo(backingStore.keySet().iterator().next()); + final var map = new HashMap(); + final var kvState = new MapWritableKVState<>("ACCOUNTS", map); + kvState.putIntoDataSource(accountID, account); + assertThat(kvState.iterateFromDataSource().next()) + .isEqualTo(map.keySet().iterator().next()); } @Test @@ -116,4 +116,23 @@ void testEqualsDifferentType() { void testEqualsWithNull() { assertThat(mapWritableKVState).isNotEqualTo(null); } + + @Test + void testEqualsDifferentKeys() { + MapWritableKVState other = new MapWritableKVState<>("ALIASES", backingStore); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testEqualsDifferentValues() { + final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", accountMapOther); + assertThat(mapWritableKVState).isNotEqualTo(other); + } + + @Test + void testHashCode() { + MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", backingStore); + assertThat(mapWritableKVState).hasSameHashCodeAs(other); + } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java index 931c0d67bd..0f2b6fb7a8 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -18,10 +18,12 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; -import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableQueueState; -import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableKVStateBase; +import com.swirlds.state.spi.WritableQueueStateBase; +import com.swirlds.state.spi.WritableSingletonStateBase; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.BeforeEach; @@ -36,13 +38,13 @@ class MapWritableStatesTest { private MapWritableStates states; @Mock - private WritableKVState kvStateMock; + private WritableKVStateBase kvStateMock; @Mock - private WritableSingletonState singletonStateMock; + private WritableSingletonStateBase singletonStateMock; @Mock - private WritableQueueState queueStateMock; + private WritableQueueStateBase queueStateMock; private static final String KV_STATE_KEY = "kvState"; private static final String SINGLETON_KEY = "singleton"; @@ -139,4 +141,22 @@ void testHashCode() { Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); assertThat(states).hasSameHashCodeAs(other); } + + @Test + void testCommit() { + final Runnable onCommit = () -> {}; + final var state = new MapWritableStates( + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock), + onCommit); + state.commit(); + verify(kvStateMock, times(1)).commit(); + verify(singletonStateMock, times(1)).commit(); + verify(queueStateMock, times(1)).commit(); + } + + @Test + void testCommitUnknownValue() { + final var state = new MapWritableStates(Map.of("other", new Object())); + assertThatThrownBy(state::commit).isInstanceOf(IllegalStateException.class); + } } From fa57b077aa9bd41da5e4289be0462118cdfb621d Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 12 Nov 2024 10:14:39 +0200 Subject: [PATCH 28/33] Increase coverage Signed-off-by: Bilyana Gospodinova --- .../web3/state/MirrorNodeStateTest.java | 117 ++++++++++++++++++ .../state/utils/MapWritableStatesTest.java | 14 ++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 40cceb246a..8dfc1ebfdb 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -17,11 +17,15 @@ package com.hedera.mirror.web3.state; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.hedera.hapi.node.base.FileID; +import com.hedera.hapi.node.state.file.File; +import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; @@ -30,8 +34,13 @@ import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.token.TokenService; +import com.hedera.node.app.spi.validation.TruePredicate; import com.swirlds.state.StateChangeListener; import com.swirlds.state.StateChangeListener.StateType; +import com.swirlds.state.spi.KVChangeListener; +import com.swirlds.state.spi.QueueChangeListener; +import com.swirlds.state.spi.SingletonChangeListener; +import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; import java.util.HashMap; import java.util.Map; @@ -46,6 +55,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +@SuppressWarnings("unchecked") @ExtendWith(MockitoExtension.class) class MirrorNodeStateTest { @@ -302,6 +312,113 @@ void testCommit() { verify(mockMapWritableState, times(1)).commit(); } + @Test + void testCommitWithUpdateKVListener() { + final var state = buildTestState(); + final var writableKVState = new MapWritableKVState("FILES", new HashMap<>()); + final var backingStore = new HashMap(); + backingStore.put("FILES", writableKVState); + final var mapWritableStates = new MapWritableStates(backingStore); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mapWritableStates); + writableKVState.put(FileID.DEFAULT, File.DEFAULT); + + final var mockListener = mock(KVChangeListener.class); + writableKVState.registerListener(mockListener); + state.registerCommitListener(listener); + state.setWritableStates(writableStates); + state.commit(); + + verify(mockListener, times(1)).mapUpdateChange(any(), any()); + } + + @Test + void testCommitWithDeleteKVListener() { + final var state = buildTestState(); + final var writableKVState = new MapWritableKVState("FILES", new HashMap<>()); + final var backingStore = new HashMap(); + backingStore.put("FILES", writableKVState); + final var mapWritableStates = new MapWritableStates(backingStore); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mapWritableStates); + writableKVState.put(FileID.DEFAULT, File.DEFAULT); + writableKVState.remove(FileID.DEFAULT); + + final var mockListener = mock(KVChangeListener.class); + writableKVState.registerListener(mockListener); + state.registerCommitListener(listener); + state.setWritableStates(writableStates); + state.commit(); + + verify(mockListener, times(1)).mapDeleteChange(any()); + } + + @Test + void testCommitWithUpdateSingletonListener() { + final var state = buildTestState(); + final var ref = new AtomicReference<>(); + final var backingStore = new HashMap(); + final var writableState = new WritableSingletonStateBase<>("FILES", ref::get, ref::set); + backingStore.put("FILES", writableState); + final var mapWritableStates = new MapWritableStates(backingStore); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mapWritableStates); + writableState.put(1L); + + final var mockListener = mock(SingletonChangeListener.class); + writableState.registerListener(mockListener); + state.registerCommitListener(listener); + state.setWritableStates(writableStates); + state.commit(); + + verify(mockListener, times(1)).singletonUpdateChange(any()); + } + + @Test + void testCommitWithQueuePushListener() { + final var state = buildTestState(); + final var writableQueue = new ConcurrentLinkedDeque<>(); + final var backingStore = new HashMap(); + final var writableState = new ListWritableQueueState<>("FILES", writableQueue); + backingStore.put("FILES", writableState); + final var mapWritableStates = new MapWritableStates(backingStore); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mapWritableStates); + writableState.add(1L); + + final var mockListener = mock(QueueChangeListener.class); + writableState.registerListener(mockListener); + state.registerCommitListener(listener); + state.setWritableStates(writableStates); + state.commit(); + + verify(mockListener, times(1)).queuePushChange(any()); + } + + @Test + void testCommitWithQueuePopListener() { + final var state = buildTestState(); + final var backingStore = new HashMap(); + final var writableQueue = new ConcurrentLinkedDeque<>(); + final var writableState = new ListWritableQueueState<>("FILES", writableQueue); + backingStore.put("FILES", writableState); + writableQueue.push(1L); + final var mapWritableStates = new MapWritableStates(backingStore); + Map writableStates = new ConcurrentHashMap<>(); + writableStates.put(FileService.NAME, mapWritableStates); + writableState.add(1L); + writableState.peek(); + writableState.removeIf(TruePredicate.INSTANCE); + + final var mockListener = mock(QueueChangeListener.class); + writableState.registerListener(mockListener); + state.registerCommitListener(listener); + state.setWritableStates(writableStates); + state.commit(); + + verify(mockListener, times(1)).queuePopChange(); + } + @Test void testEqualsSameInstance() { assertThat(mirrorNodeState).isEqualTo(mirrorNodeState); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java index 0f2b6fb7a8..0905d44db6 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -144,16 +145,23 @@ void testHashCode() { @Test void testCommit() { - final Runnable onCommit = () -> {}; final var state = new MapWritableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock), - onCommit); + Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); state.commit(); verify(kvStateMock, times(1)).commit(); verify(singletonStateMock, times(1)).commit(); verify(queueStateMock, times(1)).commit(); } + @Test + void testCommitWithListener() { + final Runnable onCommit = mock(Runnable.class); + final var state = new MapWritableStates(Map.of(KV_STATE_KEY, kvStateMock), onCommit); + state.commit(); + verify(kvStateMock, times(1)).commit(); + verify(onCommit, times(1)).run(); + } + @Test void testCommitUnknownValue() { final var state = new MapWritableStates(Map.of("other", new Object())); From 28a2912e257828b768dc3c0840a9bb371c8c0efd Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 12 Nov 2024 13:31:53 +0200 Subject: [PATCH 29/33] Increase coverage (again) Signed-off-by: Bilyana Gospodinova --- .../web3/state/MirrorNodeStateTest.java | 124 ++++++++---------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 8dfc1ebfdb..4fe45a19f7 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -25,7 +26,6 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.state.file.File; -import com.hedera.mirror.web3.state.utils.ListWritableQueueState; import com.hedera.mirror.web3.state.utils.MapReadableKVState; import com.hedera.mirror.web3.state.utils.MapReadableStates; import com.hedera.mirror.web3.state.utils.MapWritableKVState; @@ -37,11 +37,8 @@ import com.hedera.node.app.spi.validation.TruePredicate; import com.swirlds.state.StateChangeListener; import com.swirlds.state.StateChangeListener.StateType; -import com.swirlds.state.spi.KVChangeListener; -import com.swirlds.state.spi.QueueChangeListener; -import com.swirlds.state.spi.SingletonChangeListener; -import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.spi.WritableStates; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -314,109 +311,104 @@ void testCommit() { @Test void testCommitWithUpdateKVListener() { + // Given final var state = buildTestState(); - final var writableKVState = new MapWritableKVState("FILES", new HashMap<>()); - final var backingStore = new HashMap(); - backingStore.put("FILES", writableKVState); - final var mapWritableStates = new MapWritableStates(backingStore); - Map writableStates = new ConcurrentHashMap<>(); - writableStates.put(FileService.NAME, mapWritableStates); - writableKVState.put(FileID.DEFAULT, File.DEFAULT); + final var map = new HashMap<>(); + state.addService(EntityIdService.NAME, Map.of("EntityId", map)); - final var mockListener = mock(KVChangeListener.class); - writableKVState.registerListener(mockListener); + when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP, StateType.SINGLETON, StateType.QUEUE)); state.registerCommitListener(listener); - state.setWritableStates(writableStates); + + final var writableStates = state.getWritableStates(EntityIdService.NAME); + writableStates.get("EntityId").put(FileID.DEFAULT, File.DEFAULT); + + // When state.commit(); - verify(mockListener, times(1)).mapUpdateChange(any(), any()); + // Then + verify(listener, times(1)).mapUpdateChange(anyInt(), any(), any()); } @Test void testCommitWithDeleteKVListener() { + // Given final var state = buildTestState(); - final var writableKVState = new MapWritableKVState("FILES", new HashMap<>()); - final var backingStore = new HashMap(); - backingStore.put("FILES", writableKVState); - final var mapWritableStates = new MapWritableStates(backingStore); - Map writableStates = new ConcurrentHashMap<>(); - writableStates.put(FileService.NAME, mapWritableStates); - writableKVState.put(FileID.DEFAULT, File.DEFAULT); - writableKVState.remove(FileID.DEFAULT); + final var map = new HashMap<>(); + map.put(FileID.DEFAULT, File.DEFAULT); + state.addService(EntityIdService.NAME, Map.of("EntityId", map)); - final var mockListener = mock(KVChangeListener.class); - writableKVState.registerListener(mockListener); + when(listener.stateTypes()).thenReturn(Collections.singleton(StateType.MAP)); state.registerCommitListener(listener); - state.setWritableStates(writableStates); + + final var writableStates = state.getWritableStates(EntityIdService.NAME); + writableStates.get("EntityId").remove(FileID.DEFAULT); + + // When state.commit(); - verify(mockListener, times(1)).mapDeleteChange(any()); + // Then + verify(listener, times(1)).mapDeleteChange(anyInt(), any()); } @Test void testCommitWithUpdateSingletonListener() { + // Given final var state = buildTestState(); final var ref = new AtomicReference<>(); - final var backingStore = new HashMap(); - final var writableState = new WritableSingletonStateBase<>("FILES", ref::get, ref::set); - backingStore.put("FILES", writableState); - final var mapWritableStates = new MapWritableStates(backingStore); - Map writableStates = new ConcurrentHashMap<>(); - writableStates.put(FileService.NAME, mapWritableStates); - writableState.put(1L); + state.addService(EntityIdService.NAME, Map.of("EntityId", ref)); - final var mockListener = mock(SingletonChangeListener.class); - writableState.registerListener(mockListener); + when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP, StateType.SINGLETON, StateType.QUEUE)); state.registerCommitListener(listener); - state.setWritableStates(writableStates); + + final var writableStates = state.getWritableStates(EntityIdService.NAME); + writableStates.getSingleton("EntityId").put(1L); + + // When state.commit(); - verify(mockListener, times(1)).singletonUpdateChange(any()); + // Then + verify(listener, times(1)).singletonUpdateChange(anyInt(), any()); } @Test void testCommitWithQueuePushListener() { + // Given final var state = buildTestState(); - final var writableQueue = new ConcurrentLinkedDeque<>(); - final var backingStore = new HashMap(); - final var writableState = new ListWritableQueueState<>("FILES", writableQueue); - backingStore.put("FILES", writableState); - final var mapWritableStates = new MapWritableStates(backingStore); - Map writableStates = new ConcurrentHashMap<>(); - writableStates.put(FileService.NAME, mapWritableStates); - writableState.add(1L); + state.addService(EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); - final var mockListener = mock(QueueChangeListener.class); - writableState.registerListener(mockListener); + when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP, StateType.SINGLETON, StateType.QUEUE)); state.registerCommitListener(listener); - state.setWritableStates(writableStates); + + final var writableStates = state.getWritableStates(EntityIdService.NAME); + writableStates.getQueue("EntityId").add(1L); + + // When state.commit(); - verify(mockListener, times(1)).queuePushChange(any()); + // Then + verify(listener, times(1)).queuePushChange(anyInt(), any()); } @Test void testCommitWithQueuePopListener() { + // Given final var state = buildTestState(); - final var backingStore = new HashMap(); - final var writableQueue = new ConcurrentLinkedDeque<>(); - final var writableState = new ListWritableQueueState<>("FILES", writableQueue); - backingStore.put("FILES", writableState); - writableQueue.push(1L); - final var mapWritableStates = new MapWritableStates(backingStore); - Map writableStates = new ConcurrentHashMap<>(); - writableStates.put(FileService.NAME, mapWritableStates); - writableState.add(1L); - writableState.peek(); - writableState.removeIf(TruePredicate.INSTANCE); + state.addService(EntityIdService.NAME, Map.of("EntityId", new ConcurrentLinkedDeque<>(Set.of("value")))); - final var mockListener = mock(QueueChangeListener.class); - writableState.registerListener(mockListener); + when(listener.stateTypes()).thenReturn(Collections.singleton(StateType.QUEUE)); state.registerCommitListener(listener); - state.setWritableStates(writableStates); + + final var writableStates = state.getWritableStates(EntityIdService.NAME); + final var writableQueueState = writableStates.getQueue("EntityId"); + writableQueueState.add("value1"); + writableQueueState.peek(); + writableQueueState.removeIf(TruePredicate.INSTANCE); + + // When state.commit(); - verify(mockListener, times(1)).queuePopChange(); + // Then + verify(listener, times(1)).queuePopChange(anyInt()); } @Test From b724cbb1d23c31f343d97e7096eb982056bfbbfa Mon Sep 17 00:00:00 2001 From: Bilyana Gospodinova Date: Tue, 12 Nov 2024 13:44:32 +0200 Subject: [PATCH 30/33] Rename packages Signed-off-by: Bilyana Gospodinova --- .../hedera/mirror/web3/state/MirrorNodeState.java | 12 ++++++------ .../{utils => core}/AbstractMapReadableState.java | 2 +- .../{utils => core}/ListReadableQueueState.java | 2 +- .../{utils => core}/ListWritableQueueState.java | 2 +- .../state/{utils => core}/MapReadableKVState.java | 2 +- .../state/{utils => core}/MapReadableStates.java | 2 +- .../state/{utils => core}/MapWritableKVState.java | 2 +- .../state/{utils => core}/MapWritableStates.java | 2 +- .../mirror/web3/state/MirrorNodeStateTest.java | 8 ++++---- .../{utils => core}/ListReadableQueueStateTest.java | 2 +- .../{utils => core}/ListWritableQueueStateTest.java | 2 +- .../{utils => core}/MapReadableKVStateTest.java | 2 +- .../state/{utils => core}/MapReadableStatesTest.java | 2 +- .../{utils => core}/MapWritableKVStateTest.java | 2 +- .../state/{utils => core}/MapWritableStatesTest.java | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/AbstractMapReadableState.java (96%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/ListReadableQueueState.java (97%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/ListWritableQueueState.java (97%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/MapReadableKVState.java (98%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/MapReadableStates.java (98%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/MapWritableKVState.java (98%) rename hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/{utils => core}/MapWritableStates.java (98%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/ListReadableQueueStateTest.java (97%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/ListWritableQueueStateTest.java (97%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/MapReadableKVStateTest.java (98%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/MapReadableStatesTest.java (99%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/MapWritableKVStateTest.java (99%) rename hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/{utils => core}/MapWritableStatesTest.java (99%) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 266cd614db..6a44a24168 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -22,12 +22,12 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; -import com.hedera.mirror.web3.state.utils.ListReadableQueueState; -import com.hedera.mirror.web3.state.utils.ListWritableQueueState; -import com.hedera.mirror.web3.state.utils.MapReadableKVState; -import com.hedera.mirror.web3.state.utils.MapReadableStates; -import com.hedera.mirror.web3.state.utils.MapWritableKVState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.mirror.web3.state.core.ListReadableQueueState; +import com.hedera.mirror.web3.state.core.ListWritableQueueState; +import com.hedera.mirror.web3.state.core.MapReadableKVState; +import com.hedera.mirror.web3.state.core.MapReadableStates; +import com.hedera.mirror.web3.state.core.MapWritableKVState; +import com.hedera.mirror.web3.state.core.MapWritableStates; import com.swirlds.state.State; import com.swirlds.state.StateChangeListener; import com.swirlds.state.spi.EmptyWritableStates; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/AbstractMapReadableState.java similarity index 96% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/AbstractMapReadableState.java index 1f84b8a574..682e235277 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/AbstractMapReadableState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.ReadableStates; import jakarta.annotation.Nonnull; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListReadableQueueState.java similarity index 97% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListReadableQueueState.java index 21ed7d1291..3c4e5ecfae 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListReadableQueueState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.ReadableQueueStateBase; import jakarta.annotation.Nonnull; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListWritableQueueState.java similarity index 97% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListWritableQueueState.java index 26c50e0381..51adeb09f5 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/ListWritableQueueState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.WritableQueueStateBase; import jakarta.annotation.Nonnull; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableKVState.java similarity index 98% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableKVState.java index 3d37e99211..c4703ce720 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableKVState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableKVStateBase; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableStates.java similarity index 98% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableStates.java index b2b37423db..190f763095 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapReadableStates.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableQueueState; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableKVState.java similarity index 98% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableKVState.java index f331def8f1..ea5271d3c2 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableKVState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import com.swirlds.state.spi.WritableKVStateBase; import jakarta.annotation.Nonnull; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableStates.java similarity index 98% rename from hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableStates.java index d9567f7cac..47b421db9a 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/core/MapWritableStates.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static java.util.Objects.requireNonNull; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 4fe45a19f7..9fe6ee1a19 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -26,10 +26,10 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.state.file.File; -import com.hedera.mirror.web3.state.utils.MapReadableKVState; -import com.hedera.mirror.web3.state.utils.MapReadableStates; -import com.hedera.mirror.web3.state.utils.MapWritableKVState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.mirror.web3.state.core.MapReadableKVState; +import com.hedera.mirror.web3.state.core.MapReadableStates; +import com.hedera.mirror.web3.state.core.MapWritableKVState; +import com.hedera.mirror.web3.state.core.MapWritableStates; import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.service.contract.ContractService; import com.hedera.node.app.service.file.FileService; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListReadableQueueStateTest.java similarity index 97% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListReadableQueueStateTest.java index 665b84c061..f25bee62ba 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListReadableQueueStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.mock; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListWritableQueueStateTest.java similarity index 97% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListWritableQueueStateTest.java index 0d65b48a36..2abdb67c69 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/ListWritableQueueStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.Assertions.assertThat; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java similarity index 98% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java index eb4f27a2bb..abddf9c2b5 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableStatesTest.java similarity index 99% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableStatesTest.java index 60984b5c9b..9361385d89 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableStatesTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java similarity index 99% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java index 722b270be8..900b772052 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableStatesTest.java similarity index 99% rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableStatesTest.java index 0905d44db6..c31c7a812a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableStatesTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.mirror.web3.state.utils; +package com.hedera.mirror.web3.state.core; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; From 7f3204900ae18df755dcb87bacdfb4a6dff6135f Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Fri, 15 Nov 2024 18:44:17 +0200 Subject: [PATCH 31/33] fix: Remove duplicated classes Signed-off-by: Kristiyan Selveliev --- .../state/components/SchemaRegistryImpl.java | 2 +- .../state/utils/AbstractMapReadableState.java | 44 ----- .../state/utils/ListReadableQueueState.java | 56 ------- .../state/utils/ListWritableQueueState.java | 57 ------- .../web3/state/utils/MapReadableKVState.java | 86 ---------- .../web3/state/utils/MapReadableStates.java | 93 ----------- .../web3/state/utils/MapWritableKVState.java | 90 ---------- .../web3/state/utils/MapWritableStates.java | 130 --------------- .../components/SchemaRegistryImplTest.java | 2 +- .../utils/ListReadableQueueStateTest.java | 52 ------ .../utils/ListWritableQueueStateTest.java | 64 -------- .../state/utils/MapReadableKVStateTest.java | 121 -------------- .../state/utils/MapReadableStatesTest.java | 155 ------------------ .../state/utils/MapWritableKVStateTest.java | 119 -------------- .../state/utils/MapWritableStatesTest.java | 142 ---------------- 15 files changed, 2 insertions(+), 1211 deletions(-) delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java delete mode 100644 hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java delete mode 100644 hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java index bbcb1acce4..362a09c7e8 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/components/SchemaRegistryImpl.java @@ -22,7 +22,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.state.MirrorNodeState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.mirror.web3.state.core.MapWritableStates; import com.hedera.node.app.spi.state.FilteredReadableStates; import com.hedera.node.app.spi.state.FilteredWritableStates; import com.hedera.node.app.state.merkle.SchemaApplications; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java deleted file mode 100644 index 39f413c352..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/AbstractMapReadableState.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.ReadableStates; -import jakarta.annotation.Nonnull; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -abstract class AbstractMapReadableState implements ReadableStates { - - protected final Map states; - - public AbstractMapReadableState(@Nonnull final Map states) { - this.states = Objects.requireNonNull(states); - } - - @Override - public boolean contains(@Nonnull String stateKey) { - return states.containsKey(stateKey); - } - - @Nonnull - @Override - public Set stateKeys() { - return Collections.unmodifiableSet(states.keySet()); - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java deleted file mode 100644 index 1a2633aafe..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListReadableQueueState.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.ReadableQueueStateBase; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import jakarta.annotation.Nonnull; -import java.util.Iterator; -import java.util.Objects; -import java.util.Queue; - -public class ListReadableQueueState extends ReadableQueueStateBase { - - /** Represents the backing storage for this state */ - private final Queue backingStore; - - /** - * Create an instance using the given Queue as the backing store. This is useful when you want to - * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw - * exceptions when certain keys are accessed, etc. - * - * @param stateKey The state key for this state - * @param backingStore The backing store to use - */ - public ListReadableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { - super(stateKey); - this.backingStore = Objects.requireNonNull(backingStore); - } - - @Nullable - @Override - protected E peekOnDataSource() { - return backingStore.peek(); - } - - @NonNull - @Override - protected Iterator iterateOnDataSource() { - return backingStore.iterator(); - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java deleted file mode 100644 index 26c50e0381..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/ListWritableQueueState.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.WritableQueueStateBase; -import jakarta.annotation.Nonnull; -import java.util.Iterator; -import java.util.Objects; -import java.util.Queue; - -public class ListWritableQueueState extends WritableQueueStateBase { - /** Represents the backing storage for this state */ - private final Queue backingStore; - - /** - * Create an instance using the given Queue as the backing store. This is useful when you want to - * pre-populate the queue, or if you want to use Mockito to mock it or cause it to throw - * exceptions when certain keys are accessed, etc. - * - * @param stateKey The state key for this state - * @param backingStore The backing store to use - */ - public ListWritableQueueState(@Nonnull final String stateKey, @Nonnull final Queue backingStore) { - super(stateKey); - this.backingStore = Objects.requireNonNull(backingStore); - } - - @Override - protected void addToDataSource(@Nonnull E element) { - backingStore.add(element); - } - - @Override - protected void removeFromDataSource() { - backingStore.remove(); - } - - @Nonnull - @Override - protected Iterator iterateOnDataSource() { - return backingStore.iterator(); - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java deleted file mode 100644 index c8b8acb455..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableKVState.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2023-2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableKVStateBase; -import jakarta.annotation.Nonnull; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; - -/** - * A simple implementation of {@link ReadableKVState} backed by a - * {@link Map}. Test code has the option of creating an instance disregarding the backing map, or by - * supplying the backing map to use. This latter option is useful if you want to use Mockito to spy - * on it, or if you want to pre-populate it, or use Mockito to make the map throw an exception in - * some strange case, or in some other way work with the backing map directly. - * - * @param The key type - * @param The value type - */ -public class MapReadableKVState extends ReadableKVStateBase { - /** Represents the backing storage for this state */ - private final Map backingStore; - - /** - * Create an instance using the given map as the backing store. This is useful when you want to - * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw - * exceptions when certain keys are accessed, etc. - * - * @param stateKey The state key for this state - * @param backingStore The backing store to use - */ - public MapReadableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { - super(stateKey); - this.backingStore = Objects.requireNonNull(backingStore); - } - - @Override - protected V readFromDataSource(@Nonnull K key) { - return backingStore.get(key); - } - - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return backingStore.keySet().iterator(); - } - - @Override - public long size() { - return backingStore.size(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MapReadableKVState that = (MapReadableKVState) o; - return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); - } - - @Override - public int hashCode() { - return Objects.hash(getStateKey(), backingStore); - } - - @Override - public String toString() { - return "MapReadableKVState{" + "backingStore=" + backingStore + '}'; - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java deleted file mode 100644 index b2b37423db..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapReadableStates.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableQueueState; -import com.swirlds.state.spi.ReadableSingletonState; -import jakarta.annotation.Nonnull; -import java.util.Map; -import java.util.Objects; - -@SuppressWarnings("unchecked") -public class MapReadableStates extends AbstractMapReadableState { - - public MapReadableStates(@Nonnull final Map states) { - super(states); - } - - @Nonnull - @Override - public ReadableKVState get(@Nonnull String stateKey) { - final var state = states.get(Objects.requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); - } - if (!(state instanceof ReadableKVState)) { - throw new IllegalArgumentException("State is not an instance of ReadableKVState: " + stateKey); - } - - return (ReadableKVState) state; - } - - @Nonnull - @Override - public ReadableSingletonState getSingleton(@Nonnull String stateKey) { - final var state = states.get(Objects.requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); - } - - if (!(state instanceof ReadableSingletonState)) { - throw new IllegalArgumentException("State is not an instance of ReadableSingletonState: " + stateKey); - } - - return (ReadableSingletonState) state; - } - - @Nonnull - @Override - public ReadableQueueState getQueue(@Nonnull String stateKey) { - final var state = states.get(Objects.requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown queue state key: " + stateKey); - } - - if (!(state instanceof ReadableQueueState)) { - throw new IllegalArgumentException("State is not an instance of ReadableQueueState: " + stateKey); - } - - return (ReadableQueueState) state; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MapReadableStates that = (MapReadableStates) o; - return Objects.equals(states, that.states); - } - - @Override - public int hashCode() { - return Objects.hash(states); - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java deleted file mode 100644 index bd0c365703..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableKVState.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import com.swirlds.state.spi.WritableKVStateBase; -import jakarta.annotation.Nonnull; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; - -public class MapWritableKVState extends WritableKVStateBase { - - private final Map backingStore; - - /** - * Create an instance using the given map as the backing store. This is useful when you want to - * pre-populate the map, or if you want to use Mockito to mock it or cause it to throw - * exceptions when certain keys are accessed, etc. - * - * @param stateKey The state key for this state - * @param backingStore The backing store to use - */ - public MapWritableKVState(@Nonnull final String stateKey, @Nonnull final Map backingStore) { - super(stateKey); - this.backingStore = Objects.requireNonNull(backingStore); - } - - @Override - protected V readFromDataSource(@Nonnull K key) { - return backingStore.get(key); - } - - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return backingStore.keySet().iterator(); - } - - @Override - protected V getForModifyFromDataSource(@Nonnull K key) { - return backingStore.get(key); - } - - @Override - protected void putIntoDataSource(@Nonnull K key, @Nonnull V value) { - backingStore.put(key, value); - } - - @Override - protected void removeFromDataSource(@Nonnull K key) { - backingStore.remove(key); - } - - @Override - public long sizeOfDataSource() { - return backingStore.size(); - } - - @Override - public String toString() { - return "MapWritableKVState{" + "backingStore=" + backingStore + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MapWritableKVState that = (MapWritableKVState) o; - return Objects.equals(getStateKey(), that.getStateKey()) && Objects.equals(backingStore, that.backingStore); - } - - @Override - public int hashCode() { - return Objects.hash(getStateKey(), backingStore); - } -} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java deleted file mode 100644 index df5397a375..0000000000 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/utils/MapWritableStates.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static java.util.Objects.requireNonNull; - -import com.swirlds.state.spi.CommittableWritableStates; -import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableKVStateBase; -import com.swirlds.state.spi.WritableQueueState; -import com.swirlds.state.spi.WritableQueueStateBase; -import com.swirlds.state.spi.WritableSingletonState; -import com.swirlds.state.spi.WritableSingletonStateBase; -import com.swirlds.state.spi.WritableStates; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; -import java.util.Map; -import java.util.Objects; - -@SuppressWarnings({"rawtypes", "unchecked"}) -public class MapWritableStates extends AbstractMapReadableState implements WritableStates, CommittableWritableStates { - - @Nullable - private final Runnable onCommit; - - public MapWritableStates(@Nonnull final Map states) { - this(states, null); - } - - public MapWritableStates(@Nonnull final Map states, @Nullable final Runnable onCommit) { - super(states); - this.onCommit = onCommit; - } - - @Nonnull - @Override - public WritableKVState get(@Nonnull String stateKey) { - final var state = states.get(requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown k/v state key: " + stateKey); - } - if (!(state instanceof WritableKVState)) { - throw new IllegalArgumentException("State is not an instance of WritableKVState: " + stateKey); - } - - return (WritableKVState) state; - } - - @Nonnull - @Override - public WritableSingletonState getSingleton(@Nonnull final String stateKey) { - final var state = states.get(requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown singleton state key: " + stateKey); - } - - if (!(state instanceof WritableSingletonState)) { - throw new IllegalArgumentException("State is not an instance of WritableSingletonState: " + stateKey); - } - - return (WritableSingletonState) state; - } - - @Nonnull - @Override - public WritableQueueState getQueue(@Nonnull final String stateKey) { - final var state = states.get(requireNonNull(stateKey)); - if (state == null) { - throw new IllegalArgumentException("Unknown queue state key: " + stateKey); - } - - if (!(state instanceof WritableQueueState)) { - throw new IllegalArgumentException("State is not an instance of WritableQueueState: " + stateKey); - } - - return (WritableQueueState) state; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MapWritableStates that = (MapWritableStates) o; - return Objects.equals(states, that.states); - } - - @Override - public int hashCode() { - return Objects.hash(states); - } - - @Override - public void commit() { - states.values().forEach(state -> { - switch (state) { - case WritableKVStateBase kv -> kv.commit(); - case WritableSingletonStateBase singleton -> singleton.commit(); - case WritableQueueStateBase queue -> queue.commit(); - default -> throw new IllegalStateException( - "Unknown state type " + state.getClass().getName()); - } - }); - if (onCommit != null) { - onCommit.run(); - } - } - - @Override - public String toString() { - return "MapWritableStates{" + "states=" + states + '}'; - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java index d00ba1d1b0..fde60fe8c5 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/SchemaRegistryImplTest.java @@ -26,7 +26,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.state.MirrorNodeState; -import com.hedera.mirror.web3.state.utils.MapWritableStates; +import com.hedera.mirror.web3.state.core.MapWritableStates; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.state.merkle.SchemaApplicationType; import com.hedera.node.app.state.merkle.SchemaApplications; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java deleted file mode 100644 index 665b84c061..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListReadableQueueStateTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Iterator; -import java.util.Queue; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@SuppressWarnings({"unchecked"}) -@ExtendWith(MockitoExtension.class) -class ListReadableQueueStateTest { - - @Mock - private Queue backingStore; - - @Test - void testIterateOnDataSource() { - final var iterator = mock(Iterator.class); - when(backingStore.iterator()).thenReturn(iterator); - final var queueState = new ListReadableQueueState<>("KEY", backingStore); - assertThat(queueState.iterateOnDataSource()).isEqualTo(iterator); - } - - @Test - void testPeekOnDataSource() { - final var firstElem = new Object(); - when((backingStore.peek())).thenReturn(firstElem); - final var queueState = new ListReadableQueueState<>("KEY", backingStore); - assertThat(queueState.peekOnDataSource()).isEqualTo(firstElem); - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java deleted file mode 100644 index 0d65b48a36..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/ListWritableQueueStateTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedDeque; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -@SuppressWarnings({"unchecked"}) -@ExtendWith(MockitoExtension.class) -class ListWritableQueueStateTest { - - private Queue backingStore; - - @BeforeEach - void setup() { - backingStore = new ConcurrentLinkedDeque<>(); - } - - @Test - void testAddToDatasource() { - final var elem = new Object(); - final var queueState = new ListWritableQueueState<>("KEY", backingStore); - queueState.addToDataSource(elem); - assertThat(backingStore).contains(elem); - } - - @Test - void testRemoveFromDatasource() { - final var elem = new Object(); - backingStore.add(elem); - final var queueState = new ListWritableQueueState<>("KEY", backingStore); - queueState.removeFromDataSource(); - assertThat(backingStore).isEmpty(); - } - - @Test - void testIterateOnDataSource() { - final var elem = new Object(); - backingStore.add(elem); - final var iterator = backingStore.iterator(); - final var queueState = new ListWritableQueueState<>("KEY", backingStore); - assertThat(queueState.iterateOnDataSource().next()).isEqualTo(iterator.next()); - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java deleted file mode 100644 index eb4f27a2bb..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableKVStateTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.state.token.Account; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MapReadableKVStateTest { - - private MapReadableKVState mapReadableKVState; - - private Map accountMap; - - @Mock - private AccountID accountID; - - @Mock - private Account account; - - @BeforeEach - void setup() { - accountMap = Map.of(accountID, account); - mapReadableKVState = new MapReadableKVState<>("ACCOUNTS", accountMap); - } - - @Test - void testReadFromDataSource() { - assertThat(mapReadableKVState.readFromDataSource(accountID)).isEqualTo(account); - } - - @Test - void testReadFromDataSourceNotExisting() { - assertThat(mapReadableKVState.readFromDataSource( - AccountID.newBuilder().accountNum(1L).build())) - .isNull(); - } - - @Test - void testIterateFromDataSource() { - assertThat(mapReadableKVState.iterateFromDataSource().hasNext()).isTrue(); - assertThat(mapReadableKVState.iterateFromDataSource().next()).isEqualTo(accountID); - } - - @Test - void testSize() { - assertThat(mapReadableKVState.size()).isEqualTo(1L); - final var accountID1 = AccountID.newBuilder().accountNum(1L).build(); - final var accountID2 = AccountID.newBuilder().accountNum(2L).build(); - final var mapReadableKVStateBigger = new MapReadableKVState<>( - "ACCOUNTS", - Map.of( - accountID1, - Account.newBuilder().accountId(accountID1).build(), - accountID2, - Account.newBuilder().accountId(accountID2).build())); - assertThat(mapReadableKVStateBigger.size()).isEqualTo(2L); - } - - @Test - void testEqualsSameInstance() { - assertThat(mapReadableKVState).isEqualTo(mapReadableKVState); - } - - @Test - void testEqualsDifferentType() { - assertThat(mapReadableKVState).isNotEqualTo("someString"); - } - - @Test - void testEqualsWithNull() { - assertThat(mapReadableKVState).isNotEqualTo(null); - } - - @Test - void testEqualsSameValues() { - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); - assertThat(mapReadableKVState).isEqualTo(other); - } - - @Test - void testEqualsDifferentKeys() { - MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); - assertThat(mapReadableKVState).isNotEqualTo(other); - } - - @Test - void testEqualsDifferentValues() { - final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMapOther); - assertThat(mapReadableKVState).isNotEqualTo(other); - } - - @Test - void testHashCode() { - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); - assertThat(mapReadableKVState).hasSameHashCodeAs(other); - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java deleted file mode 100644 index 60984b5c9b..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapReadableStatesTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - -import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableQueueState; -import com.swirlds.state.spi.ReadableSingletonState; -import java.util.Map; -import java.util.Set; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MapReadableStatesTest { - - private MapReadableStates states; - - @Mock - private ReadableKVState kvStateMock; - - @Mock - private ReadableSingletonState singletonStateMock; - - @Mock - private ReadableQueueState queueStateMock; - - private static final String KV_STATE_KEY = "kvState"; - private static final String SINGLETON_KEY = "singleton"; - private static final String QUEUE_KEY = "queue"; - - @BeforeEach - void setup() { - states = new MapReadableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); - } - - @Test - void testGetState() { - assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); - } - - @Test - void testGetStateNotFound() { - assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetStateNotCorrectType() { - assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetSingletonState() { - assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); - } - - @Test - void testGetSingletonStateNotFound() { - assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetSingletonStateNotCorrectType() { - assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetQueueState() { - assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); - } - - @Test - void testGetQueueStateNotFound() { - assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetQueueStateNotCorrectType() { - assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testContains() { - assertThat(states.contains(KV_STATE_KEY)).isTrue(); - assertThat(states.contains(SINGLETON_KEY)).isTrue(); - assertThat(states.contains(QUEUE_KEY)).isTrue(); - assertThat(states.contains("unknown")).isFalse(); - } - - @Test - void testStateKeysReturnsCorrectSet() { - assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); - } - - @Test - void testStateKeysReturnsUnmodifiableSet() { - Set keys = states.stateKeys(); - assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); - } - - @Test - void testEqualsSameInstance() { - assertThat(states).isEqualTo(states); - } - - @Test - void testEqualsDifferentType() { - assertThat(states).isNotEqualTo("someString"); - } - - @Test - void testEqualsWithNull() { - assertThat(states).isNotEqualTo(null); - } - - @Test - void testEqualsSameValues() { - MapReadableStates other = new MapReadableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); - assertThat(states).isEqualTo(other); - } - - @Test - void testEqualsDifferentValues() { - MapReadableStates other = new MapReadableStates(Map.of(KV_STATE_KEY, kvStateMock)); - assertThat(states).isNotEqualTo(other); - } - - @Test - void testHashCode() { - MapReadableStates other = new MapReadableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); - assertThat(states).hasSameHashCodeAs(other); - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java deleted file mode 100644 index 614d7fe380..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableKVStateTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.when; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.state.token.Account; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MapWritableKVStateTest { - - private MapWritableKVState mapWritableKVState; - - @Mock - private Map backingStore; - - @Mock - private AccountID accountID; - - @Mock - private Account account; - - @BeforeEach - void setup() { - mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - } - - @Test - void testGetForModifyFromDataSourceReturnsCorrectValue() { - when(backingStore.get(accountID)).thenReturn(account); - assertThat(mapWritableKVState.getForModifyFromDataSource(accountID)).isEqualTo(account); - } - - @Test - void testDataSourceSizeIsZero() { - assertThat(mapWritableKVState.sizeOfDataSource()).isZero(); - } - - @Test - void testReadFromDataSourceReturnsCorrectValue() { - when(backingStore.get(accountID)).thenReturn(account); - assertThat(mapWritableKVState.readFromDataSource(accountID)).isEqualTo(account); - } - - @Test - void testIterateFromDataSourceReturnsEmptyIterator() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(mapWritableKVState.iterateFromDataSource().next()) - .isEqualTo(backingStore.keySet().iterator().next()); - } - - @Test - void testPutIntoDataSource() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.putIntoDataSource(accountID, account); - assertThat(backingStore.get(accountID)).isEqualTo(account); - } - - @Test - void testRemoveFromDataSource() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - backingStore.put(accountID, account); - assertThat(mapWritableKVState.contains(accountID)).isTrue(); - mapWritableKVState.removeFromDataSource(accountID); - assertThat(backingStore.get(accountID)).isNull(); - } - - @Test - void testCommit() { - final var backingStore = new HashMap(); - final var mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", backingStore); - mapWritableKVState.put(accountID, account); - assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isFalse(); - mapWritableKVState.commit(); - assertThat(mapWritableKVState.modifiedKeys().isEmpty()).isTrue(); - } - - @Test - void testEqualsSameInstance() { - assertThat(mapWritableKVState).isEqualTo(mapWritableKVState); - } - - @Test - void testEqualsDifferentType() { - assertThat(mapWritableKVState).isNotEqualTo("someString"); - } - - @Test - void testEqualsWithNull() { - assertThat(mapWritableKVState).isNotEqualTo(null); - } -} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java deleted file mode 100644 index 931c0d67bd..0000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/utils/MapWritableStatesTest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2024 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. - */ - -package com.hedera.mirror.web3.state.utils; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - -import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableQueueState; -import com.swirlds.state.spi.WritableSingletonState; -import java.util.Map; -import java.util.Set; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MapWritableStatesTest { - - private MapWritableStates states; - - @Mock - private WritableKVState kvStateMock; - - @Mock - private WritableSingletonState singletonStateMock; - - @Mock - private WritableQueueState queueStateMock; - - private static final String KV_STATE_KEY = "kvState"; - private static final String SINGLETON_KEY = "singleton"; - private static final String QUEUE_KEY = "queue"; - - @BeforeEach - void setup() { - states = new MapWritableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); - } - - @Test - void testGetState() { - assertThat(states.get(KV_STATE_KEY)).isEqualTo(kvStateMock); - } - - @Test - void testGetStateNotFound() { - assertThatThrownBy(() -> states.get("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetStateNotCorrectType() { - assertThatThrownBy(() -> states.get(SINGLETON_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetSingletonState() { - assertThat(states.getSingleton(SINGLETON_KEY)).isEqualTo(singletonStateMock); - } - - @Test - void testGetSingletonStateNotFound() { - assertThatThrownBy(() -> states.getSingleton("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetSingletonStateNotCorrectType() { - assertThatThrownBy(() -> states.getSingleton(QUEUE_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetQueueState() { - assertThat(states.getQueue(QUEUE_KEY)).isEqualTo(queueStateMock); - } - - @Test - void testGetQueueStateNotFound() { - assertThatThrownBy(() -> states.getQueue("unknown")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testGetQueueStateNotCorrectType() { - assertThatThrownBy(() -> states.getQueue(KV_STATE_KEY)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testContains() { - assertThat(states.contains(KV_STATE_KEY)).isTrue(); - assertThat(states.contains(SINGLETON_KEY)).isTrue(); - assertThat(states.contains(QUEUE_KEY)).isTrue(); - assertThat(states.contains("unknown")).isFalse(); - } - - @Test - void testStateKeysReturnsCorrectSet() { - assertThat(states.stateKeys()).isEqualTo(Set.of(KV_STATE_KEY, SINGLETON_KEY, QUEUE_KEY)); - } - - @Test - void testStateKeysReturnsUnmodifiableSet() { - Set keys = states.stateKeys(); - assertThatThrownBy(() -> keys.add("newKey")).isInstanceOf(UnsupportedOperationException.class); - } - - @Test - void testEqualsSameInstance() { - assertThat(states).isEqualTo(states); - } - - @Test - void testEqualsDifferentClass() { - assertThat(states).isNotEqualTo("other"); - } - - @Test - void testEqualsWithNull() { - assertThat(states).isNotEqualTo(null); - } - - @Test - void testHashCode() { - MapWritableStates other = new MapWritableStates( - Map.of(KV_STATE_KEY, kvStateMock, SINGLETON_KEY, singletonStateMock, QUEUE_KEY, queueStateMock)); - assertThat(states).hasSameHashCodeAs(other); - } -} From 62d40fa7bb95ff7e18366f2bdc5ea5feb28ff42a Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Fri, 15 Nov 2024 19:08:17 +0200 Subject: [PATCH 32/33] fix: Fix code smells Signed-off-by: Kristiyan Selveliev --- .../components/ServiceMigratorImplTest.java | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java index eced784614..c6f6ab2729 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServiceMigratorImplTest.java @@ -142,54 +142,55 @@ void doMigrationsWithMultipleRegistrationsWithInvalidSchemaRegistry() { Registration registration1 = new Registration(service1, registry1); Registration registration2 = new Registration(service2, registry2); when(servicesRegistry.registrations()).thenReturn(Set.of(registration1, registration2)); - var exception = assertThrows( - IllegalArgumentException.class, - () -> serviceMigrator.doMigrations( - mirrorNodeState, - servicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); + + var servicesVersion = bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion(); + var servicesSoftwareVersion = new ServicesSoftwareVersion(servicesVersion); + var configuration = new ConfigProviderImpl().getConfiguration(); + + var exception = assertThrows(IllegalArgumentException.class, () -> { + serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + servicesSoftwareVersion, + configuration, + networkInfo, + metrics); + }); assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); } @Test void doMigrationsInvalidState() { - var exception = assertThrows( - IllegalArgumentException.class, - () -> serviceMigrator.doMigrations( - mockState, - servicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); + var servicesVersion = bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion(); + var servicesSoftwareVersion = new ServicesSoftwareVersion(servicesVersion); + var configuration = new ConfigProviderImpl().getConfiguration(); + + var exception = assertThrows(IllegalArgumentException.class, () -> { + serviceMigrator.doMigrations( + mockState, servicesRegistry, null, servicesSoftwareVersion, configuration, networkInfo, metrics); + }); assertThat(exception.getMessage()).isEqualTo("Can only be used with MirrorNodeState instances"); } @Test void doMigrationsInvalidServicesRegistry() { - var exception = assertThrows( - IllegalArgumentException.class, - () -> serviceMigrator.doMigrations( - mirrorNodeState, - mockServicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); + var servicesVersion = bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion(); + var servicesSoftwareVersion = new ServicesSoftwareVersion(servicesVersion); + var configuration = new ConfigProviderImpl().getConfiguration(); + + var exception = assertThrows(IllegalArgumentException.class, () -> { + serviceMigrator.doMigrations( + mirrorNodeState, + mockServicesRegistry, + null, + servicesSoftwareVersion, + configuration, + networkInfo, + metrics); + }); assertThat(exception.getMessage()).isEqualTo("Can only be used with ServicesRegistryImpl instances"); } @@ -199,18 +200,21 @@ void doMigrationsInvalidSchemaRegistry() { final var mockServiceRegistration = mock(Registration.class); when(servicesRegistry.registrations()).thenReturn(Set.of(mockServiceRegistration)); when(mockServiceRegistration.registry()).thenReturn(mockSchemaRegistry); - var exception = assertThrows( - IllegalArgumentException.class, - () -> serviceMigrator.doMigrations( - mirrorNodeState, - servicesRegistry, - null, - new ServicesSoftwareVersion(bootstrapConfig - .getConfigData(VersionConfig.class) - .servicesVersion()), - new ConfigProviderImpl().getConfiguration(), - networkInfo, - metrics)); + + var servicesVersion = bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion(); + var servicesSoftwareVersion = new ServicesSoftwareVersion(servicesVersion); + var configuration = new ConfigProviderImpl().getConfiguration(); + + var exception = assertThrows(IllegalArgumentException.class, () -> { + serviceMigrator.doMigrations( + mirrorNodeState, + servicesRegistry, + null, + servicesSoftwareVersion, + configuration, + networkInfo, + metrics); + }); assertThat(exception.getMessage()).isEqualTo("Can only be used with SchemaRegistryImpl instances"); } From 7feac7d076a42146aae2bc385558e41f98a80295 Mon Sep 17 00:00:00 2001 From: Kristiyan Selveliev Date: Fri, 15 Nov 2024 21:31:01 +0200 Subject: [PATCH 33/33] fix: Fix code smells Signed-off-by: Kristiyan Selveliev --- .../web3/state/components/ServicesRegistryImplTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java index 6169a25aff..16accfba7a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/components/ServicesRegistryImplTest.java @@ -42,7 +42,7 @@ void testEmptyRegistrations() { void testRegister() { Service service = new FileServiceImpl(); servicesRegistry.register(service); - assertThat(servicesRegistry.registrations().size()).isEqualTo(1); + assertThat(servicesRegistry.registrations()).hasSize(1); } @Test @@ -62,6 +62,6 @@ void testSubRegistry() { servicesRegistry.register(service2); ServicesRegistryImpl subRegistry = (ServicesRegistryImpl) servicesRegistry.subRegistryFor(service.getServiceName(), service2.getServiceName()); - assertThat(subRegistry.registrations().size()).isEqualTo(2); + assertThat(subRegistry.registrations()).hasSize(2); } }