Skip to content

Commit

Permalink
begin implementing multiple fee recipient
Browse files Browse the repository at this point in the history
  • Loading branch information
tbenr committed Jan 23, 2022
1 parent f88438e commit f553dbb
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2022 ConsenSys AG.
*
* 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.provider;

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import tech.pegasys.teku.api.schema.BLSPubKey;

public class BLSPubKeyKeyDeserializer extends KeyDeserializer {

@Override
public Object deserializeKey(String key, DeserializationContext ctxt) {
return BLSPubKey.fromHexString(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ private void addTekuMappers() {
module.addDeserializer(BLSPubKey.class, new BLSPubKeyDeserializer());
module.addDeserializer(BLSSignature.class, new BLSSignatureDeserializer());
module.addSerializer(BLSSignature.class, new BLSSignatureSerializer());
module.addKeyDeserializer(BLSPubKey.class, new BLSPubKeyKeyDeserializer());

module.addDeserializer(Bytes32.class, new Bytes32Deserializer());
module.addDeserializer(Bytes4.class, new Bytes4Deserializer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import picocli.CommandLine.Option;
import tech.pegasys.teku.config.TekuConfiguration;
import tech.pegasys.teku.validator.api.ValidatorConfig;

public class ValidatorProposerOptions {
@Option(
Expand All @@ -26,7 +27,31 @@ public class ValidatorProposerOptions {
hidden = true)
private String proposerDefaultFeeRecipient = null;

@Option(
names = {"--Xvalidators-proposer-config"},
paramLabel = "<STRING>",
description = "remote URL or local file path to load proposer configuration from",
arity = "0..1",
hidden = true)
private String proposerConfig = null;

@Option(
names = {"--Xvalidators-proposer-config-refresh-rate"},
paramLabel = "<INTEGER>",
description =
"Sets the frequency, in seconds, at which the proposer configuration is reloaded. "
+ "0 means never refresh.",
arity = "0..1",
hidden = true)
private long proposerConfigRefreshRate =
ValidatorConfig.DEFAULT_VALIDATOR_PROPOSER_CONFIG_REFRESH_RATE.toSeconds();

public void configure(TekuConfiguration.Builder builder) {
builder.validator(config -> config.proposerDefaultFeeRecipient(proposerDefaultFeeRecipient));
builder.validator(
config ->
config
.proposerDefaultFeeRecipient(proposerDefaultFeeRecipient)
.proposerConfigSource(proposerConfig)
.proposerConfigSourceRefreshRate(proposerConfigRefreshRate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class ValidatorConfig {
public static final boolean DEFAULT_GENERATE_EARLY_ATTESTATIONS = true;
public static final boolean DEFAULT_SEND_ATTESTATIONS_AS_BATCH = true;
public static final Optional<Bytes32> DEFAULT_GRAFFITI = Optional.empty();
public static final Duration DEFAULT_VALIDATOR_PROPOSER_CONFIG_REFRESH_RATE =
Duration.ofSeconds(60);

private final List<String> validatorKeys;
private final List<String> validatorExternalSignerPublicKeySources;
Expand All @@ -58,6 +60,8 @@ public class ValidatorConfig {
private final boolean useDependentRoots;
private final boolean generateEarlyAttestations;
private final Optional<Eth1Address> proposerDefaultFeeRecipient;
private final String proposerConfigSource;
private final long proposerConfigSourceRefreshRate;

private ValidatorConfig(
final List<String> validatorKeys,
Expand All @@ -76,7 +80,9 @@ private ValidatorConfig(
final int validatorExternalSignerConcurrentRequestLimit,
final boolean useDependentRoots,
final boolean generateEarlyAttestations,
final Optional<Eth1Address> proposerDefaultFeeRecipient) {
final Optional<Eth1Address> proposerDefaultFeeRecipient,
final String proposerConfigSource,
final long proposerConfigSourceRefreshRate) {
this.validatorKeys = validatorKeys;
this.validatorExternalSignerPublicKeySources = validatorExternalSignerPublicKeySources;
this.validatorExternalSignerUrl = validatorExternalSignerUrl;
Expand All @@ -97,6 +103,8 @@ private ValidatorConfig(
this.useDependentRoots = useDependentRoots;
this.generateEarlyAttestations = generateEarlyAttestations;
this.proposerDefaultFeeRecipient = proposerDefaultFeeRecipient;
this.proposerConfigSource = proposerConfigSource;
this.proposerConfigSourceRefreshRate = proposerConfigSourceRefreshRate;
}

public static Builder builder() {
Expand Down Expand Up @@ -195,6 +203,8 @@ public static final class Builder {
private boolean useDependentRoots = DEFAULT_USE_DEPENDENT_ROOTS;
private boolean generateEarlyAttestations = DEFAULT_GENERATE_EARLY_ATTESTATIONS;
private Optional<Eth1Address> proposerDefaultFeeRecipient = Optional.empty();
private String proposerConfigSource;
private long proposerConfigSourceRefreshRate;

private Builder() {}

Expand Down Expand Up @@ -304,6 +314,16 @@ public Builder proposerDefaultFeeRecipient(final String proposerDefaultFeeRecipi
return this;
}

public Builder proposerConfigSource(final String proposerConfigSource) {
this.proposerConfigSource = proposerConfigSource;
return this;
}

public Builder proposerConfigSourceRefreshRate(final long proposerConfigSourceRefreshRate) {
this.proposerConfigSourceRefreshRate = proposerConfigSourceRefreshRate;
return this;
}

public ValidatorConfig build() {
validateExternalSignerUrlAndPublicKeys();
validateExternalSignerKeystoreAndPasswordFileConfig();
Expand All @@ -326,7 +346,9 @@ public ValidatorConfig build() {
validatorExternalSignerConcurrentRequestLimit,
useDependentRoots,
generateEarlyAttestations,
proposerDefaultFeeRecipient);
proposerDefaultFeeRecipient,
proposerConfigSource,
proposerConfigSourceRefreshRate);
}

private void validateExternalSignerUrlAndPublicKeys() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2022 ConsenSys AG.
*
* 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.validator.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
import java.util.Optional;
import tech.pegasys.teku.api.schema.BLSPubKey;
import tech.pegasys.teku.infrastructure.ssz.type.Bytes20;

public class ProposerConfig {
@JsonProperty(value = "proposer_config", required = true)
private Map<BLSPubKey, Config> proposerConfig;

@JsonProperty(value = "default_config", required = true)
private Config defaultConfig;

public Optional<Config> getConfigForPubKey(final String pubKey) {
return getConfigForPubKey(BLSPubKey.fromHexString(pubKey));
}

public Optional<Config> getConfigForPubKey(final BLSPubKey pubKey) {
return Optional.ofNullable(proposerConfig.get(pubKey));
}

public Optional<Config> getDefaultConfig() {
return Optional.ofNullable(defaultConfig);
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Config {
@JsonProperty(value = "fee_recipient", required = true)
private Bytes20 feeRecipient;

public Bytes20 getFeeRecipient() {
return feeRecipient;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2022 ConsenSys AG.
*
* 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.validator.client;

import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.pegasys.teku.infrastructure.async.AsyncRunner;
import tech.pegasys.teku.infrastructure.async.Cancellable;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.service.serviceutils.Service;

public class ProposerConfigService extends Service {
private static final Logger LOG = LogManager.getLogger();
static final Duration DEFAULT_REFRESH_RATE = Duration.ofMinutes(1);

private final Duration refreshRate;
private final AsyncRunner asyncRunner;
private Optional<Cancellable> cancellable = Optional.empty();
private final AtomicBoolean running = new AtomicBoolean(false);

public ProposerConfigService(final AsyncRunner asyncRunner) {
this.asyncRunner = asyncRunner;
this.refreshRate = DEFAULT_REFRESH_RATE;
}

@Override
protected SafeFuture<?> doStart() {
cancellable =
Optional.of(
asyncRunner.runWithFixedDelay(
this::loadProposerConfig,
refreshRate,
error -> LOG.error("Failed to refresh proposer configuration", error)));
// Run immediately on start
loadProposerConfig();
return SafeFuture.COMPLETE;
}

@Override
protected SafeFuture<?> doStop() {
cancellable.ifPresent(Cancellable::cancel);
cancellable = Optional.empty();
return SafeFuture.COMPLETE;
}

private void loadProposerConfig() {
if (running.compareAndSet(false, true)) {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2022 ConsenSys AG.
*
* 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.validator.client.loader;

import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException;
import tech.pegasys.teku.provider.JsonProvider;
import tech.pegasys.teku.validator.client.ProposerConfig;

public class ProposerConfigLoader {
private final JsonProvider jsonProvider = new JsonProvider();

public ProposerConfig getProposerConfig(final String source) {
try {
final ProposerConfig proposerConfig =
jsonProvider.getObjectMapper().readValue(source, ProposerConfig.class);
return proposerConfig;
} catch (IOException ex) {
throw new InvalidConfigurationException("Failed to proposer config from URL " + source, ex);
}
}

public ProposerConfig getProposerConfig(final URL source) {
try {
final ProposerConfig proposerConfig =
jsonProvider.getObjectMapper().readValue(source, ProposerConfig.class);
return proposerConfig;
} catch (IOException ex) {
throw new InvalidConfigurationException("Failed to proposer config from URL " + source, ex);
}
}

private Optional<URL> getUrl(final String source) {
try {
return Optional.of(new URL(source));
} catch (IOException e) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2022 ConsenSys AG.
*
* 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.validator.client.loader;

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

import com.google.common.io.Resources;
import java.net.URL;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.infrastructure.ssz.type.Bytes20;
import tech.pegasys.teku.validator.client.ProposerConfig;
import tech.pegasys.teku.validator.client.ProposerConfig.Config;

public class ProposerConfigLoaderTest {
private final ProposerConfigLoader loader = new ProposerConfigLoader();

@Test
void shouldLoadValidConfig() {
final URL resource = Resources.getResource("proposerConfig.json");

ProposerConfig config = loader.getProposerConfig(resource);
Optional<Config> theConfig =
config.getConfigForPubKey(
"0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a");
assertThat(theConfig).isPresent();
assertThat(theConfig.get().getFeeRecipient())
.isEqualTo(Bytes20.fromHexString("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"));

Optional<Config> defaultConfig = config.getDefaultConfig();
assertThat(defaultConfig).isPresent();
assertThat(defaultConfig.get().getFeeRecipient())
.isEqualTo(Bytes20.fromHexString("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"));
}
}
10 changes: 10 additions & 0 deletions validator/client/src/test/resources/proposerConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"proposer_config": {
"0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": {
"fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"
}
},
"default_config": {
"fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A"
}
}

0 comments on commit f553dbb

Please sign in to comment.