-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: design roster interfaces (#10428)
Signed-off-by: Edward Wertz <edward@swirldslabs.com>
- Loading branch information
1 parent
78653bb
commit be962a0
Showing
7 changed files
with
582 additions
and
4 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
...orm-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RosterEntry>, SelfSerializable{ | ||
int size(); | ||
boolean contains(NodeId nodeId); | ||
RosterEntry getEntry(NodeId nodeId); | ||
long getTotalWeight(); | ||
} | ||
``` |
69 changes: 69 additions & 0 deletions
69
platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RosterEntry>, SelfSerializable { | ||
|
||
/** | ||
* @return a collection of all unique nodeIds in the roster. | ||
*/ | ||
@NonNull | ||
Collection<NodeId> 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; | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...form-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
162 changes: 162 additions & 0 deletions
162
...lds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<NodeId, RosterEntry> entries = new HashMap<>(); | ||
private List<NodeId> 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<NodeId, KeysAndCerts> 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<NodeId> 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<RosterEntry> 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(); | ||
} | ||
} |
Oops, something went wrong.