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

cmd: add combine command #1799

Merged
merged 23 commits into from
Feb 22, 2023
Merged

cmd: add combine command #1799

merged 23 commits into from
Feb 22, 2023

Conversation

gsora
Copy link
Collaborator

@gsora gsora commented Feb 13, 2023

This commit adds the combine command.

It accepts an input directory path containing all the private key shares of a DV, an output directory path, and a lockfile path.

While the output directory can be nonexistent (will be created automatically), input and lockfile paths must exist.

The result of this process is a keystore file containing the combined private key of the distributed validator.

category: feature
ticket: #1311

This command accepts an input directory path containing all the private key shares of a DV, an output directory path, and a lockfile path.

While the output directory can be nonexistent (will be created automatically), input and lockfile paths must exist.

The result of this process is a keystore file containing the combined private key of the distributed validator.
@gsora gsora added the enhancement New feature or request label Feb 13, 2023
@gsora gsora self-assigned this Feb 13, 2023
@codecov
Copy link

codecov bot commented Feb 13, 2023

Codecov Report

Base: 54.99% // Head: 55.10% // Increases project coverage by +0.10% 🎉

Coverage data is based on head (d21717a) compared to base (eeab76e).
Patch coverage: 50.30% of modified lines in pull request are covered.

❗ Current head d21717a differs from pull request most recent head 3107d1a. Consider uploading reports for the commit 3107d1a to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1799      +/-   ##
==========================================
+ Coverage   54.99%   55.10%   +0.10%     
==========================================
  Files         169      170       +1     
  Lines       22137    22215      +78     
