Skip to content

Commit

Permalink
Create the key at —p2p-private-key-file path if none (Consensys#7875)
Browse files Browse the repository at this point in the history
  • Loading branch information
courtneyeh authored Jan 17, 2024
1 parent b71a58e commit 821da85
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ payload attributes to be calculated and sent with every fcU. This could be usefu
consuming the `payload_attributes` SSE events.
- Added Deneb (aka Dencun) configuration for Sepolia network for epoch 132608 (2024-01-30 22:51:12 UTC).
- Added Deneb (aka Dencun) configuration for Holesky network for epoch 29696 (2024-02-07 11:34:24 UTC).
- Generate key at `—p2p-private-key-file` path if specified file doesn't exist.

### Bug Fixes
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import io.libp2p.core.crypto.PrivKey;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import tech.pegasys.teku.networking.p2p.network.config.NetworkConfig.PrivateKeySource;
import tech.pegasys.teku.networking.p2p.network.config.PrivateKeySource;
import tech.pegasys.teku.storage.store.KeyValueStore;

public class LibP2PPrivateKeyLoader implements LibP2PNetwork.PrivateKeyProvider {
Expand All @@ -39,12 +39,12 @@ public LibP2PPrivateKeyLoader(
public PrivKey get() {
final Bytes privKeyBytes =
privateKeySource
.map(PrivateKeySource::getPrivateKeyBytes)
.orElseGet(this::generateNewPrivateKey);
.map(PrivateKeySource::getOrGeneratePrivateKeyBytes)
.orElseGet(() -> generateAndSaveNewPrivateKey(keyValueStore));
return KeyKt.unmarshalPrivateKey(privKeyBytes.toArrayUnsafe());
}

private Bytes generateNewPrivateKey() {
private Bytes generateAndSaveNewPrivateKey(final KeyValueStore<String, Bytes> keyValueStore) {
final Bytes privateKey;
final Optional<Bytes> generatedKeyBytes = keyValueStore.get(GENERATED_NODE_KEY_KEY);
if (generatedKeyBytes.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright Consensys Software Inc., 2024
*
* 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 tech.pegasys.teku.networking.p2p.network.config;

import static tech.pegasys.teku.infrastructure.logging.StatusLogger.STATUS_LOG;

import io.libp2p.core.crypto.KeyKt;
import io.libp2p.core.crypto.KeyType;
import io.libp2p.core.crypto.PrivKey;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
import org.apache.tuweni.bytes.Bytes;

public class FilePrivateKeySource implements PrivateKeySource {
private final String fileName;

public FilePrivateKeySource(String fileName) {
this.fileName = fileName;
}

@Override
public Bytes getOrGeneratePrivateKeyBytes() {
try {
File file = new File(fileName);
if (!file.createNewFile()) {
return getPrivateKeyBytesFromFile();
}
final PrivKey privKey = KeyKt.generateKeyPair(KeyType.SECP256K1).component1();
final Bytes privateKeyBytes = Bytes.wrap(KeyKt.marshalPrivateKey(privKey));
Files.writeString(file.toPath(), privateKeyBytes.toHexString());
STATUS_LOG.usingGeneratedP2pPrivateKey(fileName, true);
return privateKeyBytes;

} catch (IOException e) {
throw new IllegalArgumentException(
"Not able to create or retrieve p2p private key file - " + fileName);
}
}

private Bytes getPrivateKeyBytesFromFile() {
try {
final Bytes privateKeyBytes = Bytes.fromHexString(Files.readString(Paths.get(fileName)));
STATUS_LOG.usingGeneratedP2pPrivateKey(fileName, false);
return privateKeyBytes;
} catch (IOException e) {
throw new RuntimeException("p2p private key file not found - " + fileName);
}
}

public String getFileName() {
return fileName;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FilePrivateKeySource that = (FilePrivateKeySource) o;
return Objects.equals(fileName, that.fileName);
}

@Override
public int hashCode() {
return Objects.hash(fileName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,22 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.InetAddresses.isInetAddress;

import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException;
import tech.pegasys.teku.infrastructure.io.PortAvailability;
import tech.pegasys.teku.networking.p2p.gossip.config.GossipConfig;

public class NetworkConfig {

public interface PrivateKeySource {
Bytes getPrivateKeyBytes();
}

private static final Logger LOG = LogManager.getLogger();

public static final String DEFAULT_P2P_INTERFACE = "0.0.0.0";
Expand Down Expand Up @@ -271,26 +262,4 @@ public Builder yamuxEnabled(final boolean yamuxEnabled) {
return this;
}
}

@VisibleForTesting
public static class FilePrivateKeySource implements PrivateKeySource {
private final String fileName;

public FilePrivateKeySource(String fileName) {
this.fileName = fileName;
}

@Override
public Bytes getPrivateKeyBytes() {
try {
return Bytes.fromHexString(Files.readString(Paths.get(fileName)));
} catch (IOException e) {
throw new RuntimeException("p2p private key file not found - " + fileName);
}
}

public String getFileName() {
return fileName;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Consensys Software Inc., 2024
*
* 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 tech.pegasys.teku.networking.p2p.network.config;

import org.apache.tuweni.bytes.Bytes;

public interface PrivateKeySource {
Bytes getOrGeneratePrivateKeyBytes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.network.p2p.jvmlibp2p.PrivateKeyGenerator;
import tech.pegasys.teku.networking.p2p.network.config.NetworkConfig.PrivateKeySource;
import tech.pegasys.teku.networking.p2p.network.config.PrivateKeySource;
import tech.pegasys.teku.storage.store.MemKeyValueStore;

public class LibP2PPrivateKeyLoaderTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Consensys Software Inc., 2024
*
* 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 tech.pegasys.teku.networking.p2p.network.config;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import tech.pegasys.teku.network.p2p.jvmlibp2p.PrivateKeyGenerator;

class FilePrivateKeySourceTest {

@Test
void shouldCreateKeyAndSaveToFile(@TempDir Path tempDir) throws IOException {
final Path file = tempDir.resolve("file.txt");
final PrivateKeySource privateKeySource = new FilePrivateKeySource(file.toString());
final Bytes generatedBytes = privateKeySource.getOrGeneratePrivateKeyBytes();
final Bytes savedBytes = Bytes.fromHexString(Files.readString(file));

assertThat(generatedBytes).isEqualTo(savedBytes);
}

@Test
void shouldGetKeyFromSavedFile(@TempDir Path tempDir) throws IOException {
final Path file = tempDir.resolve("file.txt");
final Bytes privateKey = Bytes.wrap(PrivateKeyGenerator.generate().bytes());
Files.writeString(file, privateKey.toHexString());
final PrivateKeySource privateKeySource = new FilePrivateKeySource(file.toString());

assertThat(privateKeySource.getOrGeneratePrivateKeyBytes()).isEqualTo(privateKey);
}

@Test
void shouldThrowExceptionIfInvalidFileName(@TempDir Path tempDir) {
final PrivateKeySource privateKeySource =
new FilePrivateKeySource(tempDir + "/invalid file name!!\0");
assertThatThrownBy(privateKeySource::getOrGeneratePrivateKeyBytes)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Not able to create or retrieve p2p private key file -");
}

@Test
void shouldThrowExceptionIfProvideDirectory(@TempDir Path tempDir) {
final PrivateKeySource privateKeySource = new FilePrivateKeySource(tempDir.toString());
assertThatThrownBy(privateKeySource::getOrGeneratePrivateKeyBytes)
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("p2p private key file not found -");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ void getAdvertisedIp_shouldResolveLocalhostIpWhenInterfaceIpIsAnyLocalIpv6() {
assertThat(result).isNotEqualTo("0.0.0.0");
}

@Test
void checkPrivateKeySourceCreatedCorrectly() {
final NetworkConfig config =
NetworkConfig.builder()
.advertisedIp(advertisedIp)
.networkInterface(listenIp)
.privateKeyFile("file.txt")
.build();
final Optional<PrivateKeySource> source = config.getPrivateKeySource();
final PrivateKeySource expected = new FilePrivateKeySource("file.txt");

assertThat(source).isPresent();
assertThat(source).contains(expected);
}

private NetworkConfig createConfig() {
return NetworkConfig.builder().advertisedIp(advertisedIp).networkInterface(listenIp).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException;
import tech.pegasys.teku.networking.eth2.P2PConfig;
import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig;
import tech.pegasys.teku.networking.p2p.network.config.FilePrivateKeySource;
import tech.pegasys.teku.networking.p2p.network.config.NetworkConfig;
import tech.pegasys.teku.networking.p2p.network.config.NetworkConfig.FilePrivateKeySource;

public class P2POptionsTest extends AbstractBeaconNodeCommandTest {

Expand Down

0 comments on commit 821da85

Please sign in to comment.