Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feerecipient and gas_limit routes #4511

Merged
merged 17 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 106 additions & 5 deletions packages/api/src/keymanager/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export type ResponseStatus<Status> = {
message?: string;
};

export type FeeRecipientData = {
pubkey: string;
ethaddress: string;
};
export type GasLimitData = {
pubkey: string;
gasLimit: number;
};

export type SignerDefinition = {
pubkey: PubkeyHex;
/**
Expand Down Expand Up @@ -140,7 +149,7 @@ export type Api = {
*
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml
*/
deleteKeystores(
deleteKeys(
pubkeysHex: string[]
): Promise<{
data: ResponseStatus<DeletionStatus>[];
Expand All @@ -166,16 +175,40 @@ export type Api = {
): Promise<{
data: ResponseStatus<DeleteRemoteKeyStatus>[];
}>;

listFeeRecipient(
pubkey: string
): Promise<{
data: FeeRecipientData;
}>;
setFeeRecipient(pubkey: string, ethaddress: string): Promise<void>;
deleteFeeRecipient(pubkey: string): Promise<void>;

getGasLimit(
pubkey: string
): Promise<{
data: GasLimitData;
}>;
setGasLimit(pubkey: string, gasLimit: number): Promise<void>;
deleteGasLimit(pubkey: string): Promise<void>;
};

export const routesData: RoutesData<Api> = {
listKeys: {url: "/eth/v1/keystores", method: "GET"},
importKeystores: {url: "/eth/v1/keystores", method: "POST"},
deleteKeystores: {url: "/eth/v1/keystores", method: "DELETE"},
deleteKeys: {url: "/eth/v1/keystores", method: "DELETE"},

listRemoteKeys: {url: "/eth/v1/remotekeys", method: "GET"},
importRemoteKeys: {url: "/eth/v1/remotekeys", method: "POST"},
deleteRemoteKeys: {url: "/eth/v1/remotekeys", method: "DELETE"},

listFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "GET"},
setFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "POST"},
deleteFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "DELETE"},

getGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "GET"},
g11tech marked this conversation as resolved.
Show resolved Hide resolved
setGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "POST"},
deleteGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "DELETE"},
};

/* eslint-disable @typescript-eslint/naming-convention */
Expand All @@ -189,7 +222,7 @@ export type ReqTypes = {
slashing_protection?: SlashingProtectionData;
};
};
deleteKeystores: {body: {pubkeys: string[]}};
deleteKeys: {body: {pubkeys: string[]}};

listRemoteKeys: ReqEmpty;
importRemoteKeys: {
Expand All @@ -198,6 +231,14 @@ export type ReqTypes = {
};
};
deleteRemoteKeys: {body: {pubkeys: string[]}};

listFeeRecipient: {params: {pubkey: string}};
setFeeRecipient: {params: {pubkey: string}; body: {ethaddress: string}};
deleteFeeRecipient: {params: {pubkey: string}};

getGasLimit: {params: {pubkey: string}};
setGasLimit: {params: {pubkey: string}; body: {gas_limit: string | number}};
deleteGasLimit: {params: {pubkey: string}};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
Expand All @@ -208,7 +249,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
parseReq: ({body: {keystores, passwords, slashing_protection}}) => [keystores, passwords, slashing_protection],
schema: {body: Schema.Object},
},
deleteKeystores: {
deleteKeys: {
writeReq: (pubkeys) => ({body: {pubkeys}}),
parseReq: ({body: {pubkeys}}) => [pubkeys],
schema: {body: Schema.Object},
Expand All @@ -225,6 +266,52 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
parseReq: ({body: {pubkeys}}) => [pubkeys],
schema: {body: Schema.Object},
},

listFeeRecipient: {
writeReq: (pubkey) => ({params: {pubkey}}),
parseReq: ({params: {pubkey}}) => [pubkey],
schema: {
params: {pubkey: Schema.StringRequired},
},
},
setFeeRecipient: {
writeReq: (pubkey, ethaddress) => ({params: {pubkey}, body: {ethaddress}}),
parseReq: ({params: {pubkey}, body: {ethaddress}}) => [pubkey, ethaddress],
schema: {
params: {pubkey: Schema.StringRequired},
body: Schema.Object,
},
},
deleteFeeRecipient: {
writeReq: (pubkey) => ({params: {pubkey}}),
parseReq: ({params: {pubkey}}) => [pubkey],
schema: {
params: {pubkey: Schema.StringRequired},
},
},

getGasLimit: {
writeReq: (pubkey) => ({params: {pubkey}}),
parseReq: ({params: {pubkey}}) => [pubkey],
schema: {
params: {pubkey: Schema.StringRequired},
},
},
setGasLimit: {
writeReq: (pubkey, gas_limit) => ({params: {pubkey}, body: {gas_limit}}),
parseReq: ({params: {pubkey}, body: {gas_limit}}) => [pubkey, parseGasLimit(gas_limit)],
schema: {
params: {pubkey: Schema.StringRequired},
body: Schema.Object,
},
},
deleteGasLimit: {
writeReq: (pubkey) => ({params: {pubkey}}),
parseReq: ({params: {pubkey}}) => [pubkey],
schema: {
params: {pubkey: Schema.StringRequired},
},
},
};
}