==========================================
+ Hits        12175    12242      +67     
- Misses       8396     8404       +8     
- Partials     1566     1569       +3     
Impacted Files Coverage Δ
cmd/cmd.go 80.95% <0.00%> (-0.78%) ⬇️
cmd/combine.go 0.00% <0.00%> (ø)
combine/combine.go 60.29% <60.29%> (ø)
app/app.go 61.35% <0.00%> (+0.64%) ⬆️
core/tracker/tracker2/tracker.go 76.62% <0.00%> (+3.18%) ⬆️
app/vmock.go 79.48% <0.00%> (+6.66%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@gsora gsora marked this pull request as draft February 13, 2023 10:45
@gsora gsora marked this pull request as ready for review February 13, 2023 11:23
@gsora gsora requested a review from OisinKyne February 13, 2023 11:37
cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated
func bindCombineFlags(flags *pflag.FlagSet, inputDir, outputDir, lockfile *string) {
flags.StringVar(inputDir, "keyfile-dir", "./validator-keys", `Directory containing all the "keyfile-N.json" and "keyfile-N.txt" files`)
flags.StringVar(outputDir, "out-dir", "./recombined-validator-key", "Directory where to save the resulting private key")
flags.StringVar(lockfile, "lockfile-path", "./lock.json", "Charon lock.json file")
Copy link
Contributor

Choose a reason for hiding this comment

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

suggest aligning with run flag: lock-file

Copy link
Contributor

@OisinKyne OisinKyne Feb 13, 2023

Choose a reason for hiding this comment

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

How does this work for e.g. a 10 validator, 4 node cluster? Do you put 40 files in a flat folder? or 4 subfolders? do we do any traversing/exploring subdirectories? Would be great if we could dump e.g. 4 .charon folders slightly renamed alongside one another, and our command is able by default to find the lockfile(s) and validator_keys folders.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe this command should be require some work from the user: to recombine all the keys, it's assumed some work is needed to arrange them in the correct order.

If we want to automate some part of this, we could (as you suggested) let the user provide the validator_keys directories renamed with the public key share, and do some directory traversing to recombine.

I still believe though that each validator recombination needs a separate invocation of the combine command.

Copy link
Contributor

@OisinKyne OisinKyne Feb 13, 2023

Choose a reason for hiding this comment

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

Mmmm similar to exits, I believe in practice we will see people mostly combine/exit all validators in a cluster or none. I think partial will be a rare edge case. With the scale of enterprise deployments, I don't think a separate invocation per key is scalable, everyone will DIY their own shell script for-loop.

I think @Battenfield should user test some options, but I think 'people will send their .charon folder to the combiner, the combiner will dump all n folders into one directory, having to rename each of them something other than .charon (awkward but feels unavoidable), and from there I think they should be able to run charon combine --data-dir ./this-folder-ive-prepared, from where we should be able to traverse it, assuming that each subdir is a .charon folder, and we can find ./this-folder-ive-prepared/*/cluster-lock.json in each of them, along with their enr so we know who is who, and even a ./this-folder-ive-prepared/*/validator_keys/* path that we can use to find hopefully all of the keystores we expect. Assuming we can find everything we need, get or create output-dir and spit out the combined keystores. This discovery should probably only need to find threshold number of operators rather than everyone in the lock file (e.g. 3 folders of stuff in a four node cluster, so that you can combine with the minimum number of participants).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

everyone will DIY their own shell script for-loop

Correct, I implied users will do something similar... I don't see it as being completely unfeasible, and on the flipside depending on deployments needs it could actually be more comfortable.

I imagine a K8S deployment where validator keys are secrets, you have to script your way around the secrets API to mount directories in pods and have combine work with them anyway.

Regardless, I'm open to changes on this.

Copy link
Contributor

@OisinKyne OisinKyne Feb 14, 2023

Choose a reason for hiding this comment

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

Yeah, you make me also wonder should we add a keymanager-address flag like the DKG and create cluster command, that can push the combined keys into a web3 keymanager. (can make a separate ticket for this, too much scope creep)

I think humour me on this one and we go with this combine command attempting to combine every private key in the lockfile and warns/(errors and needs to be flag overridden) if it can't discover a threshold of the private keys and passwords it needs to complete.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alright, let's go this way.

An issue that arises is usability.

Users don't know what's in their keystore (what share is represented by a given keystore file?), and even if this is an information that can be easily obtained by opening the JSON file, I'd say not everybody is willing to do so.

A thing we could do is the following:

  • each operator creates a directory named after their ENR, and places all the JSON and txt keystore files in it
  • one operator collects all the directories and places them in a parent directory, and then invokes the combine command pointing it to said directory, the lock file and an output directory
  • the tool firstly makes sure that there are the same number of directories as the number of operators, and that those directories are named after the ENRs
  • for each validator N, counting from 0 to NumValidators, combine reads keystore-N.{json, txt} and yields the public key, checking that it's contained in the N-th validator entry in the lock file
  • assuming this is correct, it proceeds with the combination and saves the resulting private key in the destination directory, with the JSON and txt files named after the root public key
  • runs this for each validator in the lock file

WDYT?

Copy link
Collaborator Author

@gsora gsora Feb 15, 2023

Choose a reason for hiding this comment

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

@corverroos @OisinKyne considering this process yields recombined private keys, I think it's better to write a file with the private key bytes to disk instead of our own keystore files: if a group of operator wants to recombine their private key(s), most probably they want to stop using charon, so writing keystore files seems a bit pointless.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

@gsora the intention behind writing a keystore is to align with an ethereum standard here https://eips.ethereum.org/EIPS/eip-2335 This is a format supported by all clients. The idea is they would import these keystores into the clients as if they were exported from the eth2-staking-cli. Our .charon/validator_keys folder also matches that structure (albeit with key shares not 'normal' keys)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, I’ll write keystore files then.

The rest of the UX is fine for you?

cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated Show resolved Hide resolved
@gsora gsora added the do not merge Indicate to bulldozer bot that this PR should not be merged label Feb 13, 2023
@gsora gsora linked an issue Feb 16, 2023 that may be closed by this pull request
@gsora gsora removed the do not merge Indicate to bulldozer bot that this PR should not be merged label Feb 16, 2023
@gsora
Copy link
Collaborator Author

gsora commented Feb 16, 2023

Removing the do-not-merge tag since the code is ready for prime time.

@OisinKyne I added some docs on what the combine command expects from the user in order to work on the function doc itself, I'll replicate them in the obol-docs as soon as I have an ACK on the UX.

cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated Show resolved Hide resolved
combine/combine.go Outdated Show resolved Hide resolved
combine/combine.go Outdated Show resolved Hide resolved
commit a04ce78
Author: Dhruv Bodani <[email protected]>
Date:   Mon Feb 20 20:04:37 2023 +0530

    *: upgrade to go1.20.1 (#1824)

    Upgrades go version to 1.20.1.

    category: misc
    ticket: #1821

commit c63199e
Author: corver <[email protected]>
Date:   Mon Feb 20 15:36:34 2023 +0200

    gomod: upgrade libp2p  (#1823)

    Upgrades libp2p to v0.25.1

    category: refactor
    ticket: none

commit e3161a3
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon Feb 20 10:41:34 2023 +0000

    build(deps): Bump golang.org/x/net from 0.6.0 to 0.7.0 (#1822)

    (dependabot) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.6.0 to 0.7.0.

commit ce76297
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Mon Feb 20 10:36:13 2023 +0000

    build(deps): Bump github.com/attestantio/go-eth2-client from 0.15.3 to 0.15.4 (#1819)

    Bumps [github.com/attestantio/go-eth2-client](https://github.com/attestantio/go-eth2-client) from 0.15.3 to 0.15.4.
    <details>
    <summary>Commits</summary>
    <ul>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/c06075ae2b1cce11556e07dde1ff7b650668b80c"><code>c06075a</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/attestantio/go-eth2-client/issues/46">#46</a> from attestantio/dvt</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/776af407e5ea768d2e534331cf85a7fb3ac59d59"><code>776af40</code></a> Merge branch 'master' into dvt</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/d21be8e84af4a8e953b996de7818e8698e8bdb2d"><code>d21be8e</code></a> Fixes</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/6a22388d146182276771328f6e415c4fa17bb362"><code>6a22388</code></a> Update http/blindedbeaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/c2b26ac084e7227396e33021bd1035b1a9fdb9b2"><code>c2b26ac</code></a> Update http/blindedbeaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/3ca9b6d4dee2ddfb607808cfcd52862be5ad8f2e"><code>3ca9b6d</code></a> Update http/beaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/9646809365152d3eb7c239d0339e4aac91bef58a"><code>9646809</code></a> Update http/beaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/d29581f31c965436f465c034b56c507c5e0f73e9"><code>d29581f</code></a> Update http/beaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/870a2f3a67465c8a7c2476dc284103d0a05a06e0"><code>870a2f3</code></a> Update http/beaconblockproposal.go</li>
    <li><a href="https://github.com/attestantio/go-eth2-client/commit/097a53c5ef6c46b844c2804693bff3967f99db86"><code>097a53c</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/attestantio/go-eth2-client/issues/45">#45</a> from avalkov/support-extra-headers</li>
    <li>Additional commits viewable in <a href="https://github.com/attestantio/go-eth2-client/compare/v0.15.3...v0.15.4">compare view</a></li>
    </ul>
    </details>
    <br />

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/attestantio/go-eth2-client&package-manager=go_modules&previous-version=0.15.3&new-version=0.15.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

    [//]: # (dependabot-automerge-start)
    [//]: # (dependabot-automerge-end)

    ---

    <details>
    <summary>Dependabot commands and options</summary>
    <br />

    You can trigger Dependabot actions by commenting on this PR:
    - `@dependabot rebase` will rebase this PR
    - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
    - `@dependabot merge` will merge this PR after your CI passes on it
    - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
    - `@dependabot cancel merge` will cancel a previously requested merge and block automerging
    - `@dependabot reopen` will reopen this PR if it is closed
    - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

    </details>

commit bf0f469
Author: corver <[email protected]>
Date:   Fri Feb 17 15:48:09 2023 +0200

    dkg: add deposit data to lock file (#1815)

    Populates the deposit data field in the generated lock files.

    category: feature
    ticket: #1775

commit b16fd01
Author: Gianguido Sorà <[email protected]>
Date:   Thu Feb 16 17:45:18 2023 +0100

    p2p: remove pointless "No connections to relay" log line (#1816)

    That log line wasn't useful anyway.

    category: refactor
    ticket: none

commit 14d6b7c
Author: corver <[email protected]>
Date:   Thu Feb 16 17:43:32 2023 +0200

    cluster: draft v1.6 lock with deposit data (#1813)

    Adds a new draft lock version v1.6 which includes deposit data in the lock's distributed validators.

    category: feature
    ticket: #1775

commit d56cfd5
Author: corver <[email protected]>
Date:   Wed Feb 15 16:25:55 2023 +0200

    cmd: route libp2p to charon logger (#1810)

    Fixes issue with libp2p using its own logger. Instead, it now logs via the configured charon logger (incl pushing to loki if enabled).

    category: refactor
    ticket: none

commit f1d260b
Author: corver <[email protected]>
Date:   Wed Feb 15 09:17:04 2023 +0200

    app: add a test for remote relay connections (#1808)

    Adds a test/script to test remote relay connection count.

    category: test
    ticket: none

commit 3c91888
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Feb 14 17:07:50 2023 +0000

    build(deps): Bump go.uber.org/goleak from 1.2.0 to 1.2.1 (#1807)

    (dependabot) Bumps [go.uber.org/goleak](https://github.com/uber-go/goleak) from 1.2.0 to 1.2.1.

commit fbfbfdf
Author: corver <[email protected]>
Date:   Tue Feb 14 17:45:11 2023 +0200

    cmd/relay: fix connection limits (#1805)

    Fixes issue with relay limits not being applied correctly getting stuck at 256 connections.

    category: bug
    ticket: none

commit bc73565
Author: Dhruv Bodani <[email protected]>
Date:   Tue Feb 14 21:00:09 2023 +0530

    compose: fix very large smoke test (#1797)

    Enables very large smoke test with 7 of 10 cluster and 100 validators.

    category: test
    ticket: #1471

commit 2403c2a
Author: Abhishek Kumar <[email protected]>
Date:   Tue Feb 14 16:48:02 2023 +0530

    validatorapi: fix content type in http response (#1803)

    Fixes sending incorrect `Content-Type` header in HTTP response. Found this bug while integrating nimbus VC which errored when charon sent `text/plain` instead of `application/json`.

    category: bug
    ticket: none
@gsora
Copy link
Collaborator Author

gsora commented Feb 22, 2023

@OisinKyne instead of creating a single validator_keys output directory, I create one for each validator named after its public key.

I implemented what you suggested wrt the input directory tree (aka it's compatible with .charon directory scheme).

Is that fine?

@OisinKyne
Copy link
Contributor

OisinKyne commented Feb 22, 2023

@OisinKyne instead of creating a single validator_keys output directory, I create one for each validator named after its public key.

I implemented what you suggested wrt the input directory tree (aka it's compatible with .charon directory scheme).

Is that fine?

Have you tried importing private keys in that format into validator clients? I don't believe that structure would be recognised. See:
https://lighthouse-book.sigmaprime.io/validator-import-launchpad.html#instructions
https://chainsafe.github.io/lodestar/usage/validator-management/#option-1-import-keys-to-lodestars-keystores-folder
https://docs.teku.consensys.net/Reference/CLI/CLI-Syntax#validator-keys
https://docs.prylabs.network/docs/wallet/nondeterministic#import-validator-accounts

I think only one validator_keys folder, files starting with keystore- and .json/.txt for file and password are all important conventions.

Second thing I want to check is that the format charon create cluster outputs can immediately work with charon combine ... will do now

combine/combine.go Outdated Show resolved Hide resolved
@OisinKyne
Copy link
Contributor

Screenshot 2023-02-22 at 12 47 20

Okay this is way old, was under the impression this was changed, will make tickets for this

@gsora
Copy link
Collaborator Author

gsora commented Feb 22, 2023

I think only one validator_keys folder, files starting with keystore- and .json/.txt for file and password are all important conventions.

Oh I see!

Ok, I'll move the json and txt file down under a validator_keys directory, then I'd say we're good to go with the merge.

cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated Show resolved Hide resolved
cmd/combine.go Outdated
}

func bindCombineFlags(flags *pflag.FlagSet, keystoresDir *string, force *bool) {
flags.StringVar(keystoresDir, "keystores-dir", "./", `Directory containing all the keystore files organized by ENR, and the lock file.`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
flags.StringVar(keystoresDir, "keystores-dir", "./", `Directory containing all the keystore files organized by ENR, and the lock file.`)
flags.StringVar(keystoresDir, "cluster-dir", ".charon/cluster/", `Parent directory containing a number of .charon subdirectories from each node in the cluster.`)

--cluster-dir aligns with charon create cluster, see no keystores-dir in our code yet.

Not sure about the default folder, maybe it could be ./obol/, will post something in #dev or see if chris has a strong product opinion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would push for ./.charon to keep it aligned with all the other cli commands.

}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Haven't looked at the encompassing error handling around this function, but can a partial success happen where we don't manage to combine all keys successfully but do output some? We probably don't want to allow that, and should fail entirely if that's not already the case. (assume we freak out if err != nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, the function will error out if one of the validator keys can't be combined but the artifacts of the other keys will remain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm merging this PR, let's address this behavior in a separate one if needs a change.

return cluster.Lock{}, nil, errors.Wrap(err, "cluster lock hash verification failed")
}

if err := lock.VerifySignatures(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we allow --no-verify flags on this command @corverroos ? Imagined use case would be trying to recombine a really old cluster in a modern (>v1.0) charon version, and not being able to get it to verifySignatures correctly? (Or we can just pinky promise to always support old signatures/verification for anything that could possibly be in prod?)

Happy to skip this if not a concern

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm merging this PR, let's address this behavior in a separate one if needs a change.

gsora and others added 2 commits February 22, 2023 14:18
Copy link
Contributor

@OisinKyne OisinKyne left a comment

Choose a reason for hiding this comment

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

Just want a slight change to the output format, a couple of tweaks to the explainer text.

For now, I've suggested a different default folder path, I don't like what I've suggested as .charon/cluster, but that is where the default create goes, so I think we should put the default combine there too, and fix both shortly together.

@gsora gsora added the merge when ready Indicates bulldozer bot may merge when all checks pass label Feb 22, 2023
@obol-bulldozer obol-bulldozer bot merged commit eeff3cd into main Feb 22, 2023
@obol-bulldozer obol-bulldozer bot deleted the gsora/combine_cli branch February 22, 2023 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request merge when ready Indicates bulldozer bot may merge when all checks pass
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make it easier to recombine keys
4 participants