From 78653bbf448feadaef0b7e73a84fae1eddf73989 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Wed, 20 Dec 2023 10:53:45 -0500 Subject: [PATCH 1/9] fix: Added asserts for threads in all modifying methods (#10584) Signed-off-by: Ivan Malygin --- .../internal/merkle/VirtualRootNode.java | 180 ++++++++++-------- 1 file changed, 102 insertions(+), 78 deletions(-) diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java index 8c19c3f69603..a96e6a161c54 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java @@ -316,6 +316,12 @@ public static class ClassVersion { private VirtualMapStatistics statistics; + /** + * This reference is used to assert that there is only one thread modifying the VM at a time. + * NOTE: This field is used *only* if assertions are enabled, otherwise it always has null value. + */ + private final AtomicReference currentModifyingThreadRef = new AtomicReference<>(null); + /** * Creates a new empty root node. This constructor is used for deserialization and * reconnects, not for normal use. @@ -791,9 +797,14 @@ public boolean containsKey(final K key) { public V getForModify(final K key) { throwIfImmutable(); Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); - final VirtualLeafRecord rec = records.findLeafRecord(key, true); - statistics.countUpdatedEntities(); - return rec == null ? null : rec.getValue(); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + final VirtualLeafRecord rec = records.findLeafRecord(key, true); + statistics.countUpdatedEntities(); + return rec == null ? null : rec.getValue(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); + } } /** @@ -826,20 +837,25 @@ public V get(final K key) { public void put(final K key, final V value) { throwIfImmutable(); assert !isHashed() : "Cannot modify already hashed node"; - Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + + final long path = records.findKey(key); + if (path == INVALID_PATH) { + // The key is not stored. So add a new entry and return. + add(key, value); + statistics.countAddedEntities(); + statistics.setSize(state.size()); + return; + } - final long path = records.findKey(key); - if (path == INVALID_PATH) { - // The key is not stored. So add a new entry and return. - add(key, value); - statistics.countAddedEntities(); - statistics.setSize(state.size()); - return; + final VirtualLeafRecord leaf = new VirtualLeafRecord<>(path, key, value); + cache.putLeaf(leaf); + statistics.countUpdatedEntities(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); } - - final VirtualLeafRecord leaf = new VirtualLeafRecord<>(path, key, value); - cache.putLeaf(leaf); - statistics.countUpdatedEntities(); } /** @@ -859,16 +875,20 @@ public void put(final K key, final V value) { public V replace(final K key, final V value) { throwIfImmutable(); Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + // Attempt to replace the existing leaf + final boolean success = replaceImpl(key, value); + statistics.countUpdatedEntities(); + if (success) { + return value; + } - // Attempt to replace the existing leaf - final boolean success = replaceImpl(key, value); - statistics.countUpdatedEntities(); - if (success) { - return value; + // We failed to find an existing leaf (dirty or clean). So throw an ISE. + throw new IllegalStateException("Can not replace value that is not in the map"); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); } - - // We failed to find an existing leaf (dirty or clean). So throw an ISE. - throw new IllegalStateException("Can not replace value that is not in the map"); } /** @@ -882,67 +902,71 @@ public V replace(final K key, final V value) { public V remove(final K key) { throwIfImmutable(); Objects.requireNonNull(key); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + // Verify whether the current leaf exists. If not, we can just return null. + VirtualLeafRecord leafToDelete = records.findLeafRecord(key, true); + if (leafToDelete == null) { + return null; + } - // Verify whether the current leaf exists. If not, we can just return null. - VirtualLeafRecord leafToDelete = records.findLeafRecord(key, true); - if (leafToDelete == null) { - return null; - } - - // Mark the leaf as being deleted. - cache.deleteLeaf(leafToDelete); - statistics.countRemovedEntities(); + // Mark the leaf as being deleted. + cache.deleteLeaf(leafToDelete); + statistics.countRemovedEntities(); - // We're going to need these - final long lastLeafPath = state.getLastLeafPath(); - final long firstLeafPath = state.getFirstLeafPath(); - final long leafToDeletePath = leafToDelete.getPath(); - - // If the leaf was not the last leaf, then move the last leaf to take this spot - if (leafToDeletePath != lastLeafPath) { - final VirtualLeafRecord lastLeaf = records.findLeafRecord(lastLeafPath, true); - assert lastLeaf != null; - cache.clearLeafPath(lastLeafPath); - lastLeaf.setPath(leafToDeletePath); - cache.putLeaf(lastLeaf); - // NOTE: at this point, if leafToDelete was in the cache at some "path" index, it isn't anymore! - // The lastLeaf has taken its place in the path index. - } + // We're going to need these + final long lastLeafPath = state.getLastLeafPath(); + final long firstLeafPath = state.getFirstLeafPath(); + final long leafToDeletePath = leafToDelete.getPath(); + + // If the leaf was not the last leaf, then move the last leaf to take this spot + if (leafToDeletePath != lastLeafPath) { + final VirtualLeafRecord lastLeaf = records.findLeafRecord(lastLeafPath, true); + assert lastLeaf != null; + cache.clearLeafPath(lastLeafPath); + lastLeaf.setPath(leafToDeletePath); + cache.putLeaf(lastLeaf); + // NOTE: at this point, if leafToDelete was in the cache at some "path" index, it isn't anymore! + // The lastLeaf has taken its place in the path index. + } - // If the parent of the last leaf is root, then we can simply do some bookkeeping. - // Otherwise, we replace the parent of the last leaf with the sibling of the last leaf, - // and mark it dirty. This covers all cases. - final long lastLeafParent = getParentPath(lastLeafPath); - if (lastLeafParent == ROOT_PATH) { - if (firstLeafPath == lastLeafPath) { - // We just removed the very last leaf, so set these paths to be invalid - state.setFirstLeafPath(INVALID_PATH); - state.setLastLeafPath(INVALID_PATH); + // If the parent of the last leaf is root, then we can simply do some bookkeeping. + // Otherwise, we replace the parent of the last leaf with the sibling of the last leaf, + // and mark it dirty. This covers all cases. + final long lastLeafParent = getParentPath(lastLeafPath); + if (lastLeafParent == ROOT_PATH) { + if (firstLeafPath == lastLeafPath) { + // We just removed the very last leaf, so set these paths to be invalid + state.setFirstLeafPath(INVALID_PATH); + state.setLastLeafPath(INVALID_PATH); + } else { + // We removed the second to last leaf, so the first & last leaf paths are now the same. + state.setLastLeafPath(FIRST_LEFT_PATH); + } } else { - // We removed the second to last leaf, so the first & last leaf paths are now the same. - state.setLastLeafPath(FIRST_LEFT_PATH); + final long lastLeafSibling = getSiblingPath(lastLeafPath); + final VirtualLeafRecord sibling = records.findLeafRecord(lastLeafSibling, true); + assert sibling != null; + cache.clearLeafPath(lastLeafSibling); + cache.deleteHash(lastLeafParent); + sibling.setPath(lastLeafParent); + cache.putLeaf(sibling); + + // Update the first & last leaf paths + state.setFirstLeafPath(lastLeafParent); // replaced by the sibling, it is now first + state.setLastLeafPath(lastLeafSibling - 1); // One left of the last leaf sibling + } + if (statistics != null) { + statistics.setSize(state.size()); } - } else { - final long lastLeafSibling = getSiblingPath(lastLeafPath); - final VirtualLeafRecord sibling = records.findLeafRecord(lastLeafSibling, true); - assert sibling != null; - cache.clearLeafPath(lastLeafSibling); - cache.deleteHash(lastLeafParent); - sibling.setPath(lastLeafParent); - cache.putLeaf(sibling); - - // Update the first & last leaf paths - state.setFirstLeafPath(lastLeafParent); // replaced by the sibling, it is now first - state.setLastLeafPath(lastLeafSibling - 1); // One left of the last leaf sibling - } - if (statistics != null) { - statistics.setSize(state.size()); - } - // Get the value and return it (as read only). - final V value = leafToDelete.getValue(); - //noinspection unchecked - return value == null ? null : (V) value.asReadOnly(); + // Get the value and return it (as read only). + final V value = leafToDelete.getValue(); + //noinspection unchecked + return value == null ? null : (V) value.asReadOnly(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); + } } /* From be962a04717dcc88266a79402286290d1e921cc2 Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:09:54 -0600 Subject: [PATCH 2/9] feat: design roster interfaces (#10428) Signed-off-by: Edward Wertz --- .../roster/roster-datastructures-and-apis.md | 31 ++++ .../com/swirlds/platform/roster/Roster.java | 69 ++++++++ .../swirlds/platform/roster/RosterEntry.java | 73 ++++++++ .../roster/legacy/AddressBookRoster.java | 162 ++++++++++++++++++ .../roster/legacy/AddressRosterEntry.java | 162 ++++++++++++++++++ .../roster/legacy/AddressBookRosterTests.java | 84 +++++++++ .../system/address/AddressBookTests.java | 5 +- 7 files changed, 582 insertions(+), 4 deletions(-) create mode 100644 platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java create mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java diff --git a/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md b/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md new file mode 100644 index 000000000000..d3eceaaafb7a --- /dev/null +++ b/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md @@ -0,0 +1,31 @@ +# Roster APIs + +The following roster api is reduced from the address book to just the fields that are needed by the platform to establish mutual TLS connections, gossip, validate events and state, come to consensus, and detect an ISS. + +The data for each node is contained in the node's `RosterEntry`. + +## Roster Interfaces + +### RosterEntry + +```java +public interface RosterEntry extends SelfSerializable { + NodeId getNodeId(); + long getWeight(); + String getHostname(); + int getPort(); + PublicKey getSigningPublicKey(); + X509Certificate getSigningCertificate(); + boolean isZeroWeight(); +} +``` +### Roster + +```java +public interface Roster extends Iterable, SelfSerializable{ + int size(); + boolean contains(NodeId nodeId); + RosterEntry getEntry(NodeId nodeId); + long getTotalWeight(); +} +``` \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java new file mode 100644 index 000000000000..5e14a90e2e9a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 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.swirlds.platform.roster; + +import com.swirlds.common.io.SelfSerializable; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Collection; + +/** + * A roster is the set of nodes that are creating events and contributing to consensus. The data in a Roster object is + * immutable must not change over time. + */ +public interface Roster extends Iterable, SelfSerializable { + + /** + * @return a collection of all unique nodeIds in the roster. + */ + @NonNull + Collection getNodeIds(); + + /** + * @param nodeId the nodeId of the {@link RosterEntry} to get + * @return the RosterEntry with the given nodeId + * @throws java.util.NoSuchElementException if the nodeId is not in the roster + */ + @NonNull + RosterEntry getEntry(@NonNull NodeId nodeId); + + /** + * @param nodeId the nodeId to check for membership in the roster + * @return true if there is a rosterEntry with the given nodeId, false otherwise + */ + default boolean contains(@NonNull NodeId nodeId) { + return getNodeIds().contains(nodeId); + } + + /** + * @return the total number of nodes in the roster + */ + default int getSize() { + return getNodeIds().size(); + } + + /** + * @return the total weight of all nodes in the roster + */ + default long getTotalWeight() { + long totalWeight = 0; + for (final RosterEntry entry : this) { + totalWeight += entry.getWeight(); + } + return totalWeight; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java new file mode 100644 index 000000000000..0f15497f6d71 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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.swirlds.platform.roster; + +import com.swirlds.common.io.SelfSerializable; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.PublicKey; +import java.security.cert.X509Certificate; + +/** + * A RosterEntry is a single node in the roster. It contains the node's ID, weight, network address, and public signing + * key in the form of an X509Certificate. The data in a RosterEntry object is immutable and must not change over time. + */ +public interface RosterEntry extends SelfSerializable { + + /** + * @return the ID of the node + */ + @NonNull + NodeId getNodeId(); + + /** + * @return the non-negative consensus weight of the node + */ + long getWeight(); + + /** + * @return the hostname portion of a node's gossip endpoint. + */ + @NonNull + String getHostname(); + + /** + * @return the port portion of a node's gossip endpoint. + */ + int getPort(); + + /** + * @return the X509Certificate containing the public signing key of the node + */ + @NonNull + X509Certificate getSigningCertificate(); + + /** + * @return the public signing key of the node + */ + @NonNull + default PublicKey getSigningPublicKey() { + return getSigningCertificate().getPublicKey(); + } + + /** + * @return true if the weight is zero, false otherwise + */ + default boolean isZeroWeight() { + return getWeight() == 0; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java new file mode 100644 index 000000000000..d7833dd708f7 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 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.swirlds.platform.roster.legacy; + +import com.swirlds.base.utility.ToStringBuilder; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.Roster; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * A {@link Roster} implementation that uses an {@link AddressBook} as its backing data structure. + */ +public class AddressBookRoster implements Roster { + private static final long CLASS_ID = 0x7104f97d4e298619L; + + private static final class ClassVersion { + public static final int ORIGINAL = 1; + } + + private final Map entries = new HashMap<>(); + private List nodeOrder; + + /** + * Constructs a new {@link AddressBookRoster} from the given {@link AddressBook} and {@link KeysAndCerts} map. + * + * @param addressBook the address book + * @param keysAndCertsMap the keys and certs map + */ + public AddressBookRoster( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCertsMap) { + Objects.requireNonNull(addressBook); + Objects.requireNonNull(keysAndCertsMap); + + for (final Address address : addressBook) { + entries.put(address.getNodeId(), new AddressRosterEntry(address, keysAndCertsMap.get(address.getNodeId()))); + } + + nodeOrder = entries.keySet().stream().sorted().toList(); + } + + /** + * Empty constructor for deserialization. + */ + public AddressBookRoster() { + nodeOrder = new ArrayList<>(); + } + + @Override + public long getClassId() { + return CLASS_ID; + } + + @Override + public int getVersion() { + return ClassVersion.ORIGINAL; + } + + @Override + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { + out.writeInt(entries.size()); + for (final RosterEntry entry : this) { + out.writeSerializable(entry, true); + } + } + + @Override + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final RosterEntry entry = in.readSerializable(); + entries.put(entry.getNodeId(), entry); + } + nodeOrder = entries.keySet().stream().sorted().toList(); + } + + @Override + @NonNull + public Collection getNodeIds() { + return nodeOrder; + } + + @Override + @NonNull + public RosterEntry getEntry(@NonNull final NodeId nodeId) { + Objects.requireNonNull(nodeId); + final RosterEntry entry = entries.get(nodeId); + if (entry == null) { + throw new NoSuchElementException("No entry found for nodeId " + nodeId); + } + return entry; + } + + @Override + @NonNull + public Iterator iterator() { + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < nodeOrder.size(); + } + + @Override + public RosterEntry next() { + return entries.get(nodeOrder.get(index++)); + } + }; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AddressBookRoster that = (AddressBookRoster) o; + return Objects.equals(entries, that.entries); + } + + @Override + public int hashCode() { + return Objects.hash(entries); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("entries", entries).toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java new file mode 100644 index 000000000000..24eb86a7cd45 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 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.swirlds.platform.roster.legacy; + +import com.swirlds.base.utility.ToStringBuilder; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * An {@link Address} wrapper that implements the {@link RosterEntry} interface. + */ +public class AddressRosterEntry implements RosterEntry { + + private static final long CLASS_ID = 0x4e700e352be188aaL; + + private static final class ClassVersion { + public static final int ORIGINAL = 1; + } + + private static final int ENCODED_CERT_MAX_SIZE = 8192; + + private Address address; + private X509Certificate sigCert; + + /** + * Constructs a new {@link AddressRosterEntry} from the given {@link Address} and {@link KeysAndCerts}. + * + * @param address the address + * @param keysAndCerts the keys and certs containing the signing certificate + */ + public AddressRosterEntry(@NonNull final Address address, @NonNull final KeysAndCerts keysAndCerts) { + Objects.requireNonNull(address); + Objects.requireNonNull(keysAndCerts); + + this.address = address; + this.sigCert = keysAndCerts.sigCert(); + } + + /** + * Empty constructor for deserialization. + */ + public AddressRosterEntry() {} + + /** + * {@inheritDoc} + */ + @Override + public long getClassId() { + return CLASS_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public int getVersion() { + return ClassVersion.ORIGINAL; + } + + @Override + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { + out.writeSerializable(address, false); + try { + out.writeByteArray(sigCert.getEncoded()); + } catch (final CertificateEncodingException e) { + throw new IOException("Could not encode certificate", e); + } + } + + @Override + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { + address = in.readSerializable(false, Address::new); + final byte[] encodedCert = in.readByteArray(ENCODED_CERT_MAX_SIZE); + try { + sigCert = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (final CertificateException e) { + throw new IOException("Could not decode certificate", e); + } + } + + @Override + @NonNull + public NodeId getNodeId() { + return address.getNodeId(); + } + + @Override + public long getWeight() { + return address.getWeight(); + } + + @NonNull + @Override + public String getHostname() { + return Objects.requireNonNullElse(address.getHostnameExternal(), ""); + } + + @Override + public int getPort() { + return address.getPortExternal(); + } + + @NonNull + @Override + public X509Certificate getSigningCertificate() { + return sigCert; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AddressRosterEntry that = (AddressRosterEntry) o; + return Objects.equals(address, that.address) && Objects.equals(sigCert, that.sigCert); + } + + @Override + public int hashCode() { + return Objects.hash(address, sigCert); + } + + @Override + public String toString() { + + return new ToStringBuilder(this) + .append("address", address) + .append("sigCert", sigCert) + .toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java new file mode 100644 index 000000000000..26a06b406d81 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.swirlds.platform.roster.legacy; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.Roster; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class AddressBookRosterTests { + + @DisplayName("Serialize and deserialize AddressBook derived Roster") + @ParameterizedTest + @MethodSource({"com.swirlds.platform.crypto.CryptoArgsProvider#basicTestArgs"}) + void serializeDeserializeTest( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCerts) + throws IOException, ConstructableRegistryException { + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); + final Roster roster = new AddressBookRoster(addressBook, keysAndCerts); + + final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + final SerializableDataOutputStream out = new SerializableDataOutputStream(byteOut); + + out.writeSerializable(roster, true); + + final SerializableDataInputStream in = + new SerializableDataInputStream(new ByteArrayInputStream(byteOut.toByteArray())); + + Roster roster2 = in.readSerializable(); + assertEquals(roster, roster2); + } + + @DisplayName("Roster derived from AddressBook") + @ParameterizedTest + @MethodSource({"com.swirlds.platform.crypto.CryptoArgsProvider#basicTestArgs"}) + void addressBookRosterTest( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCerts) { + final Roster roster = new AddressBookRoster(addressBook, keysAndCerts); + final Iterator entries = roster.iterator(); + for (int i = 0; i < addressBook.getSize(); i++) { + final NodeId nodeId = addressBook.getNodeId(i); + final Address address = addressBook.getAddress(nodeId); + final RosterEntry rosterEntry = entries.next(); + assertEquals(address.getHostnameExternal(), rosterEntry.getHostname()); + assertEquals(address.getPortExternal(), rosterEntry.getPort()); + assertEquals(address.getNodeId(), rosterEntry.getNodeId()); + assertEquals(address.getWeight(), rosterEntry.getWeight()); + assertEquals(address.getSigPublicKey(), rosterEntry.getSigningPublicKey()); + } + assertFalse(entries.hasNext()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java index 5a6510d78b95..db64b8ec7845 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java @@ -18,7 +18,6 @@ import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.platform.system.address.AddressBookUtils.parseAddressBookText; -import static com.swirlds.test.framework.TestQualifierTags.TIME_CONSUMING; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -48,7 +47,6 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @DisplayName("AddressBook Tests") @@ -329,9 +327,8 @@ void atoStringSanityTest() { @Test @DisplayName("Serialization Test") - @Tag(TIME_CONSUMING) void serializationTest() throws IOException, ConstructableRegistryException { - ConstructableRegistry.getInstance().registerConstructables("com.swirlds.common.system"); + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); final AddressBook original = new RandomAddressBookGenerator(getRandomPrintSeed()) .setSize(100) From 272983d0c2b09972e0e1470d8b5177564e4c88c7 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:32:44 -0600 Subject: [PATCH 3/9] chore: Fix remaining `Crypto` tests (#10500) Signed-off-by: Neeharika-Sompalli --- .../app/workflows/ingest/IngestChecker.java | 40 ++++++++--- .../scope/HandleHederaNativeOperations.java | 8 ++- .../src/main/java/module-info.java | 3 +- .../HandleHederaNativeOperationsTest.java | 5 +- .../impl/validators/AllowanceValidator.java | 9 +-- .../crypto/AutoAccountCreationSuite.java | 1 + .../suites/leaky/LeakyCryptoTestsSuite.java | 69 +++++++++++-------- .../src/main/resource/bootstrap.properties | 2 +- 8 files changed, 88 insertions(+), 49 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java index 0146342d40b3..d3a8f362646a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java @@ -29,22 +29,26 @@ import static com.hedera.node.app.spi.HapiUtils.isHollow; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; +import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.SignaturePair; import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.annotations.NodeSelfId; import com.hedera.node.app.fees.FeeContextImpl; import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.info.CurrentPlatformStatus; +import com.hedera.node.app.service.evm.utils.EthSigsUtils; import com.hedera.node.app.signature.DefaultKeyVerifier; import com.hedera.node.app.signature.ExpandedSignaturePair; import com.hedera.node.app.signature.SignatureExpander; import com.hedera.node.app.signature.SignatureVerifier; import com.hedera.node.app.spi.authorization.Authorizer; import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.HederaState; @@ -55,6 +59,8 @@ import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; import com.hedera.node.config.data.HederaConfig; +import com.hedera.node.config.data.LazyCreationConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -229,16 +235,32 @@ private void verifyPayerSignature( signatureExpander.expand(sigPairs, expandedSigs); if (!isHollow(payer)) { signatureExpander.expand(payerKey, sigPairs, expandedSigs); + } else { + // If the payer is hollow, then we need to expand the signature for the payer + final var originals = txInfo.signatureMap().sigPairOrElse(emptyList()).stream() + .filter(SignaturePair::hasEcdsaSecp256k1) + .filter(pair -> Bytes.wrap(EthSigsUtils.recoverAddressFromPubKey( + pair.pubKeyPrefix().toByteArray())) + .equals(payer.alias())) + .findFirst(); + validateTruePreCheck(originals.isPresent(), INVALID_SIGNATURE); + validateTruePreCheck( + configuration.getConfigData(LazyCreationConfig.class).enabled(), INVALID_SIGNATURE); + signatureExpander.expand(List.of(originals.get()), expandedSigs); + } - // Verify the signatures - final var results = signatureVerifier.verify(txInfo.signedBytes(), expandedSigs); - final var verifier = new DefaultKeyVerifier(sigPairs.size(), hederaConfig, results); - final var payerKeyVerification = verifier.verificationFor(payerKey); - - // This can happen if the signature map was missing a signature for the payer account. - if (payerKeyVerification.failed()) { - throw new PreCheckException(INVALID_SIGNATURE); - } + // Verify the signatures + final var results = signatureVerifier.verify(txInfo.signedBytes(), expandedSigs); + final var verifier = new DefaultKeyVerifier(sigPairs.size(), hederaConfig, results); + final SignatureVerification payerKeyVerification; + if (!isHollow(payer)) { + payerKeyVerification = verifier.verificationFor(payerKey); + } else { + payerKeyVerification = verifier.verificationFor(payer.alias()); + } + // This can happen if the signature map was missing a signature for the payer account. + if (payerKeyVerification.failed()) { + throw new PreCheckException(INVALID_SIGNATURE); } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index b2496737a86e..5bba8ec06890 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.LAZY_CREATION_MEMO; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static java.util.Objects.requireNonNull; @@ -103,9 +104,10 @@ public void setNonce(final long contractNumber, final long nonce) { // Note the use of the null "verification assistant" callback; we don't want any // signing requirements enforced for this synthetic transaction try { - return context.dispatchRemovablePrecedingTransaction( - synthTxn, CryptoCreateRecordBuilder.class, null, context.payer()) - .status(); + final var childRecordBuilder = context.dispatchRemovablePrecedingTransaction( + synthTxn, CryptoCreateRecordBuilder.class, null, context.payer()); + childRecordBuilder.memo(LAZY_CREATION_MEMO); + return childRecordBuilder.status(); } catch (final HandleException e) { // It is critically important we don't let HandleExceptions propagate to the workflow because // it doesn't rollback for contract operations so we can commit gas charges; that is, the diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index fef550881971..b71a00b773e8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -51,7 +51,8 @@ com.hedera.node.app.service.contract.impl.test; exports com.hedera.node.app.service.contract.impl.exec.failure to - com.hedera.node.app.service.contract.impl.test; + com.hedera.node.app.service.contract.impl.test, + com.hedera.node.app.service.contract.impl.test.exec.scope; exports com.hedera.node.app.service.contract.impl.exec; exports com.hedera.node.app.service.contract.impl.exec.operations to com.hedera.node.app.service.contract.impl.test; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index 40f30c3f6f7e..f8871dfa84c8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -45,6 +45,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.Key; @@ -150,9 +151,9 @@ void createsHollowAccountByDispatching() { .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); - given(context.dispatchRemovablePrecedingTransaction( + when(context.dispatchRemovablePrecedingTransaction( eq(synthTxn), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) - .willReturn(cryptoCreateRecordBuilder); + .thenReturn(cryptoCreateRecordBuilder); given(cryptoCreateRecordBuilder.status()).willReturn(OK); final var status = subject.createHollowAccount(CANONICAL_ALIAS); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java index 1152dcccd1ad..7834a7bb0e28 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java @@ -70,13 +70,10 @@ protected void validateSerialNums( */ public static int aggregateApproveNftAllowances(final List nftAllowances) { int nftAllowancesTotal = 0; - final var setOfSerials = new HashSet(); - for (final var allowances : nftAllowances) { - setOfSerials.addAll(allowances.serialNumbers()); - if (!setOfSerials.isEmpty()) { - nftAllowancesTotal += setOfSerials.size(); - setOfSerials.clear(); + // each serial is counted as an allowance + if (!allowances.serialNumbers().isEmpty()) { + nftAllowancesTotal += allowances.serialNumbers().size(); } else { nftAllowancesTotal++; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index d1b43a6094dd..0cf5a7608c01 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -506,6 +506,7 @@ final HapiSpec canAutoCreateWithNftTransferToEvmAddress() { recordWith().status(SUCCESS).consensusTimeImpliedByNonce(parentConsTime.get(), -1)))); } + @HapiTest final HapiSpec multipleTokenTransfersSucceed() { final var initialTokenSupply = 1000; final var multiTokenXfer = "multiTokenXfer"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 004700de17b1..838ebbed246c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -70,11 +70,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOfDeferred; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.remembering; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; @@ -121,6 +119,7 @@ import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoTransfer; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoUpdate; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ALIAS_ALREADY_ASSIGNED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE; @@ -208,7 +207,7 @@ public List getSpecsInSuite() { hollowAccountCompletionWithEthereumTransaction(), hollowAccountCreationChargesExpectedFees(), lazyCreateViaEthereumCryptoTransfer(), - hollowAccountCompletionWithSimultaniousPropertiesUpdate(), + hollowAccountCompletionWithSimultaneousPropertiesUpdate(), contractDeployAfterEthereumTransferLazyCreate(), contractCallAfterEthereumTransferLazyCreate(), autoAssociationPropertiesWorkAsExpected(), @@ -217,6 +216,7 @@ public List getSpecsInSuite() { customFeesHaveExpectedAutoCreateInteractions()); } + @HapiTest final HapiSpec autoAssociationPropertiesWorkAsExpected() { final var minAutoRenewPeriodPropertyName = "ledger.autoRenewPeriod.minDuration"; final var maxAssociationsPropertyName = "ledger.maxAutoAssociations"; @@ -252,6 +252,7 @@ final HapiSpec autoAssociationPropertiesWorkAsExpected() { validateChargedUsd(updateWithExpiredAccount, plusTenSlotsFee)); } + @HapiTest final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButServiceFee() { final var civilian = "civilian"; final var creation = "creation"; @@ -306,10 +307,11 @@ final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButSe // because this fails depending on the previous operation reaching // consensus before the current operation or after, since we have added // deferStatusResolution - .hasPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE) - .hasKnownStatus(INSUFFICIENT_PAYER_BALANCE))); + .hasPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE) + .hasKnownStatusFrom(INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE))); } + // Cannot be enabled as HapiTest because long term schedule transactions is not implemented in mod service final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { return defaultHapiSpec("ScheduledCryptoApproveAllowanceWaitForExpiryTrue") .given( @@ -388,11 +390,11 @@ final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { getTokenNftInfo(NON_FUNGIBLE_TOKEN, 2L).hasSpenderID(SPENDER)); } + // @HapiTest // will be enabled in next PR final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled") + return propertyPreservingHapiSpec("txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled") + .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED), overridingTwo( LAZY_CREATION_ENABLED, FALSE_VALUE, CRYPTO_CREATE_WITH_ALIAS_ENABLED, FALSE_VALUE), @@ -434,9 +436,10 @@ final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled( .has(accountWith().key(SECP_256K1_SOURCE_KEY).noAlias()); allRunFor(spec, op, op2, op3, op4, op5, op6, op7, hapiGetAccountInfo); })) - .then(overridingAllOfDeferred(() -> startingProps)); + .then(); } + // Cannot be enabled as HapiTest because tokens.maxPerAccount is not being used in mod service final HapiSpec maxAutoAssociationSpec() { final int MONOGAMOUS_NETWORK = 1; final int maxAutoAssociations = 100; @@ -460,6 +463,7 @@ final HapiSpec maxAutoAssociationSpec() { overriding("tokens.maxPerAccount", "" + ADVENTUROUS_NETWORK)); } + // Cannot be enabled as HapiTest because expiration is not implemented in mod service public HapiSpec canDissociateFromMultipleExpiredTokens() { final var civilian = "civilian"; final long initialSupply = 100L; @@ -496,6 +500,7 @@ public HapiSpec canDissociateFromMultipleExpiredTokens() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } + @HapiTest final HapiSpec cannotExceedAccountAllowanceLimit() { return defaultHapiSpec("CannotExceedAccountAllowanceLimit") .given( @@ -563,11 +568,11 @@ final HapiSpec cannotExceedAccountAllowanceLimit() { HEDERA_ALLOWANCES_MAX_ACCOUNT_LIMIT, "100")); } + @HapiTest final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("CreateAnAccountWithEVMAddressAliasAndECKey") + return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddressAliasAndECKey") + .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED), overridingTwo(LAZY_CREATION_ENABLED, TRUE_VALUE, CRYPTO_CREATE_WITH_ALIAS_ENABLED, TRUE_VALUE), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(withOpContext((spec, opLog) -> { @@ -619,9 +624,10 @@ final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { getTxnRecord("createTxn").hasPriority(recordWith().hasNoAlias()); allRunFor(spec, hapiGetAccountInfo, hapiGetAnotherAccountInfo, getTxnRecord); })) - .then(overridingAllOfDeferred(() -> startingProps)); + .then(); } + @HapiTest final HapiSpec createAnAccountWithEVMAddress() { return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddress") .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) @@ -643,6 +649,7 @@ final HapiSpec createAnAccountWithEVMAddress() { .then(); } + @HapiTest final HapiSpec cannotExceedAllowancesTransactionLimit() { return defaultHapiSpec("CannotExceedAllowancesTransactionLimit") .given( @@ -691,11 +698,13 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addCryptoAllowance(OWNER, SECOND_SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L)) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L, 1L, 1L, 1L, 1L)) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addCryptoAllowance(OWNER, SPENDER, 100L) @@ -703,7 +712,8 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addCryptoAllowance(OWNER, SPENDER, 100L) .addCryptoAllowance(OWNER, SPENDER, 200L) .addCryptoAllowance(OWNER, SPENDER, 200L) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) @@ -711,7 +721,8 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED)) + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED)) .then( // reset overridingTwo( @@ -719,11 +730,11 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { HEDERA_ALLOWANCES_MAX_ACCOUNT_LIMIT, "100")); } + @HapiTest final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("HollowAccountCompletionNotAcceptedWhenFlagIsDisabled") + return propertyPreservingHapiSpec("HollowAccountCompletionNotAcceptedWhenFlagIsDisabled") + .preserving(LAZY_CREATION_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED), overriding(LAZY_CREATION_ENABLED, TRUE), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(LAZY_CREATE_SPONSOR).balance(INITIAL_BALANCE * ONE_HBAR), @@ -765,6 +776,7 @@ final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { })); } + @HapiTest final HapiSpec hollowAccountCreationChargesExpectedFees() { final long REDUCED_NODE_FEE = 2L; final long REDUCED_NETWORK_FEE = 3L; @@ -831,13 +843,13 @@ final HapiSpec hollowAccountCreationChargesExpectedFees() { })) .then(uploadDefaultFeeSchedules(GENESIS)); } - + // @HapiTest /// will be enabled after EthereumTransaction hollow account finalization is implemented final HapiSpec hollowAccountCompletionWithEthereumTransaction() { final Map startingProps = new HashMap<>(); final String CONTRACT = "Fuse"; - return defaultHapiSpec("HollowAccountCompletionWithEthereumTransaction") + return propertyPreservingHapiSpec("HollowAccountCompletionWithEthereumTransaction") + .preserving(LAZY_CREATION_ENABLED, CHAIN_ID_PROP) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CHAIN_ID_PROP), overridingTwo(LAZY_CREATION_ENABLED, TRUE, CHAIN_ID_PROP, "298"), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), @@ -881,6 +893,7 @@ final HapiSpec hollowAccountCompletionWithEthereumTransaction() { })); } + @HapiTest final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -930,6 +943,7 @@ final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { })); } + @HapiTest final HapiSpec contractCallAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -979,6 +993,7 @@ final HapiSpec contractCallAfterEthereumTransferLazyCreate() { })); } + @HapiTest final HapiSpec lazyCreateViaEthereumCryptoTransfer() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -1062,7 +1077,8 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { })); } - final HapiSpec hollowAccountCompletionWithSimultaniousPropertiesUpdate() { + @HapiTest + final HapiSpec hollowAccountCompletionWithSimultaneousPropertiesUpdate() { return propertyPreservingHapiSpec("hollowAccountCompletionWithSimultaniousPropertiesUpdate") .preserving(LAZY_CREATION_ENABLED) .given( @@ -1092,14 +1108,13 @@ final HapiSpec hollowAccountCompletionWithSimultaniousPropertiesUpdate() { .then(withOpContext((spec, opLog) -> { final var op2 = fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(LAZY_CREATION_ENABLED, "" + FALSE)) - .deferStatusResolution(); + .overridingProps(Map.of(LAZY_CREATION_ENABLED, "" + FALSE)); final var op3 = cryptoTransfer( tinyBarsFromTo(LAZY_CREATE_SPONSOR, CRYPTO_TRANSFER_RECEIVER, ONE_HUNDRED_HBARS)) .payingWith(SECP_256K1_SOURCE_KEY) .sigMapPrefixes(uniqueWithFullPrefixesFor(SECP_256K1_SOURCE_KEY)) - .hasPrecheck(OK) + .hasPrecheckFrom(OK, INVALID_SIGNATURE) .hasKnownStatus(INVALID_PAYER_SIGNATURE) .via(TRANSFER_TXN_2); diff --git a/hedera-node/test-clients/src/main/resource/bootstrap.properties b/hedera-node/test-clients/src/main/resource/bootstrap.properties index 0650df1c2b59..83ef79f71414 100644 --- a/hedera-node/test-clients/src/main/resource/bootstrap.properties +++ b/hedera-node/test-clients/src/main/resource/bootstrap.properties @@ -52,7 +52,7 @@ hedera.workflows.enabled= accounts.maxNumber=20000000 autoCreation.enabled=true lazyCreation.enabled=true -cryptoCreateWithAlias.enabled=false +cryptoCreateWithAlias.enabled=true entities.limitTokenAssociations=false balances.exportDir.path=/opt/hgcapp/accountBalances/ balances.exportEnabled=false From 862912b656bd5439f0ae8387e2b7978bfe2c237f Mon Sep 17 00:00:00 2001 From: Valentin Valkanov Date: Wed, 20 Dec 2023 20:58:59 +0200 Subject: [PATCH 4/9] fix: getErc20TokenNameExceedingLimits and getErc721TokenURIFromErc20TokenFails tests (#10568) Signed-off-by: Valentin Valkanov --- .../failure/CustomExceptionalHaltReason.java | 6 ++++++ .../hts/tokenuri/TokenUriCall.java | 8 ++++++++ .../hts/tokenuri/TokenUriCallTest.java | 17 +++++++++++++++++ .../contract/precompile/ERCPrecompileSuite.java | 1 + .../suites/leaky/LeakyContractTestsSuite.java | 1 + 5 files changed, 33 insertions(+) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java index 29483b6ab390..e28f2957ef59 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java @@ -20,6 +20,7 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import edu.umd.cs.findbugs.annotations.NonNull; +import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; public enum CustomExceptionalHaltReason implements ExceptionalHaltReason { @@ -76,6 +77,11 @@ public static ResponseCodeEnum statusFor(@NonNull final ExceptionalHaltReason re public static String errorMessageFor(@NonNull final ExceptionalHaltReason reason) { requireNonNull(reason); + // #10568 - We add this check to match mono behavior + if (reason == CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS) { + return Bytes.of(ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED.name().getBytes()) + .toHexString(); + } return reason.toString(); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java index 108e515cd91f..beb01ec40a45 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java @@ -16,9 +16,12 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -49,6 +52,11 @@ public TokenUriCall( @Override protected @NonNull FullResult resultOfViewingNft(@NonNull final Token token, final Nft nft) { requireNonNull(token); + // #10568 - We add this check to match mono behavior + if (token.tokenType() == TokenType.FUNGIBLE_COMMON) { + return revertResult(ResponseCodeEnum.INVALID_TOKEN_ID, gasCalculator.viewGasRequirement()); + } + String metadata; if (nft != null) { metadata = new String(nft.metadata().toByteArray()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java index af567762bcac..0eb4917aed5b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java @@ -17,12 +17,14 @@ package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.tokenuri; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CIVILIAN_OWNED_NFT; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NFT_SERIAL_NO; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_ID; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriTranslator; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; @@ -50,4 +52,19 @@ void returnsUnaliasedOwnerLongZeroForPresentTokenAndNonTreasuryNft() { .array()), result.getOutput()); } + + @Test + void revertsWhenTokenIsNotERC721() { + // given + subject = new TokenUriCall(gasCalculator, mockEnhancement(), FUNGIBLE_TOKEN, NFT_SERIAL_NO); + given(nativeOperations.getNft(FUNGIBLE_TOKEN.tokenId().tokenNum(), NFT_SERIAL_NO)) + .willReturn(null); + + // when + final var result = subject.execute().fullResult().result(); + + // then + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(Bytes.wrap(ResponseCodeEnum.INVALID_TOKEN_ID.name().getBytes()), result.getOutput()); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java index bb2bb3e81e55..13901687365f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java @@ -1193,6 +1193,7 @@ ERC_721_CONTRACT, OWNER_OF, asHeadlongAddress(tokenAddr.get()), BigInteger.ONE) } // Expects revert + @HapiTest final HapiSpec getErc721TokenURIFromErc20TokenFails() { final var invalidTokenURITxn = "tokenURITxnFromErc20"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 3683261a8e02..77022b60e0c3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -829,6 +829,7 @@ final HapiSpec transferFailsWithIncorrectAmounts() { childRecordsCheck(transferTokenWithNegativeAmountTxn, CONTRACT_REVERT_EXECUTED)); } + @HapiTest final HapiSpec getErc20TokenNameExceedingLimits() { final var REDUCED_NETWORK_FEE = 1L; final var REDUCED_NODE_FEE = 1L; From 740b25b0c71e77ba1b3133c39683299d281d4465 Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:10:17 -0300 Subject: [PATCH 5/9] fix: 10535 add enum converter (#10557) Signed-off-by: Maxi Tartaglia --- .../config/impl/converters/EnumConverter.java | 57 ++++++++ .../impl/internal/ConverterService.java | 44 +++--- .../impl/converters/EnumConverterTest.java | 90 ++++++++++++ .../impl/internal/ConverterServiceTest.java | 130 ++++++++++++++++++ 4 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java create mode 100644 platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java create mode 100644 platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java new file mode 100644 index 000000000000..f41f5d8fc1ae --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.swirlds.config.impl.converters; + +import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; + +/** + * Implementation of {@code ConfigConverter} specifically for converting enums + */ +public class EnumConverter> implements ConfigConverter { + + private final Class valueType; + + /** + * @param valueType enum type this converter is associated to + * @throws NullPointerException if {@code valueType} is {@code null} + */ + public EnumConverter(@NonNull Class valueType) { + this.valueType = Objects.requireNonNull(valueType, "valueType must not be null"); + } + + /** + * @param value String value to be converted to a valid enum instance + * @return the enum instance associated with {@code value} + * @throws IllegalArgumentException if value is not a valid enum value for {@code valueType} + * @throws NullPointerException if {@code value} is {@code null} + */ + @Nullable + @Override + public T convert(@NonNull String value) throws IllegalArgumentException, NullPointerException { + try { + return Enum.valueOf(this.valueType, Objects.requireNonNull(value, "value must not be null")); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException( + "Can not convert value '%s' of Enum '%s' by default. Please add a custom config converter." + .formatted(value, this.valueType), + e); + } + } +} diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java index dbadb477e607..9fe15e26e24e 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java @@ -24,6 +24,7 @@ import com.swirlds.config.impl.converters.ChronoUnitConverter; import com.swirlds.config.impl.converters.DoubleConverter; import com.swirlds.config.impl.converters.DurationConverter; +import com.swirlds.config.impl.converters.EnumConverter; import com.swirlds.config.impl.converters.FileConverter; import com.swirlds.config.impl.converters.FloatConverter; import com.swirlds.config.impl.converters.IntegerConverter; @@ -47,9 +48,9 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; class ConverterService implements ConfigLifecycle { private final Map, ConfigConverter> converters; @@ -94,7 +95,7 @@ class ConverterService implements ConfigLifecycle { private static final ConfigConverter CHRONO_UNIT_CONVERTER = new ChronoUnitConverter(); ConverterService() { - this.converters = new HashMap<>(); + this.converters = new ConcurrentHashMap<>(); } @NonNull @@ -115,7 +116,6 @@ private > Class getConverterType(@NonNull fin .orElseGet(() -> getConverterType((Class) converterClass.getSuperclass())); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Nullable T convert(@Nullable final String value, @NonNull final Class targetClass) { throwIfNotInitialized(); @@ -126,20 +126,7 @@ T convert(@Nullable final String value, @NonNull final Class targetClass) if (Objects.equals(targetClass, String.class)) { return (T) value; } - final ConfigConverter converter = (ConfigConverter) converters.get(targetClass); - - if (converter == null && targetClass.isEnum()) { - // FUTURE WORK: once logging is added to this module, log a warning here - // ("No converter defined for type '" + targetClass + "'. Converting using backup enum converter."); - try { - return (T) Enum.valueOf((Class) targetClass, value); - } catch (final IllegalArgumentException e) { - throw new IllegalArgumentException( - "Can not convert value '%s' of Enum '%s' by default. Please add a custom config converter." - .formatted(value, targetClass), - e); - } - } + final ConfigConverter converter = getOrAdConverter(targetClass); if (converter == null) { throw new IllegalArgumentException("No converter defined for type '" + targetClass + "'"); @@ -216,11 +203,30 @@ public boolean isInitialized() { return initialized; } - @SuppressWarnings("unchecked") @Nullable ConfigConverter getConverterForType(@NonNull final Class valueType) { throwIfNotInitialized(); Objects.requireNonNull(valueType, "valueType must not be null"); - return (ConfigConverter) converters.get(valueType); + return getOrAdConverter(valueType); + } + + /** + * @param valueType type to convert to + * @return + *
    + *
  • the previously configured {@code ConfigConverter} if exist for {@code valueType}
  • + *
  • a new instance of {@code EnumConverter} if {@code valueType} is an enum + * and no {@code ConfigConverter} was found
  • + *
  • {@code null} otherwise
  • + *
+ */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private ConfigConverter getOrAdConverter(@NonNull Class valueType) { + ConfigConverter converter = (ConfigConverter) converters.get(valueType); + + if (converter == null && valueType.isEnum()) { + return (ConfigConverter) converters.computeIfAbsent(valueType, c -> new EnumConverter(c)); + } + return converter; } } diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java new file mode 100644 index 000000000000..5cc3ab549585 --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 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.swirlds.config.impl.converters; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class EnumConverterTest { + + @Test + public void itFailsWhenConstructingWithNull() { + // given + Supplier> supplier = () -> new EnumConverter<>(null); + // then + Assertions.assertThrows(NullPointerException.class, supplier::get); + } + + @Test + public void itFailsWhenConvertingNull() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + // then + Assertions.assertThrows(NullPointerException.class, () -> converter.convert(null)); + } + + @Test + public void itSucceedsWhenConvertingAValidEnumValue() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + + // when + final NumberEnum value = converter.convert("ONE"); + + // then + Assertions.assertEquals(NumberEnum.ONE, value); + } + + @Test + public void itFailsWhenConvertingInvalidEnumValue() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + + // then + Assertions.assertThrows(IllegalArgumentException.class, () -> converter.convert("")); + } + + @Test + void itSuccessfullyConvertsValidEnumValueWithSpecialChar() { + // given + final EnumConverter converter = new EnumConverter<>(SpecialCharacterEnum.class); + + // then: + Assertions.assertEquals(SpecialCharacterEnum.Ñ, converter.convert("Ñ")); + } + + @ParameterizedTest + @ValueSource(strings = {"One", "one", "onE", "oNe", " ONE", "ONE ", "DOS", "OnE", "null"}) + void itFailsConvertingInvalidEnumValues(final String param) { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + // then + Assertions.assertThrows(IllegalArgumentException.class, () -> converter.convert(param)); + } + + private enum NumberEnum { + ONE, + TWO + } + + private enum SpecialCharacterEnum { + Ñ, + } +} diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java new file mode 100644 index 000000000000..453e23dc95f4 --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 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.swirlds.config.impl.internal; + +import com.swirlds.config.api.Configuration; +import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.config.api.converter.ConfigConverter; +import com.swirlds.config.extensions.sources.SimpleConfigSource; +import com.swirlds.config.impl.converters.EnumConverter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConverterServiceTest { + + private ConverterService converterService; + + @BeforeEach + public void setUp() { + converterService = new ConverterService(); + converterService.init(); + } + + @Test + void itSuccessfullyCreatesAndReturnsAConverter() { + // given + final ConverterService cs = converterService; + + ConfigConverter converterForType = cs.getConverterForType(NumberEnum.class); + // then: + Assertions.assertNotNull(converterForType); + Assertions.assertInstanceOf(EnumConverter.class, converterForType); + } + + @Test + void itDoesNotCreateANewInstanceForAlreadyCachedConverters() { + // given + final ConverterService cs = converterService; + ConfigConverter converter = cs.getConverterForType(NumberEnum.class); + + // then: + Assertions.assertSame(converter, cs.getConverterForType(NumberEnum.class)); + } + + @Test + void itSuccessfullyUsesConfiguredEnumConverterAboveDefaultConverter() { + // given + ConverterService cs = new ConverterService(); + FakeEnumConverter converter = new FakeEnumConverter(); + cs.addConverter(converter); // creates a new enumConverter for NumberAndValueEnum + cs.init(); + + // then: + // NumberValueEnum gets converted with FakeEnumConverter + Assertions.assertSame(converter, cs.getConverterForType(NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("ONE", NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("", NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("DOS", NumberAndValueEnum.class)); + Assertions.assertInstanceOf(FakeEnumConverter.class, cs.getConverterForType(NumberAndValueEnum.class)); + // and: + // NumberEnum stills gets converted with defaultEnumConverter + Assertions.assertEquals(NumberEnum.ONE, cs.convert("ONE", NumberEnum.class)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cs.convert("", NumberEnum.class)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cs.convert("DOS", NumberEnum.class)); + Assertions.assertInstanceOf(EnumConverter.class, cs.getConverterForType(NumberEnum.class)); + } + + @Test + void testIntegration() { + + // given + final Configuration configuration = ConfigurationBuilder.create() + .withSource(new SimpleConfigSource("plain-value", "ONE")) + .withSource(new SimpleConfigSource("complex-value-one", "ONE")) + .withSource(new SimpleConfigSource("complex-value-two", "DOS")) + .withSource(new SimpleConfigSource("complex-value-three", "THREE")) + .build(); + + // when + final NumberEnum uno = configuration.getValue("plain-value", NumberEnum.class); + final NumberAndValueEnum one = configuration.getValue("complex-value-one", NumberAndValueEnum.class); + + // then + Assertions.assertEquals(uno, NumberEnum.ONE); + Assertions.assertEquals(one, NumberAndValueEnum.ONE); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> configuration.getValue("complex-value-two", NumberAndValueEnum.class)); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> configuration.getValue("complex-value-three", NumberAndValueEnum.class)); + } + + /** + * An enum converter that always returns the same constant + */ + private static class FakeEnumConverter implements ConfigConverter { + @Override + public NumberAndValueEnum convert(String value) { + return NumberAndValueEnum.ONE; + } + } + + private enum NumberEnum { + ONE + } + + private enum NumberAndValueEnum { + ONE(1); + final int value; + + NumberAndValueEnum(int value) { + this.value = value; + } + } +} From 73c6397778fcd767b8a30abcd7a0d1b74412520c Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Wed, 20 Dec 2023 22:21:55 +0200 Subject: [PATCH 6/9] fix: Resolve EthereumSuite Errors (#10230) Signed-off-by: Ivo Yankov --- .../app/throttle/ThrottleAccumulator.java | 9 +- .../SingleTransactionRecordBuilderImpl.java | 5 ++ .../exec/ContextTransactionProcessor.java | 2 +- .../impl/exec/TransactionProcessor.java | 31 ++++++- .../operations/BasicCustomCallOperation.java | 1 + .../handlers/EthereumTransactionHandler.java | 44 +++++++--- .../impl/hevm/HederaEvmTransactionResult.java | 4 + .../EthereumTransactionRecordBuilder.java | 3 + .../test/exec/TransactionProcessorTest.java | 18 ++++ .../EthereumTransactionHandlerTest.java | 85 ++++++++++++++++--- .../bdd/suites/ethereum/EthereumSuite.java | 69 ++++++++++++++- .../src/main/resource/spec-default.properties | 2 +- 12 files changed, 243 insertions(+), 30 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java index 506790873505..5a4f2a443a87 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java @@ -80,6 +80,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.IntSupplier; import org.apache.commons.lang3.tuple.Pair; @@ -512,9 +513,11 @@ private long getGasLimitForContractTx( return switch (function) { case CONTRACT_CREATE -> txn.contractCreateInstance().gas(); case CONTRACT_CALL -> txn.contractCall().gas(); - case ETHEREUM_TRANSACTION -> EthTxData.populateEthTxData( - txn.ethereumTransaction().ethereumData().toByteArray()) - .gasLimit(); + case ETHEREUM_TRANSACTION -> Optional.of( + txn.ethereumTransactionOrThrow().ethereumData().toByteArray()) + .map(EthTxData::populateEthTxData) + .map(EthTxData::gasLimit) + .orElse(0L); default -> 0L; }; } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index ca2fc4c9f52a..ab17a05b9cc4 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -1128,4 +1128,9 @@ private TransactionBody inProgressBody() { throw new IllegalStateException("Record being built for unparseable transaction", e); } } + + public EthereumTransactionRecordBuilder feeChargedToPayer(@NonNull long amount) { + transactionRecordBuilder.transactionFee(transactionFee + amount); + return this; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 2254640bb1f8..9f003f98fa94 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -58,7 +58,7 @@ public class ContextTransactionProcessor implements Callable { private final ActionSidecarContentTracer tracer; private final RootProxyWorldUpdater rootProxyWorldUpdater; - private final HevmTransactionFactory hevmTransactionFactory; + public final HevmTransactionFactory hevmTransactionFactory; private final Supplier feesOnlyUpdater; private final Map processors; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index 2434363ca9f4..b1c3f1130d6e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -26,8 +26,10 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Duration; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; @@ -37,6 +39,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; @@ -205,10 +208,34 @@ private InvolvedParties computeInvolvedParties( if (transaction.isCreate()) { final Address to; if (transaction.isEthereumTransaction()) { + final ReadableAccountStore accountStore = + updater.enhancement().nativeOperations().readableAccountStore(); + var createBody = requireNonNull(transaction.hapiCreation()); + final var modifiedBodyBuilder = createBody.copyBuilder(); + + final Account sponsor = requireNonNull(accountStore.getAccountById(transaction.senderId())); + if (sponsor.memo() != null) { + modifiedBodyBuilder.memo(sponsor.memo()); + } + if (sponsor.autoRenewAccountId() != null) { + modifiedBodyBuilder.autoRenewAccountId(sponsor.autoRenewAccountId()); + } + if (sponsor.stakedAccountId() != null) { + modifiedBodyBuilder.stakedAccountId(sponsor.stakedAccountId()); + } + if (sponsor.autoRenewSeconds() > 0) { + modifiedBodyBuilder.autoRenewPeriod(Duration.newBuilder() + .seconds(sponsor.autoRenewSeconds()) + .build()); + } + modifiedBodyBuilder.maxAutomaticTokenAssociations(sponsor.maxAutoAssociations()); + modifiedBodyBuilder.declineReward(sponsor.declineReward()); + + final var modifiedBody = modifiedBodyBuilder.build(); to = Address.contractAddress(sender.getAddress(), sender.getNonce()); - updater.setupAliasedTopLevelCreate(requireNonNull(transaction.hapiCreation()), to); + updater.setupAliasedTopLevelCreate(modifiedBody, to); } else { - to = updater.setupTopLevelCreate(requireNonNull(transaction.hapiCreation())); + to = updater.setupTopLevelCreate(transaction.hapiCreation()); } parties = new InvolvedParties(sender, relayer, to); } else { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java index f7c392fd8a0a..ad4f64659954 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java @@ -83,6 +83,7 @@ default Operation.OperationResult executeChecked(@NonNull final MessageFrame fra requireNonNull(frame); try { final var address = to(frame); + if (addressChecks().isNeitherSystemNorPresent(address, frame)) { return new Operation.OperationResult(cost(frame), INVALID_SOLIDITY_ADDRESS); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index 68dc3828be59..34e7f698da84 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -17,8 +17,13 @@ package com.hedera.node.app.service.contract.impl.handlers; import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.throwIfUnsuccessful; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; +import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; @@ -30,15 +35,18 @@ import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.mono.fees.calculation.ethereum.txns.EthereumTransactionResourceUsage; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -64,18 +72,20 @@ public EthereumTransactionHandler( } @Override - public void preHandle(@NonNull final PreHandleContext context) { + public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { requireNonNull(context); final var body = context.body().ethereumTransactionOrThrow(); final var fileStore = context.createStore(ReadableFileStore.class); final var hederaConfig = context.configuration().getConfigData(HederaConfig.class); - final var ethTxData = callDataHydration - .tryToHydrate(body, fileStore, hederaConfig.firstUserEntity()) - .ethTxData(); - if (ethTxData != null) { - // Ignore the return value; we just want to cache the signature for use in handle() - ethereumSignatures.computeIfAbsent(ethTxData); - } + final var hydratedTx = callDataHydration.tryToHydrate(body, fileStore, hederaConfig.firstUserEntity()); + + validateTruePreCheck(hydratedTx.status() == OK, hydratedTx.status()); + + final var ethTxData = hydratedTx.ethTxData(); + validateTruePreCheck(ethTxData != null, INVALID_ETHEREUM_TRANSACTION); + + // Ignore the return value; we just want to cache the signature for use in handle() + ethereumSignatures.computeIfAbsent(ethTxData); } @Override @@ -83,15 +93,24 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Create the transaction-scoped component final var component = provider.get().create(context, ETHEREUM_TRANSACTION); - // Run its in-scope transaction and get the outcome - final var outcome = component.contextTransactionProcessor().call(); + final var hevmTransactionFactory = component.contextTransactionProcessor().hevmTransactionFactory; + final var hevmTransaction = hevmTransactionFactory.fromHapiTransaction(context.body()); + + final var accountStore = context.readableStore(ReadableAccountStore.class); + final var sender = accountStore.getAccountById(Objects.requireNonNull(hevmTransaction.senderId())); // Assemble the appropriate top-level record for the result final var ethTxData = requireNonNull(requireNonNull(component.hydratedEthTxData()).ethTxData()); + + // Run its in-scope transaction and get the outcome + final var outcome = component.contextTransactionProcessor().call(); + final var recordBuilder = context.recordBuilder(EthereumTransactionRecordBuilder.class) .ethereumHash(Bytes.wrap(ethTxData.getEthereumHash())) - .status(outcome.status()); + .status(outcome.status()) + .feeChargedToPayer(outcome.tinybarGasCost()); + if (ethTxData.hasToAddress()) { // The Ethereum transaction was a top-level MESSAGE_CALL recordBuilder.contractID(outcome.recipientId()).contractCallResult(outcome.result()); @@ -99,6 +118,9 @@ public void handle(@NonNull final HandleContext context) throws HandleException // The Ethereum transaction was a top-level CONTRACT_CREATION recordBuilder.contractID(outcome.recipientIdIfCreated()).contractCreateResult(outcome.result()); } + + validateTrue(sender.ethereumNonce() == ethTxData.nonce(), WRONG_NONCE); + throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index 669234fe96a6..f98177de6754 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CONTRACT_STORAGE_EXCEEDED; @@ -75,6 +76,7 @@ public record HederaEvmTransactionResult( private static final Bytes INSUFFICIENT_GAS_REASON = Bytes.wrap(INSUFFICIENT_GAS.name()); private static final Bytes INVALID_CONTRACT_REASON = Bytes.wrap(INVALID_CONTRACT_ID.name()); private static final Bytes MAX_CHILD_RECORDS_EXCEEDED_REASON = Bytes.wrap(MAX_CHILD_RECORDS_EXCEEDED.name()); + private static final Bytes INSUFFICIENT_TX_FEE_REASON = Bytes.wrap(INSUFFICIENT_TX_FEE.name()); /** * Converts this result to a {@link ContractFunctionResult} for a transaction based on the given @@ -141,6 +143,8 @@ public ResponseCodeEnum finalStatus() { return INVALID_CONTRACT_ID; } else if (revertReason.equals(MAX_CHILD_RECORDS_EXCEEDED_REASON)) { return MAX_CHILD_RECORDS_EXCEEDED; + } else if (revertReason.equals(INSUFFICIENT_TX_FEE_REASON)) { + return INSUFFICIENT_TX_FEE; } else { return CONTRACT_REVERT_EXECUTED; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java index b7c61de2fe66..d9d28278e77e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java @@ -68,4 +68,7 @@ public interface EthereumTransactionRecordBuilder { */ @NonNull EthereumTransactionRecordBuilder ethereumHash(@NonNull Bytes ethereumHash); + + @NonNull + EthereumTransactionRecordBuilder feeChargedToPayer(@NonNull long amount); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java index 4069f4e4fc95..0a9f1a8ed186 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java @@ -58,12 +58,14 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.contract.impl.exec.FrameRunner; import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; @@ -72,6 +74,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; @@ -136,6 +139,15 @@ class TransactionProcessorTest { @Mock private CustomGasCharging gasCharging; + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + @Mock + private HederaNativeOperations nativeOperations; + + @Mock + private ReadableAccountStore readableAccountStore; + private TransactionProcessor subject; @BeforeEach @@ -272,6 +284,12 @@ void ethCreateHappyPathAsExpected() { messageCallProcessor, contractCreationProcessor)) .willReturn(SUCCESS_RESULT); + given(worldUpdater.enhancement()).willReturn(enhancement); + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.readableAccountStore()).willReturn(readableAccountStore); + final var parsedAccount = + Account.newBuilder().accountId(senderAccount.hederaId()).build(); + given(readableAccountStore.getAccountById(SENDER_ID)).willReturn(parsedAccount); given(initialFrame.getSelfDestructs()).willReturn(Set.of(NON_SYSTEM_LONG_ZERO_ADDRESS)); final var result = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index 59785bc0ade3..aab524374339 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -19,33 +19,48 @@ import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_038; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITHOUT_TO_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_TO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.HEVM_CREATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SUCCESS_RESULT; +import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.contract.EthereumTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.CallOutcome; import com.hedera.node.app.service.contract.impl.exec.ContextTransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.TransactionComponent; +import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; import com.hedera.node.app.service.contract.impl.handlers.EthereumTransactionHandler; +import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; +import com.hedera.node.app.service.contract.impl.infra.HevmTransactionFactory; import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; import com.hedera.node.app.service.contract.impl.state.RootProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.test.TestHelpers; import com.hedera.node.app.service.file.ReadableFileStore; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import com.hedera.node.config.data.ContractsConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -75,15 +90,30 @@ class EthereumTransactionHandlerTest { @Mock private TransactionComponent.Factory factory; - @Mock - private ContextTransactionProcessor processor; - @Mock private EthereumTransactionRecordBuilder recordBuilder; @Mock private RootProxyWorldUpdater baseProxyWorldUpdater; + @Mock + private HevmTransactionFactory hevmTransactionFactory; + + @Mock + private HederaEvmContext hederaEvmContext; + + @Mock + private ActionSidecarContentTracer tracer; + + @Mock + private Supplier feesOnlyUpdater; + + @Mock + private TransactionProcessor transactionProcessor; + + @Mock + private ReadableAccountStore readableAccountStore; + private EthereumTransactionHandler subject; @BeforeEach @@ -91,23 +121,56 @@ void setUp() { subject = new EthereumTransactionHandler(ethereumSignatures, callDataHydration, () -> factory); } + void setUpTransactionProcessing() { + final var contractsConfig = DEFAULT_CONFIG.getConfigData(ContractsConfig.class); + final var processors = Map.of(VERSION_038, transactionProcessor); + + final var contextTransactionProcessor = new ContextTransactionProcessor( + HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS), + handleContext, + contractsConfig, + DEFAULT_CONFIG, + hederaEvmContext, + tracer, + baseProxyWorldUpdater, + hevmTransactionFactory, + feesOnlyUpdater, + processors); + + given(component.contextTransactionProcessor()).willReturn(contextTransactionProcessor); + given(hevmTransactionFactory.fromHapiTransaction(handleContext.body())).willReturn(HEVM_CREATION); + + final AccountID senderId = HEVM_CREATION.senderId(); + final var parsedAccount = Account.newBuilder().accountId(senderId).build(); + + given(handleContext.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); + given(readableAccountStore.getAccountById(HEVM_CREATION.senderId())).willReturn(parsedAccount); + given(transactionProcessor.processTransaction( + HEVM_CREATION, + baseProxyWorldUpdater, + feesOnlyUpdater, + hederaEvmContext, + tracer, + DEFAULT_CONFIG)) + .willReturn(SUCCESS_RESULT); + } + @Test void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { given(factory.create(handleContext, ETHEREUM_TRANSACTION)).willReturn(component); given(component.hydratedEthTxData()).willReturn(HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS)); - given(component.contextTransactionProcessor()).willReturn(processor); + setUpTransactionProcessing(); given(handleContext.recordBuilder(EthereumTransactionRecordBuilder.class)) .willReturn(recordBuilder); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome( expectedResult, SUCCESS_RESULT.finalStatus(), CALLED_CONTRACT_ID, SUCCESS_RESULT.gasPrice()); - given(processor.call()).willReturn(expectedOutcome); - given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITH_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); + given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -116,26 +179,26 @@ void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { void delegatesToCreatedComponentAndExposesEthTxDataCreateWithoutToAddress() { given(factory.create(handleContext, ETHEREUM_TRANSACTION)).willReturn(component); given(component.hydratedEthTxData()).willReturn(HydratedEthTxData.successFrom(ETH_DATA_WITHOUT_TO_ADDRESS)); - given(component.contextTransactionProcessor()).willReturn(processor); + setUpTransactionProcessing(); given(handleContext.recordBuilder(EthereumTransactionRecordBuilder.class)) .willReturn(recordBuilder); given(baseProxyWorldUpdater.getCreatedContractIds()).willReturn(List.of(CALLED_CONTRACT_ID)); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITHOUT_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); - given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITHOUT_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); + given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @Test - void preHandleCachesTheSignaturesIfDataCanBeHydrated() { + void preHandleCachesTheSignaturesIfDataCanBeHydrated() throws PreCheckException { final var ethTxn = EthereumTransactionBody.newBuilder() .ethereumData(TestHelpers.ETH_WITH_TO_ADDRESS) .build(); @@ -151,7 +214,7 @@ void preHandleCachesTheSignaturesIfDataCanBeHydrated() { } @Test - void preHandleIgnoresFailureToHydrate() { + void preHandleDoesNotIgnoreFailureToHydrate() throws PreCheckException { final var ethTxn = EthereumTransactionBody.newBuilder().ethereumData(Bytes.EMPTY).build(); final var body = @@ -161,7 +224,7 @@ void preHandleIgnoresFailureToHydrate() { given(preHandleContext.configuration()).willReturn(DEFAULT_CONFIG); given(callDataHydration.tryToHydrate(ethTxn, fileStore, 1001L)) .willReturn(HydratedEthTxData.failureFrom(INVALID_ETHEREUM_TRANSACTION)); - subject.preHandle(preHandleContext); + assertThrowsPreCheck(() -> subject.preHandle(preHandleContext), INVALID_ETHEREUM_TRANSACTION); verifyNoInteractions(ethereumSignatures); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java index 75952f7974de..d81948d85c45 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java @@ -165,7 +165,6 @@ public List getSpecsInSuite() { .toList(); } - @HapiTest @Disabled("Failing or intermittently failing HAPI Test") HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { final AtomicReference
tokenCreateContractAddress = new AtomicReference<>(); @@ -296,6 +295,66 @@ List feePaymentMatrix() { .toList(); } + @HapiTest + HapiSpec matrixedPayerRelayerTest1() { + return feePaymentMatrix().get(0); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest2() { + return feePaymentMatrix().get(1); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest3() { + return feePaymentMatrix().get(2); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest4() { + return feePaymentMatrix().get(3); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest5() { + return feePaymentMatrix().get(4); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest6() { + return feePaymentMatrix().get(5); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest7() { + return feePaymentMatrix().get(6); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest8() { + return feePaymentMatrix().get(7); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest9() { + return feePaymentMatrix().get(8); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest10() { + return feePaymentMatrix().get(9); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest11() { + return feePaymentMatrix().get(10); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest12() { + return feePaymentMatrix().get(11); + } + @BddTestNameDoesNotMatchMethodName HapiSpec matrixedPayerRelayerTest( final boolean success, final long senderGasPrice, final long relayerOffered, final long senderCharged) { @@ -349,6 +408,7 @@ HapiSpec matrixedPayerRelayerTest( })); } + @HapiTest HapiSpec invalidTxData() { return defaultHapiSpec("InvalidTxData") .given( @@ -423,6 +483,7 @@ HapiSpec etx014ContractCreateInheritsSignerProperties() { .memo(MEMO)))); } + @HapiTest HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { final var relayerSnapshot = "relayer"; final var senderSnapshot = "sender"; @@ -464,6 +525,7 @@ HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { .has(accountWith().nonce(0L))); } + @HapiTest HapiSpec etx013PrecompileCallFailsWhenSignatureMissingFromBothEthereumAndHederaTxn() { final AtomicReference fungible = new AtomicReference<>(); final String fungibleToken = TOKEN; @@ -514,6 +576,7 @@ HELLO_WORLD_MINT_CONTRACT, asHeadlongAddress(asAddress(fungible.get())))), recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))); } + @HapiTest HapiSpec etx009CallsToTokenAddresses() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -711,6 +774,7 @@ HapiSpec etxSvc003ContractGetBytecodeQueryReturnsDeployedCode() { })); } + @HapiTest HapiSpec directTransferWorksForERC20() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; @@ -812,6 +876,7 @@ HapiSpec transferHbarsViaEip2930TxSuccessfully() { .hasTinyBars(changeFromSnapshot(aliasBalanceSnapshot, -FIVE_HBARS))); } + @HapiTest HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -856,6 +921,7 @@ HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { .withTotalSupply(totalSupply))))); } + @HapiTest HapiSpec transferTokensViaEip2930TxSuccessfully() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; @@ -908,6 +974,7 @@ HapiSpec transferTokensViaEip2930TxSuccessfully() { .withErcFungibleTransferStatus(true))))))); } + @HapiTest HapiSpec callToNonExistingContractFailsGracefully() { return defaultHapiSpec("callToNonExistingContractFailsGracefully") diff --git a/hedera-node/test-clients/src/main/resource/spec-default.properties b/hedera-node/test-clients/src/main/resource/spec-default.properties index a9960be5c818..35b75f2e88dc 100644 --- a/hedera-node/test-clients/src/main/resource/spec-default.properties +++ b/hedera-node/test-clients/src/main/resource/spec-default.properties @@ -110,7 +110,7 @@ num.opFinisher.threads=8 persistentEntities.dir.path= persistentEntities.updateCreatedManifests=true spec.autoScheduledTxns= -spec.streamlinedIngestChecks=INVALID_FILE_ID,ENTITY_NOT_ALLOWED_TO_DELETE,AUTHORIZATION_FAILED,INVALID_PRNG_RANGE,INVALID_STAKING_ID,NOT_SUPPORTED,TOKEN_ID_REPEATED_IN_TOKEN_LIST,ALIAS_ALREADY_ASSIGNED,INVALID_ALIAS_KEY,KEY_REQUIRED,BAD_ENCODING,AUTORENEW_DURATION_NOT_IN_RANGE,INVALID_ZERO_BYTE_IN_STRING,INVALID_ADMIN_KEY,ACCOUNT_DELETED,BUSY,INSUFFICIENT_PAYER_BALANCE,INSUFFICIENT_TX_FEE,INVALID_ACCOUNT_ID,INVALID_NODE_ACCOUNT,INVALID_SIGNATURE,INVALID_TRANSACTION,INVALID_TRANSACTION_BODY,INVALID_TRANSACTION_DURATION,INVALID_TRANSACTION_ID,INVALID_TRANSACTION_START,KEY_PREFIX_MISMATCH,MEMO_TOO_LONG,PAYER_ACCOUNT_NOT_FOUND,PLATFORM_NOT_ACTIVE,TRANSACTION_EXPIRED,TRANSACTION_HAS_UNKNOWN_FIELDS,TRANSACTION_ID_FIELD_NOT_ALLOWED,TRANSACTION_OVERSIZE,TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT,EMPTY_ALLOWANCES,REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT,TOKEN_HAS_NO_FREEZE_KEY,TOKEN_HAS_NO_SUPPLY_KEY,INVALID_TOKEN_INITIAL_SUPPLY,INVALID_TOKEN_DECIMALS,INVALID_TOKEN_MAX_SUPPLY,ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS,TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN,INVALID_ACCOUNT_AMOUNTS,TOKEN_NAME_TOO_LONG,TOKEN_SYMBOL_TOO_LONG,INVALID_TOKEN_NFT_SERIAL_NUMBER,PERMANENT_REMOVAL_REQUIRES_SYSTEM_INITIATION,MISSING_TOKEN_SYMBOL,MISSING_TOKEN_NAME,INVALID_EXPIRATION_TIME,EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS,INVALID_ALLOWANCE_OWNER_ID,FUNGIBLE_TOKEN_IN_NFT_ALLOWANCES,TOKEN_NOT_ASSOCIATED_TO_ACCOUNT,MAX_ALLOWANCES_EXCEEDED,INVALID_ALLOWANCE_SPENDER_ID,AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY,NFT_IN_FUNGIBLE_TOKEN_ALLOWANCES,NEGATIVE_ALLOWANCE_AMOUNT,DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL,DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL,INVALID_TOKEN_MINT_AMOUNT,INVALID_TOKEN_BURN_AMOUNT,INVALID_WIPING_AMOUNT,INVALID_NFT_ID,BATCH_SIZE_LIMIT_EXCEEDED,METADATA_TOO_LONG,INVALID_RENEWAL_PERIOD,INVALID_CUSTOM_FEE_SCHEDULE_KEY,MAX_GAS_LIMIT_EXCEEDED,CONTRACT_DELETED +spec.streamlinedIngestChecks=INVALID_FILE_ID,ENTITY_NOT_ALLOWED_TO_DELETE,AUTHORIZATION_FAILED,INVALID_PRNG_RANGE,INVALID_STAKING_ID,NOT_SUPPORTED,TOKEN_ID_REPEATED_IN_TOKEN_LIST,ALIAS_ALREADY_ASSIGNED,INVALID_ALIAS_KEY,KEY_REQUIRED,BAD_ENCODING,AUTORENEW_DURATION_NOT_IN_RANGE,INVALID_ZERO_BYTE_IN_STRING,INVALID_ADMIN_KEY,ACCOUNT_DELETED,BUSY,INSUFFICIENT_PAYER_BALANCE,INSUFFICIENT_TX_FEE,INVALID_ACCOUNT_ID,INVALID_NODE_ACCOUNT,INVALID_SIGNATURE,INVALID_TRANSACTION,INVALID_TRANSACTION_BODY,INVALID_TRANSACTION_DURATION,INVALID_TRANSACTION_ID,INVALID_TRANSACTION_START,KEY_PREFIX_MISMATCH,MEMO_TOO_LONG,PAYER_ACCOUNT_NOT_FOUND,PLATFORM_NOT_ACTIVE,TRANSACTION_EXPIRED,TRANSACTION_HAS_UNKNOWN_FIELDS,TRANSACTION_ID_FIELD_NOT_ALLOWED,TRANSACTION_OVERSIZE,TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT,EMPTY_ALLOWANCES,REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT,TOKEN_HAS_NO_FREEZE_KEY,TOKEN_HAS_NO_SUPPLY_KEY,INVALID_TOKEN_INITIAL_SUPPLY,INVALID_TOKEN_DECIMALS,INVALID_TOKEN_MAX_SUPPLY,ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS,TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN,INVALID_ACCOUNT_AMOUNTS,TOKEN_NAME_TOO_LONG,TOKEN_SYMBOL_TOO_LONG,INVALID_TOKEN_NFT_SERIAL_NUMBER,PERMANENT_REMOVAL_REQUIRES_SYSTEM_INITIATION,MISSING_TOKEN_SYMBOL,MISSING_TOKEN_NAME,INVALID_EXPIRATION_TIME,EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS,INVALID_ALLOWANCE_OWNER_ID,FUNGIBLE_TOKEN_IN_NFT_ALLOWANCES,TOKEN_NOT_ASSOCIATED_TO_ACCOUNT,MAX_ALLOWANCES_EXCEEDED,INVALID_ALLOWANCE_SPENDER_ID,AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY,NFT_IN_FUNGIBLE_TOKEN_ALLOWANCES,NEGATIVE_ALLOWANCE_AMOUNT,DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL,DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL,INVALID_TOKEN_MINT_AMOUNT,INVALID_TOKEN_BURN_AMOUNT,INVALID_WIPING_AMOUNT,INVALID_NFT_ID,BATCH_SIZE_LIMIT_EXCEEDED,METADATA_TOO_LONG,INVALID_RENEWAL_PERIOD,INVALID_CUSTOM_FEE_SCHEDULE_KEY,MAX_GAS_LIMIT_EXCEEDED,CONTRACT_DELETED,INVALID_ETHEREUM_TRANSACTION status.deferredResolves.doAsync=true status.preResolve.pause.ms=0 status.wait.sleep.ms=500 From 1d9aa274205d98c8878217ffbbe20ecd6b7e5dea Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:11:43 -0300 Subject: [PATCH 7/9] fix: 10558 remove AbstractEnumConfigConverter (#10559) Signed-off-by: Maxi Tartaglia --- .../config/BootstrapConfigProviderImpl.java | 2 - .../node/app/config/ConfigProviderImpl.java | 21 ++-- .../handle/SystemFileUpdateFacilityTest.java | 2 - .../config/converter/EntityTypeConverter.java | 37 ------ .../HederaFunctionalityConverter.java | 35 ------ .../converter/MapAccessTypeConverter.java | 36 ------ .../config/converter/ProfileConverter.java | 28 ----- .../converter/RecomputeTypeConverter.java | 37 ------ .../converter/SidecarTypeConverter.java | 37 ------ .../config/PropertySourceBasedConfigTest.java | 10 -- .../converter/EntityTypeConverterTest.java | 56 --------- .../HederaFunctionalityConverterTest.java | 59 ---------- .../converter/MapAccessTypeConverterTest.java | 56 --------- .../converter/ProfileConverterTest.java | 58 --------- .../converter/RecomputeTypeConverterTest.java | 56 --------- .../converter/SidecarTypeConverterTest.java | 59 ---------- .../testfixtures/HederaTestConfigBuilder.java | 12 -- .../build.gradle.kts | 2 - .../AbstractEnumConfigConverter.java | 47 -------- .../src/main/java/module-info.java | 1 - .../AbstractEnumConfigConverterTest.java | 110 ------------------ 21 files changed, 14 insertions(+), 747 deletions(-) delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java delete mode 100644 platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java delete mode 100644 platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java index 4f10b5584595..a752185e0adc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java @@ -20,7 +20,6 @@ import com.hedera.node.config.VersionedConfiguration; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.ProfileConverter; import com.hedera.node.config.converter.SemanticVersionConverter; import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; @@ -60,7 +59,6 @@ public BootstrapConfigProviderImpl() { .withConfigDataType(LedgerConfig.class) .withConverter(new BytesConverter()) .withConverter(new SemanticVersionConverter()) - .withConverter(new ProfileConverter()) .withConverter(new LongPairConverter()); try { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java index d537ed845e75..0fe32dccc488 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java @@ -22,7 +22,20 @@ import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.VersionedConfiguration; -import com.hedera.node.config.converter.*; +import com.hedera.node.config.converter.AccountIDConverter; +import com.hedera.node.config.converter.BytesConverter; +import com.hedera.node.config.converter.CongestionMultipliersConverter; +import com.hedera.node.config.converter.ContractIDConverter; +import com.hedera.node.config.converter.EntityScaleFactorsConverter; +import com.hedera.node.config.converter.FileIDConverter; +import com.hedera.node.config.converter.FunctionalitySetConverter; +import com.hedera.node.config.converter.KeyValuePairConverter; +import com.hedera.node.config.converter.KnownBlockValuesConverter; +import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; +import com.hedera.node.config.converter.LongPairConverter; +import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; +import com.hedera.node.config.converter.ScaleFactorConverter; +import com.hedera.node.config.converter.SemanticVersionConverter; import com.hedera.node.config.data.AccountsConfig; import com.hedera.node.config.data.ApiPermissionConfig; import com.hedera.node.config.data.AutoCreationConfig; @@ -177,24 +190,18 @@ private ConfigurationBuilder createConfigurationBuilder() { .withConfigDataType(VersionConfig.class) .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new SemanticVersionConverter()) .withConverter(new LongPairConverter()) .withConverter(new KeyValuePairConverter()) .withConverter(new FunctionalitySetConverter()) .withConverter(new BytesConverter()) - .withConverter(new ProfileConverter()) .withValidator(new EmulatesMapValidator()); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java index 6a3ac2866b8b..6273d87e3a13 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java @@ -46,7 +46,6 @@ import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.ProfileConverter; import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; @@ -103,7 +102,6 @@ void setUp() { final var config = new TestConfigBuilder(false) .withConverter(new BytesConverter()) .withConverter(new LongPairConverter()) - .withConverter(new ProfileConverter()) .withConfigDataType(FilesConfig.class) .withConfigDataType(HederaConfig.class) .withConfigDataType(LedgerConfig.class) diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java deleted file mode 100644 index 5c3fa36d7690..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.node.app.service.mono.context.properties.EntityType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link EntityType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class EntityTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return EntityType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java deleted file mode 100644 index 5fb31212e5bc..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.hapi.node.base.HederaFunctionality; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link HederaFunctionality}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class HederaFunctionalityConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return HederaFunctionality.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java deleted file mode 100644 index f7706ad11c3c..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.node.app.service.mono.throttling.MapAccessType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link MapAccessType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class MapAccessTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - @NonNull - @Override - protected Class getEnumType() { - return MapAccessType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java deleted file mode 100644 index a11a6f271d15..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.node.config.types.Profile; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; - -public class ProfileConverter extends AbstractEnumConfigConverter implements ConfigConverter { - @Override - protected Class getEnumType() { - return Profile.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java deleted file mode 100644 index 397507dcb02e..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper.RecomputeType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link RecomputeType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class RecomputeTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return RecomputeType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java deleted file mode 100644 index fc580d446231..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import com.hedera.hapi.streams.SidecarType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link SidecarType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class SidecarTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return SidecarType.class; - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java index d6aaeb0aa38e..781cda6c3bc7 100644 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java +++ b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java @@ -37,16 +37,11 @@ import com.hedera.node.config.converter.CongestionMultipliersConverter; import com.hedera.node.config.converter.ContractIDConverter; import com.hedera.node.config.converter.EntityScaleFactorsConverter; -import com.hedera.node.config.converter.EntityTypeConverter; import com.hedera.node.config.converter.FileIDConverter; -import com.hedera.node.config.converter.HederaFunctionalityConverter; import com.hedera.node.config.converter.KnownBlockValuesConverter; import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; -import com.hedera.node.config.converter.MapAccessTypeConverter; import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; -import com.hedera.node.config.converter.RecomputeTypeConverter; import com.hedera.node.config.converter.ScaleFactorConverter; -import com.hedera.node.config.converter.SidecarTypeConverter; import com.hedera.node.config.sources.PropertySourceBasedConfigSource; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; @@ -96,18 +91,13 @@ void testConfig() { final Configuration configuration = ConfigurationBuilder.create() .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new BytesConverter()) .withSource(new PropertySourceBasedConfigSource(propertySource)) .build(); diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java deleted file mode 100644 index 04e7f2c920cf..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.context.properties.EntityType; -import org.junit.jupiter.api.Test; - -class EntityTypeConverterTest { - - @Test - void testNullParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // when - final EntityType entityType = converter.convert("ACCOUNT"); - - // then - assertThat(entityType).isEqualTo(EntityType.ACCOUNT); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java deleted file mode 100644 index a2c9f6a8130a..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.hapi.node.base.HederaFunctionality; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class HederaFunctionalityConverterTest { - - @Test - void testNullParam() { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(HederaFunctionality.class) - void testValidParam(final HederaFunctionality functionality) { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // when - final HederaFunctionality cryptoTransfer = converter.convert(functionality.name()); - - // then - assertThat(cryptoTransfer).isEqualTo(functionality); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java deleted file mode 100644 index bd1f46aa1509..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.throttling.MapAccessType; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -class MapAccessTypeConverterTest { - - @Test - void testNullParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // when - final MapAccessType entityType = converter.convert("ACCOUNTS_GET"); - - // then - Assertions.assertThat(entityType).isEqualTo(MapAccessType.ACCOUNTS_GET); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java deleted file mode 100644 index a3d7c63ec753..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.config.types.Profile; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -public class ProfileConverterTest { - @Test - void testNullParam() { - // given - final ProfileConverter converter = new ProfileConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final ProfileConverter converter = new ProfileConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(Profile.class) - void testValidParam(final Profile value) { - // given - final ProfileConverter converter = new ProfileConverter(); - - // when - final Profile profile = converter.convert(value.name()); - - // then - assertThat(profile).isEqualTo(value); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java deleted file mode 100644 index d041646a78e7..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper.RecomputeType; -import org.junit.jupiter.api.Test; - -class RecomputeTypeConverterTest { - - @Test - void testNullParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // when - final RecomputeType entityType = converter.convert("NODE_STAKES"); - - // then - assertThat(entityType).isEqualTo(RecomputeType.NODE_STAKES); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java deleted file mode 100644 index fadd76b6a303..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 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.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.hapi.streams.SidecarType; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class SidecarTypeConverterTest { - - @Test - void testNullParam() { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(SidecarType.class) - void testValidParam(final SidecarType value) { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // when - final SidecarType sidecarType = converter.convert(value.name()); - - // then - assertThat(sidecarType).isEqualTo(value); - } -} diff --git a/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java b/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java index 40c4693a28f7..cd469b65770f 100644 --- a/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java +++ b/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java @@ -23,21 +23,15 @@ import com.hedera.node.config.converter.CongestionMultipliersConverter; import com.hedera.node.config.converter.ContractIDConverter; import com.hedera.node.config.converter.EntityScaleFactorsConverter; -import com.hedera.node.config.converter.EntityTypeConverter; import com.hedera.node.config.converter.FileIDConverter; import com.hedera.node.config.converter.FunctionalitySetConverter; -import com.hedera.node.config.converter.HederaFunctionalityConverter; import com.hedera.node.config.converter.KeyValuePairConverter; import com.hedera.node.config.converter.KnownBlockValuesConverter; import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.MapAccessTypeConverter; import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; -import com.hedera.node.config.converter.ProfileConverter; -import com.hedera.node.config.converter.RecomputeTypeConverter; import com.hedera.node.config.converter.ScaleFactorConverter; import com.hedera.node.config.converter.SemanticVersionConverter; -import com.hedera.node.config.converter.SidecarTypeConverter; import com.hedera.node.config.data.AccountsConfig; import com.hedera.node.config.data.ApiPermissionConfig; import com.hedera.node.config.data.AutoCreationConfig; @@ -183,24 +177,18 @@ public static TestConfigBuilder create() { .withConfigDataType(VersionConfig.class) .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new SemanticVersionConverter()) .withConverter(new KeyValuePairConverter()) .withConverter(new LongPairConverter()) .withConverter(new FunctionalitySetConverter()) .withConverter(new BytesConverter()) - .withConverter(new ProfileConverter()) .withValidator(new EmulatesMapValidator()); } diff --git a/platform-sdk/swirlds-config-extensions/build.gradle.kts b/platform-sdk/swirlds-config-extensions/build.gradle.kts index 4c46442a5261..219658007bf1 100644 --- a/platform-sdk/swirlds-config-extensions/build.gradle.kts +++ b/platform-sdk/swirlds-config-extensions/build.gradle.kts @@ -23,6 +23,4 @@ testModuleInfo { requires("com.swirlds.common") requires("com.swirlds.test.framework") requires("org.junit.jupiter.api") - requires("org.junit.jupiter.params") - requires("org.assertj.core") } diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java deleted file mode 100644 index b5769e2cca06..000000000000 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 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.swirlds.config.extensions.converters; - -import com.swirlds.config.api.converter.ConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; - -/** - * Abstract class to support {@link ConfigConverter} for {@link Enum} types. - * - * @param type of the enum - */ -public abstract class AbstractEnumConfigConverter> implements ConfigConverter { - - @Override - public E convert(final String value) throws IllegalArgumentException, NullPointerException { - if (value == null) { - throw new NullPointerException("null can not be converted"); - } - final Class enumType = getEnumType(); - Objects.requireNonNull(enumType, "enumType"); - return Enum.valueOf(enumType, value); - } - - /** - * Returns the {@link Class} of the {@link Enum} type. - * - * @return the {@link Class} of the {@link Enum} type - */ - @NonNull - protected abstract Class getEnumType(); -} diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java index 1af176add9ff..f202576a6e16 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java @@ -3,7 +3,6 @@ exports com.swirlds.config.extensions.reflection; exports com.swirlds.config.extensions.sources; exports com.swirlds.config.extensions.validators; - exports com.swirlds.config.extensions.converters; requires transitive com.swirlds.config.api; requires com.swirlds.base; diff --git a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java deleted file mode 100644 index 6c962118806b..000000000000 --- a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023 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.swirlds.config.extensions.test.converters; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.swirlds.config.api.Configuration; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import com.swirlds.config.extensions.sources.SimpleConfigSource; -import java.lang.annotation.ElementType; -import java.lang.annotation.RetentionPolicy; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class AbstractEnumConfigConverterTest { - - /** - * Based on https://github.com/hashgraph/hedera-services/issues/6106 we currently need to add ConfigConverter - * explicitly - */ - private static class RetentionPolicyConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return RetentionPolicy.class; - } - } - - /** - * Based on https://github.com/hashgraph/hedera-services/issues/6106 we currently need to add ConfigConverter - * explicitly - */ - private static class ElementTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return ElementType.class; - } - } - - @Test - void testNullValue() { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidValue() { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // then - assertThatThrownBy(() -> converter.convert("not-supported-value")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(value = RetentionPolicy.class) - void testValidValue(final RetentionPolicy value) { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // when - final RetentionPolicy source = converter.convert(value.name()); - - // then - assertThat(source).isEqualTo(value); - } - - @Test - void testIntegration() { - // given - final Configuration configuration = ConfigurationBuilder.create() - .withSource(new SimpleConfigSource("retention-policy", "SOURCE")) - .withSource(new SimpleConfigSource("element-type", "TYPE")) - .withConverter(new RetentionPolicyConverter()) - .withConverter(new ElementTypeConverter()) - .build(); - - // when - final RetentionPolicy source = configuration.getValue("retention-policy", RetentionPolicy.class); - final ElementType type = configuration.getValue("element-type", ElementType.class); - - // then - assertThat(source).isEqualTo(RetentionPolicy.SOURCE); - assertThat(type).isEqualTo(ElementType.TYPE); - } -} From 8c3b3defac8a32914cff47b81d6149982454358e Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 19:06:11 -0800 Subject: [PATCH 8/9] feat: add sanity precheck for contract call (#10585) Signed-off-by: lukelee-sl --- .../contract/ContractCallTransitionLogic.java | 14 +++++ .../ContractCallTransitionLogicTest.java | 21 +++++++ .../contract/hapi/ContractCallSuite.java | 63 ++++++++++++++++++- .../opcodes/CreateOperationSuite.java | 11 +++- .../suites/leaky/LeakyContractTestsSuite.java | 29 ++++++--- 5 files changed, 126 insertions(+), 12 deletions(-) diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java index 3f5c018ca16a..a1708167533f 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java @@ -27,6 +27,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_GAS_LIMIT_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; import com.hedera.node.app.service.mono.context.TransactionContext; import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.contracts.execution.CallEvmTxProcessor; @@ -238,6 +239,19 @@ private ResponseCodeEnum validateSemantics(final TransactionBody transactionBody if (op.getGas() > properties.maxGasPerSec()) { return MAX_GAS_LIMIT_EXCEEDED; } + // Do some sanity checking in advance to ensure that the target is a valid contract + // Missing entity num is a valid target for lazy create. Tokens are also valid targets + final var target = targetOf(op); + if (!target.equals(EntityNum.MISSING_NUM) + && !entityAccess.isTokenAccount(target.toId().asEvmAddress()) + && op.getAmount() == 0) { + try { + final var receiver = accountStore.loadContract(target.toId()); + validateTrue(receiver != null && receiver.isSmartContract(), INVALID_CONTRACT_ID); + } catch (InvalidTransactionException e) { + return e.getResponseCode(); + } + } return OK; } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java index dcad5cd00895..39987043b697 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java @@ -716,10 +716,22 @@ void codeCacheThrowingExceptionDuringGetDoesntPropagate() { void acceptsOkSyntax() { givenValidTxnCtx(); given(properties.maxGasPerSec()).willReturn(gas + 1); + contractAccount.setSmartContract(true); // expect: assertEquals(OK, subject.semanticCheck().apply(contractCallTxn)); } + @Test + void failsIfNotSmartContractSyntax() { + givenValidTxnCtxWithNoAmount(); + given(properties.maxGasPerSec()).willReturn(gas + 1); + given(accountStore.loadContract(new Id(target.getShardNum(), target.getRealmNum(), target.getContractNum()))) + .willReturn(contractAccount); + contractAccount.setSmartContract(false); + // expect: + assertEquals(INVALID_CONTRACT_ID, subject.semanticCheck().apply(contractCallTxn)); + } + @Test void providingGasOverLimitReturnsCorrectPrecheck() { givenValidTxnCtx(); @@ -781,6 +793,15 @@ private void givenValidTxnCtx() { contractCallTxn = op.build(); } + private void givenValidTxnCtxWithNoAmount() { + var op = TransactionBody.newBuilder() + .setContractCall(ContractCallTransactionBody.newBuilder() + .setGas(gas) + .setAmount(0) + .setContractID(target)); + contractCallTxn = op.build(); + } + private AccountID ourAccount() { return senderAccount.getId().asGrpcAccount(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java index 915a13074cec..f364ac684815 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java @@ -63,6 +63,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.createLargeFile; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; @@ -92,6 +94,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import static com.swirlds.common.utility.CommonUtils.unhex; @@ -232,6 +235,8 @@ public List getSpecsInSuite() { insufficientFee(), nonPayable(), invalidContract(), + invalidContractIsAnAccount(), + invalidContractDoesNotExist(), smartContractFailFirst(), contractTransferToSigReqAccountWithoutKeyFails(), callingDestructedContractReturnsStatusDeleted(), @@ -1512,9 +1517,15 @@ HapiSpec callingDestructedContractReturnsStatusDeleted() { "del", asHeadlongAddress(asAddress(accountIDAtomicReference.get()))) .gas(1_000_000L))) - .then(contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) - .gas(350_000L) - .hasKnownStatus(CONTRACT_DELETED)); + .then( + ifHapiTest(contractCall( + SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) + .gas(350_000L) + .hasKnownStatus(CONTRACT_DELETED)), + ifNotHapiTest(contractCall( + SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) + .gas(350_000L) + .hasPrecheck(CONTRACT_DELETED))); } @HapiTest @@ -1569,6 +1580,52 @@ HapiSpec invalidContract() { .then(contractCallWithFunctionAbi("invalid", function).hasKnownStatus(INVALID_CONTRACT_ID)); } + @HapiTest + HapiSpec invalidContractDoesNotExist() { + final AtomicReference accountID = new AtomicReference<>(); + final var function = getABIFor(FUNCTION, "getIndirect", CREATE_TRIVIAL); + + return defaultHapiSpec("invalidContractDoesNotExist") + .given() + .when( + withOpContext((spec, opLog) -> allRunFor( + spec, + ifHapiTest(contractCallWithFunctionAbi("0.0.100000001", function) + .hasKnownStatus(INVALID_CONTRACT_ID) + .via("contractDoesNotExist")))), + ifNotHapiTest(contractCallWithFunctionAbi("0.0.100000001", function) + .hasPrecheck(INVALID_CONTRACT_ID) + .via("contractDoesNotExist"))) + .then(ifNotHapiTest(getTxnRecord("contractDoesNotExist").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND))); + } + + @HapiTest + HapiSpec invalidContractIsAnAccount() { + final AtomicReference accountID = new AtomicReference<>(); + final var function = getABIFor(FUNCTION, "getIndirect", CREATE_TRIVIAL); + + return defaultHapiSpec("invalidContractIsAnAccount") + .given(cryptoCreate("invalid") + .balance(ONE_MILLION_HBARS) + .payingWith(GENESIS) + .exposingCreatedIdTo(accountID::set)) + .when( + ifHapiTest(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCallWithFunctionAbi( + "0.0." + accountID.get().getAccountNum(), function) + .hasKnownStatus(SUCCESS) + .via("contractIsAccount")))), + ifNotHapiTest(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCallWithFunctionAbi( + "0.0." + accountID.get().getAccountNum(), function) + .hasPrecheck(INVALID_CONTRACT_ID) + .via("contractIsAccount"))))) + .then(ifNotHapiTest(getTxnRecord("contractIsAccount").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND))); + } + + // This test disabled for modularization service @HapiTest HapiSpec smartContractFailFirst() { final var civilian = "civilian"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java index 8b5cb03d54ba..67ddfed90aff 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java @@ -33,6 +33,8 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.contractListWithPropertiesInheritedFrom; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; @@ -103,7 +105,14 @@ final HapiSpec factoryAndSelfDestructInConstructorContract() { uploadInitCode(contract), cryptoCreate(sender).balance(ONE_HUNDRED_HBARS), contractCreate(contract).balance(10).payingWith(sender)) - .when(contractCall(contract).hasKnownStatus(CONTRACT_DELETED).payingWith(sender)) + .when( + // Currently only mono service has a precheck for deleted contracts + ifHapiTest(contractCall(contract) + .hasKnownStatus(CONTRACT_DELETED) + .payingWith(sender)), + ifNotHapiTest(contractCall(contract) + .hasPrecheck(CONTRACT_DELETED) + .payingWith(sender))) .then(getContractBytecode(contract).hasCostAnswerPrecheck(CONTRACT_DELETED)); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 77022b60e0c3..743afbebd1fe 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -81,6 +81,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; @@ -1717,14 +1719,25 @@ final HapiSpec canCallPendingContractSafely() { .adminKey(THRESHOLD)) .toArray(HapiSpecOperation[]::new))) .when() - .then(sourcing(() -> contractCallWithFunctionAbi( - "0.0." + (createdFileNum.get() + createBurstSize), - getABIFor(FUNCTION, "addNthFib", contract), - targets, - 12L) - .payingWith(GENESIS) - .gas(300_000L) - .via(callTxn))); + .then( + sourcing(() -> ifHapiTest(contractCallWithFunctionAbi( + "0.0." + (createdFileNum.get() + createBurstSize), + getABIFor(FUNCTION, "addNthFib", contract), + targets, + 12L) + .payingWith(GENESIS) + .gas(300_000L) + .via(callTxn))), + ifNotHapiTest(contractCallWithFunctionAbi( + "0.0." + (createdFileNum.get() + createBurstSize), + getABIFor(FUNCTION, "addNthFib", contract), + targets, + 12L) + .payingWith(GENESIS) + .gas(300_000L) + // This will fail the semantics validity check that verifies existence of the contract, + .hasPrecheck(INVALID_CONTRACT_ID) + .via(callTxn))); } @HapiTest From 1061cc98303eedcf65c0edc00082cee73b0704eb Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Thu, 21 Dec 2023 09:15:26 +0200 Subject: [PATCH 9/9] test: fix submittingNodeStillPaidIfServiceFeesOmitted (#10589) Signed-off-by: Mustafa Uzun --- .../com/hedera/node/app/workflows/SolvencyPreCheck.java | 2 +- .../services/bdd/suites/records/RecordCreationSuite.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java index af571db3baec..fa3377e79663 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java @@ -142,7 +142,7 @@ public void checkSolvency( return; } - final var totalFee = fees.totalFee(); + final var totalFee = ingestCheck ? fees.totalWithoutServiceFee() : fees.totalFee(); final var availableBalance = account.tinybarBalance(); final var offeredFee = txBody.transactionFee(); final ResponseCodeEnum insufficientFeeResponseCode; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java index 70a94726705c..61f1d30b99df 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java @@ -88,12 +88,17 @@ public List getSpecsInSuite() { submittingNodeStillPaidIfServiceFeesOmitted()); } + @HapiTest final HapiSpec submittingNodeStillPaidIfServiceFeesOmitted() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final AtomicReference feeObs = new AtomicReference<>(); - return defaultHapiSpec("submittingNodeStillPaidIfServiceFeesOmitted") + return propertyPreservingHapiSpec("submittingNodeStillPaidIfServiceFeesOmitted") + .preserving(STAKING_FEES_NODE_REWARD_PERCENTAGE, STAKING_FEES_STAKING_REWARD_PERCENTAGE) .given( + overridingTwo( + STAKING_FEES_NODE_REWARD_PERCENTAGE, "10", + STAKING_FEES_STAKING_REWARD_PERCENTAGE, "10"), cryptoTransfer(tinyBarsFromTo(GENESIS, TO_ACCOUNT, ONE_HBAR)) .payingWith(GENESIS), cryptoCreate(PAYER),