Skip to content

Commit

Permalink
feat: design roster interfaces (#10428)
Browse files Browse the repository at this point in the history
Signed-off-by: Edward Wertz <edward@swirldslabs.com>
  • Loading branch information
edward-swirldslabs committed Dec 20, 2023
1 parent 78653bb commit be962a0
Show file tree
Hide file tree
Showing 7 changed files with 582 additions and 4 deletions.
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();
}
```
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;
}
}
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;
}
}
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();
}
}

0 comments on commit be962a0

Please sign in to comment.