Expand All @@ -233,10 +320,24 @@ export function getReturnTypes(): ReturnTypes<Api> {
return {
listKeys: jsonType("snake"),
importKeystores: jsonType("snake"),
deleteKeystores: jsonType("snake"),
deleteKeys: jsonType("snake"),

listRemoteKeys: jsonType("snake"),
importRemoteKeys: jsonType("snake"),
deleteRemoteKeys: jsonType("snake"),

listFeeRecipient: jsonType("snake"),
getGasLimit: jsonType("snake"),
};
}

function parseGasLimit(gasLimitInput: string | number): number {
if ((typeof gasLimitInput !== "string" && typeof gasLimitInput !== "number") || `${gasLimitInput}`.trim() === "") {
throw Error("Not valid Gas Limit");
}
const gasLimit = Number(gasLimitInput);
if (Number.isNaN(gasLimit) || gasLimit === 0) {
throw Error(`Gas Limit is not valid gasLimit=${gasLimit}`);
}
return gasLimit;
}
36 changes: 32 additions & 4 deletions packages/api/test/unit/keymanager/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {GenericServerTestCases} from "../../utils/genericServerTest.js";

// randomly pregenerated pubkey
const pubkeyRand = "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576";
const ethaddressRand = "0xabcf8e0d4e9587369b2301d0790347320302cc09";
const gasLimitRand = 30_000_000;

export const testData: GenericServerTestCases<Api> = {
listKeys: {
Expand All @@ -24,11 +26,11 @@ export const testData: GenericServerTestCases<Api> = {
},
},
importKeystores: {
args: [["key1"], ["pass1"], "slash_protection"],
args: [[pubkeyRand], ["pass1"], "slash_protection"],
res: {data: [{status: ImportStatus.imported}]},
},
deleteKeystores: {
args: [["key1"]],
deleteKeys: {
args: [[pubkeyRand]],
res: {data: [{status: DeletionStatus.deleted}], slashingProtection: "slash_protection"},
},

Expand All @@ -49,7 +51,33 @@ export const testData: GenericServerTestCases<Api> = {
res: {data: [{status: ImportRemoteKeyStatus.imported}]},
},
deleteRemoteKeys: {
args: [["key1"]],
args: [[pubkeyRand]],
res: {data: [{status: DeleteRemoteKeyStatus.deleted}]},
},

listFeeRecipient: {
args: [pubkeyRand],
res: {data: {pubkey: pubkeyRand, ethaddress: ethaddressRand}},
},
setFeeRecipient: {
args: [pubkeyRand, ethaddressRand],
res: undefined,
},
deleteFeeRecipient: {
args: [pubkeyRand],
res: undefined,
},

getGasLimit: {
args: [pubkeyRand],
res: {data: {pubkey: pubkeyRand, gasLimit: gasLimitRand}},
},
setGasLimit: {
args: [pubkeyRand, gasLimitRand],
res: undefined,
},
deleteGasLimit: {
args: [pubkeyRand],
res: undefined,
},
};
48 changes: 39 additions & 9 deletions packages/cli/src/cmds/validator/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {getSignersFromArgs} from "./signers/index.js";
import {logSigners} from "./signers/logSigners.js";
import {KeymanagerApi} from "./keymanager/impl.js";
import {PersistedKeysBackend} from "./keymanager/persistedKeys.js";
import {IPersistedKeysBackend} from "./keymanager/interface.js";
import {KeymanagerRestApiServer} from "./keymanager/server.js";

/**
Expand All @@ -24,13 +25,16 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P
const {config, network} = getBeaconConfigFromArgs(args);

const doppelgangerProtectionEnabled = args.doppelgangerProtectionEnabled;
const valProposerConfig = getProposerConfigFromArgs(args);

const beaconPaths = getBeaconPaths(args, network);
const validatorPaths = getValidatorPaths(args, network);
const accountPaths = getAccountPaths(args, network);

const logger = getCliLogger(args, {...beaconPaths, logFile: validatorPaths.logFile}, config);

const persistedKeysBackend = new PersistedKeysBackend(accountPaths);
const valProposerConfig = getProposerConfigFromArgs(args, {persistedKeysBackend, accountPaths});

const {version, commit} = getVersionData();
logger.info("Lodestar", {network, version, commit});

Expand Down Expand Up @@ -127,9 +131,15 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P
// Start keymanager API backend
// Only if keymanagerEnabled flag is set to true
if (args["keymanager"]) {
const accountPaths = getAccountPaths(args, network);
const keymanagerApi = new KeymanagerApi(validator, new PersistedKeysBackend(accountPaths));
// if proposerSettingsFile provided disable the key proposerConfigWrite in keymanager
const proposerConfigWriteDisabled = args.proposerSettingsFile !== undefined;
if (proposerConfigWriteDisabled) {
logger.warn(
"Proposer data updates (feeRecipient/gasLimit etc) will not be available via Keymanager API as proposerSettingsFile has been set"
);
}

const keymanagerApi = new KeymanagerApi(validator, persistedKeysBackend, proposerConfigWriteDisabled);
const keymanagerServer = new KeymanagerRestApiServer(
{
address: args["keymanager.address"],
Expand All @@ -145,19 +155,39 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P
}
}

function getProposerConfigFromArgs(args: IValidatorCliArgs): ValidatorProposerConfig {
function getProposerConfigFromArgs(
args: IValidatorCliArgs,
{
persistedKeysBackend,
accountPaths,
}: {persistedKeysBackend: IPersistedKeysBackend; accountPaths: {proposerDir: string}}
): ValidatorProposerConfig {
const defaultConfig = {
graffiti: args.graffiti || getDefaultGraffiti(),
strictFeeRecipientCheck: args.strictFeeRecipientCheck,
feeRecipient: args.suggestedFeeRecipient ? parseFeeRecipient(args.suggestedFeeRecipient) : undefined,
builder: {enabled: args.builder, gasLimit: args.defaultGasLimit},
};
let valProposerConfig;
if (args.proposerSettingsFile) {
// parseProposerConfig will override the defaults with the arg created defaultConfig
valProposerConfig = parseProposerConfig(args.proposerSettingsFile, defaultConfig);

let valProposerConfig: ValidatorProposerConfig;
const proposerConfigFromKeymanager = persistedKeysBackend.readProposerConfigs();

if (Object.keys(proposerConfigFromKeymanager).length > 0) {
// from persistedBackend
if (args.proposerSettingsFile) {
throw new YargsError(
`Cannot accept --proposerSettingsFile since it conflicts with proposer configs previously persisted via the keymanager api. Delete directory ${accountPaths.proposerDir} to discard them`
);
}
valProposerConfig = {proposerConfig: proposerConfigFromKeymanager, defaultConfig};
} else {
valProposerConfig = {defaultConfig} as ValidatorProposerConfig;
// from Proposer Settings File
if (args.proposerSettingsFile) {
// parseProposerConfig will override the defaults with the arg created defaultConfig
valProposerConfig = parseProposerConfig(args.proposerSettingsFile, defaultConfig);
} else {
valProposerConfig = {defaultConfig} as ValidatorProposerConfig;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving to a function below the handler getValProposerConfig(args): ValidatorProposerConfig

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean separate out this parseProposerConfig section from getProposerConfigFromArgs?

}
return valProposerConfig;
}