mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d10b7f2739 | ||
|
|
5f4be0415f | ||
|
|
0f1c6f09bd | ||
|
|
6118f9cab8 | ||
|
|
829dbd3bf2 | ||
|
|
f0ad10463e | ||
|
|
3d0dab0b95 | ||
|
|
5abfabc355 | ||
|
|
e84b600d5d | ||
|
|
e64a46f126 | ||
|
|
0746fa3048 | ||
|
|
94eb3fbca7 | ||
|
|
48d63398d4 | ||
|
|
4e1f47e187 | ||
|
|
85c7c7fc55 | ||
|
|
05406e8d81 | ||
|
|
70d451cea5 | ||
|
|
c270d7a2f7 | ||
|
|
115d037948 | ||
|
|
b34a633e53 | ||
|
|
d1b989a711 | ||
|
|
8078359eab | ||
|
|
7de8dc7424 | ||
|
|
987dbd26c6 | ||
|
|
1f0aeac9b4 | ||
|
|
37cab43242 | ||
|
|
6f94e599ac | ||
|
|
86bc3d5418 | ||
|
|
8cc45f0b4b | ||
|
|
77df1bba43 | ||
|
|
1d4e52cb49 | ||
|
|
c1e124fbd3 | ||
|
|
91bc6ec86d | ||
|
|
cd55abc9bf | ||
|
|
44478e147e | ||
|
|
61b73b0f61 | ||
|
|
5ce7491d73 | ||
|
|
b5ecf56382 | ||
|
|
0acb57df48 | ||
|
|
325baf9f8e | ||
|
|
1c2385e413 | ||
|
|
c06a709463 | ||
|
|
735cc3ae4b | ||
|
|
2da00b3930 | ||
|
|
f5db041ad8 | ||
|
|
1d94b90013 | ||
|
|
ff6a95d6a9 | ||
|
|
1b7e867251 | ||
|
|
e7918125b9 | ||
|
|
6dd96cf916 | ||
|
|
0b2ef25cc4 | ||
|
|
834e3310c5 | ||
|
|
fc9c4cfe0c | ||
|
|
f2bb5e0d51 | ||
|
|
f5fc8b363d | ||
|
|
a31509f7d6 | ||
|
|
4b451ec2fa | ||
|
|
a8fee14b89 | ||
|
|
9cfb1b5637 | ||
|
|
df1724f763 | ||
|
|
f1a586ca56 | ||
|
|
b9cb926662 | ||
|
|
220cc21356 | ||
|
|
5bc7c1a26c | ||
|
|
4c3237cd0d | ||
|
|
7215e04a69 | ||
|
|
71e9c0471b | ||
|
|
1a6f402fb8 | ||
|
|
b9297c2506 | ||
|
|
1032789706 | ||
|
|
51e289b72b | ||
|
|
a75a350cef | ||
|
|
aa34e61a80 | ||
|
|
3f33f04be2 | ||
|
|
5733b5b638 | ||
|
|
e1ce81c81d | ||
|
|
bd67ba0307 | ||
|
|
12bb5a7ab8 | ||
|
|
d57dbbf104 | ||
|
|
6482b4add6 | ||
|
|
b672e83470 | ||
|
|
710e891844 | ||
|
|
9e3bf521a0 | ||
|
|
3ca899b832 | ||
|
|
d221c0544c | ||
|
|
eed07a39a3 | ||
|
|
5a5edacd11 | ||
|
|
c7f4cb0ca5 | ||
|
|
fbc2171053 | ||
|
|
57946f1552 | ||
|
|
b487bb042c | ||
|
|
0c8029c950 | ||
|
|
b3bbeea3fb | ||
|
|
09105549b1 | ||
|
|
364c764de2 | ||
|
|
3f1fe30959 | ||
|
|
aae360ccb8 | ||
|
|
32a3e4649d | ||
|
|
dd3bd121fe | ||
|
|
edf84f47ba | ||
|
|
320c5ee9ab | ||
|
|
36a1674549 | ||
|
|
693b2a6961 | ||
|
|
e5481f9074 | ||
|
|
c89712699c | ||
|
|
7130855b73 | ||
|
|
d505966a42 | ||
|
|
732c07238a | ||
|
|
b843d0077e | ||
|
|
f11cf0cbf5 | ||
|
|
ea43e71e60 | ||
|
|
71b01ee2aa | ||
|
|
de349f5691 | ||
|
|
b1d2e94854 | ||
|
|
2293079861 | ||
|
|
69867ba21c | ||
|
|
f72af8247a | ||
|
|
9ca4406a80 | ||
|
|
1ad82adf80 | ||
|
|
11eb440df2 | ||
|
|
e2192f6992 | ||
|
|
864aa24484 | ||
|
|
9f8d7c3f7b | ||
|
|
4f643ad952 | ||
|
|
6551db8f9a | ||
|
|
d0f944eacc | ||
|
|
be3fd8edd2 | ||
|
|
afcbd177a3 | ||
|
|
a38ff57508 | ||
|
|
7d4ead9640 | ||
|
|
945f2ca47c | ||
|
|
b61eaefb0d | ||
|
|
6786742004 | ||
|
|
6cb8886672 | ||
|
|
ab5c6317ca | ||
|
|
5896579496 | ||
|
|
8df1b74a79 | ||
|
|
47cdd8402a | ||
|
|
432967c14c | ||
|
|
36033d66d2 | ||
|
|
0b354c3ec5 | ||
|
|
b3e6178149 |
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
ethdo
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
coverage.html
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Vim
|
||||
*.sw?
|
||||
|
||||
# Local TODO
|
||||
TODO.md
|
||||
|
||||
Dockerfile
|
||||
115
.github/workflows/release.yml
vendored
Normal file
115
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
|
||||
- name: Set env
|
||||
run: |
|
||||
echo '::set-env name=GO111MODULE::on'
|
||||
# Release tag comes from the github reference.
|
||||
RELEASE_TAG=$(echo ${GITHUB_REF} | sed -e 's!.*/!!')
|
||||
echo "::set-env name=RELEASE_TAG::${RELEASE_TAG}"
|
||||
echo "::set-output name=RELEASE_TAG::${RELEASE_TAG}"
|
||||
# Ensure the release tag has expected format.
|
||||
echo ${RELEASE_TAG} | grep -q '^v' || exit 1
|
||||
# Release version is same as release tag without leading 'v'.
|
||||
RELEASE_VERSION=$(echo ${GITHUB_REF} | sed -e 's!.*/v!!')
|
||||
echo "::set-env name=RELEASE_VERSION::${RELEASE_VERSION}"
|
||||
echo "::set-output name=RELEASE_VERSION::${RELEASE_VERSION}"
|
||||
|
||||
- name: Build
|
||||
run: go build -v -ldflags="-X github.com/wealdtech/ethdo/cmd.ReleaseVersion=${RELEASE_VERSION}" .
|
||||
|
||||
- name: Test
|
||||
run: go test -v .
|
||||
|
||||
- name: Fetch xgo
|
||||
run: |
|
||||
go get github.com/suburbandad/xgo
|
||||
|
||||
- name: Cross-compile
|
||||
run: xgo -v -x -ldflags="-X github.com/wealdtech/ethdo/cmd.ReleaseVersion=${RELEASE_VERSION}" --targets="linux/amd64,linux/arm64,windows/amd64" github.com/wealdtech/ethdo
|
||||
|
||||
- name: Create windows zip file
|
||||
run: |
|
||||
mv ethdo-windows-4.0-amd64.exe ethdo.exe
|
||||
zip --junk-paths ethdo-${RELEASE_VERSION}-windows-exe.zip ethdo.exe
|
||||
|
||||
- name: Create linux AMD64 tgz file
|
||||
run: |
|
||||
mv ethdo-linux-amd64 ethdo
|
||||
tar zcf ethdo-${RELEASE_VERSION}-linux-amd64.tar.gz ethdo
|
||||
|
||||
- name: Create linux ARM64 tgz file
|
||||
run: |
|
||||
mv ethdo-linux-arm64 ethdo
|
||||
tar zcf ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz ethdo
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ env.RELEASE_VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload windows zip file
|
||||
id: upload-release-asset-windows
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ethdo-${{ env.RELEASE_VERSION }}-windows-exe.zip
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-windows-exe.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload linux AMD64 tgz file
|
||||
id: upload-release-asset-linux-amd64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ethdo-${{ env.RELEASE_VERSION }}-linux-amd64.tar.gz
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-linux-amd64.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: Upload linux ARM64 tgz file
|
||||
id: upload-release-asset-linux-arm64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ethdo-${{ env.RELEASE_VERSION }}-linux-arm64.tar.gz
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-linux-arm64.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
14
CHANGELOG.md
Normal file
14
CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
||||
1.6.0:
|
||||
- update BLS HKDF function to match spec 04
|
||||
- add --launchpad option to "validator depositdata" to output data in launchpad format
|
||||
1.5.9:
|
||||
- fix issue where wallet mnemonics were not normalised to NFKD
|
||||
- "block info" supports fetching the gensis block (--slot=0)
|
||||
- "attester inclusion" command finds the inclusion slot for a validator's attestation
|
||||
- "account info" with verbose option now displays participants for distributed accounts
|
||||
- fix issue where distributed account generation without a passphrase was not allowed
|
||||
|
||||
1.5.8:
|
||||
- allow raw deposit transactions to be supplied to "deposit verify"
|
||||
- move functionality of "account withdrawalcredentials" to be part of "account info"
|
||||
- add genesis validators root to "chain info"
|
||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM golang:1.14-buster as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN go build
|
||||
|
||||
FROM debian:buster-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/ethdo /app
|
||||
|
||||
ENTRYPOINT ["/app/ethdo"]
|
||||
236
README.md
236
README.md
@@ -5,11 +5,14 @@
|
||||
|
||||
A command-line tool for managing common tasks in Ethereum 2.
|
||||
|
||||
** Please note that this library uses standards that are not yet final, and as such may result in changes that alter public and private keys. Do not use this library for production use just yet **
|
||||
**Please note that this tool and its underlying libraries have not yet undergone a security audit; use at your own risk.**
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Install](#install)
|
||||
- [Binaries](#binaries)
|
||||
- [Docker](#docker)
|
||||
- [Source](#source)
|
||||
- [Usage](#usage)
|
||||
- [Maintainers](#maintainers)
|
||||
- [Contribute](#contribute)
|
||||
@@ -17,24 +20,74 @@ A command-line tool for managing common tasks in Ethereum 2.
|
||||
|
||||
## Install
|
||||
|
||||
### Binaries
|
||||
|
||||
Binaries for the latest version of `ethdo` can be obtained from [the releases page](https://github.com/wealdtech/ethdo/releases).
|
||||
|
||||
### Docker
|
||||
|
||||
You can obtain the latest version of `ethdo` using docker with:
|
||||
|
||||
```
|
||||
docker pull wealdtech/ethdo
|
||||
```
|
||||
|
||||
### Source
|
||||
`ethdo` is a standard Go program which can be installed with:
|
||||
|
||||
```sh
|
||||
go get github.com/wealdtech/ethdo
|
||||
GO111MODULE=on go get github.com/wealdtech/ethdo
|
||||
```
|
||||
|
||||
Note that `ethdo` requires at least version 1.13 of go to operate. The version of go can be found with `go version`.
|
||||
|
||||
If this does not work please see the [troubleshooting](https://github.com/wealdtech/ethdo/blob/master/docs/troubleshooting.md) page.
|
||||
|
||||
The docker image can be build locally with:
|
||||
|
||||
```sh
|
||||
docker build -t ethdo .
|
||||
```
|
||||
|
||||
You can run `ethdo` using docker after that. Example:
|
||||
|
||||
```sh
|
||||
docker run -it ethdo --help
|
||||
```
|
||||
|
||||
Note that that many `ethdo` commands connect to the beacon node to obtain information. If the beacon node is running directly on the server this requires the `--network=host` command, for example:
|
||||
|
||||
```sh
|
||||
docker run --network=host ethdo chain status
|
||||
```
|
||||
|
||||
Alternatively, if the beacon node is running in a separate docker container a shared network can be created with `docker network create eth2` and accessed by adding `--network=eth2` added to both the beacon node and `ethdo` containers.
|
||||
|
||||
## Usage
|
||||
|
||||
ethdo contains a large number of features that are useful for day-to-day interactions with the Ethereum 2 blockchain.
|
||||
|
||||
### Wallets and accounts
|
||||
|
||||
ethdo uses the [go-eth2-wallet](https://github.com/wealdtech/go-eth2-wallet) system to provide unified access to different wallet types.
|
||||
ethdo uses the [go-eth2-wallet](https://github.com/wealdtech/go-eth2-wallet) system to provide unified access to different wallet types. When on the filesystem the locations of the created wallets and accounts are:
|
||||
|
||||
- for Linux: $HOME/.config/ethereum2/wallets
|
||||
- for OSX: $HOME/Library/Application Support/ethereum2/wallets
|
||||
- for Windows: %APPDATA%\ethereum2\wallets
|
||||
|
||||
If using the filesystem store, the additional parameter `basedir` can be supplied to change this location.
|
||||
|
||||
> If using docker as above you can make this directory accessible to docker to make wallets and accounts persistent. For example, for linux you could use the following command to list your wallets on Linux:
|
||||
>
|
||||
> ```
|
||||
> docker run -v $HOME/.config/ethereum2/wallets:/data ethdo --basedir=/data wallet list
|
||||
> ```
|
||||
>
|
||||
> This will allow you to use `ethdo` with or without docker, with the same location for wallets and accounts.
|
||||
|
||||
All ethdo comands take the following parameters:
|
||||
|
||||
- `store`: the name of the storage system for wallets. This can be one of "filesystem" or "s3", and defaults to "filesystem"
|
||||
- `store`: the name of the storage system for wallets. This can be one of "filesystem" (for local storage of the wallet) or "s3" (for remote storage of the wallet on [Amazon's S3](https://aws.amazon.com/s3/) storage system), and defaults to "filesystem"
|
||||
- `storepassphrase`: the passphrase for the store. If this is empty the store is unencrypted
|
||||
- `walletpassphrase`: the passphrase for the wallet. This is required for some wallet-centric operations such as creating new accounts
|
||||
- `accountpassphrase`: the passphrase for the account. This is required for some account-centric operations such as signing data
|
||||
@@ -72,184 +125,37 @@ If set, the `--debug` argument will output additional information about the oper
|
||||
|
||||
Commands will have an exit status of 0 on success and 1 on failure. The specific definition of success is specified in the help for each command.
|
||||
|
||||
### `wallet` commands
|
||||
## Rules for account passphrases
|
||||
|
||||
#### `accounts`
|
||||
Account passphrases are used in various places in `ethdo`. Where they are used, the following rules apply:
|
||||
|
||||
`ethdo wallet accouts` lists the accounts within a wallet.
|
||||
- commands that require passphrases to operate, for example unlocking an account, can be supplied with multiple passphrases. If they are, then each passphrase is tried until one succeeds or they all fail
|
||||
- commands that require passphrases to create, for example creating an account, must be supplied with a single passphrase. If more than one passphrase is supplied the command will fail
|
||||
|
||||
```sh
|
||||
$ ethdo wallet accounts --wallet="Personal wallet"
|
||||
Auctions
|
||||
Operations
|
||||
Spending
|
||||
```
|
||||
In addition, the following rules apply to passphrases supplied on the command line:
|
||||
|
||||
With the `--verbose` flag this will provide the public key of the accounts.
|
||||
- passphrases **must not** start with `0x`
|
||||
- passphrases **must not** contain the comma (,) character
|
||||
|
||||
```sh
|
||||
$ ethdo wallet accounts --wallet="Personal wallet" --verbose
|
||||
Auctions: 0x812f340269c315c1d882ae7c13cdaddf862dbdbd482b1836798b2070160dd1e194088cc6f39347782028d1e56bd18674
|
||||
Operations: 0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670
|
||||
Spending: 0x85dfc6dcee4c9da36f6473ec02fda283d6c920c641fc8e3a76113c5c227d4aeeb100efcfec977b12d20d571907d05650
|
||||
```
|
||||
#### `create`
|
||||
# Commands
|
||||
|
||||
`ethdo wallet create` creates a new wallet with the given parameters. Options for creating a wallet include:
|
||||
- `wallet`: the name of the wallet to create (defaults to "primary")
|
||||
- `type`: the type of wallet to create. This can be either "nd" for a non-deterministic wallet, where private keys are generated randomly, or "hd" for a hierarchical deterministic wallet, where private keys are generated from a seed and path as per [ERC-2333](https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md) (defaults to "nd")
|
||||
- `walletpassphrase`: the passphrase for of the wallet. This is required for hierarchical deterministic wallets, to protect the seed
|
||||
Command information, along with sample outputs and optional arguments, is available in [the usage section](https://github.com/wealdtech/ethdo/blob/master/docs/usage.md).
|
||||
|
||||
```sh
|
||||
$ ethdo wallet create --wallet="Personal wallet" --type="hd" --walletpassphrase="my wallet secret"
|
||||
```
|
||||
# HOWTO
|
||||
|
||||
#### `export`
|
||||
|
||||
`ethdo wallet export` exports the wallet and all of its accounts. Options for exporting a wallet include:
|
||||
- `wallet`: the name of the wallet to export (defaults to "primary")
|
||||
- `exportpassphrase`: the passphrase with which to encrypt the wallet backup
|
||||
|
||||
```sh
|
||||
$ ethdo wallet export --wallet="Personal wallet" --exportpassphrase="my export secret"
|
||||
0x01c7a27ad40d45b4ae5be5f...
|
||||
```
|
||||
|
||||
The encrypted wallet export is written to the console; it can be redirected to store it in a file.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet export --wallet="Personal wallet" --exportpassphrase="my export secret" >export.dat
|
||||
```
|
||||
|
||||
#### `import`
|
||||
|
||||
`ethdo wallet import` imports a wallet and all of its accounts exported by `ethdo wallet export`. Options for importing a wallet include:
|
||||
- `importdata`: the data exported by `ethdo wallet export`
|
||||
- `importpassphrase`: the passphrase that was provided to `ethdo wallet export` to encrypt the data
|
||||
|
||||
```sh
|
||||
$ ethdo wallet import --importdata="0x01c7a27ad40d45b4ae5be5f..." --importpassphrase="my export secret"
|
||||
```
|
||||
|
||||
The encrypted wallet export can be read from a file. For example with Unix systems:
|
||||
|
||||
```sh
|
||||
$ ethdo wallet import --importdata=`cat export.dat` --importpassphrase="my export secret"
|
||||
```
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo wallet info` provides information about a given wallet. Options include:
|
||||
- `wallet`: the name of the wallet
|
||||
|
||||
```sh
|
||||
$ ethdo wallet info --wallet="Personal wallet"
|
||||
Type: hierarchical deterministic
|
||||
Accounts: 3
|
||||
```
|
||||
|
||||
#### `list`
|
||||
|
||||
`ethdo wallet list` lists all wallets in the store.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet list
|
||||
Personal wallet
|
||||
```
|
||||
|
||||
**N.B.** encrypted wallets will not show up in this list unless the correct passphrase for the store is supplied.
|
||||
|
||||
#### `seed`
|
||||
|
||||
`ethdo wallet seed` provides the seed for hierarchical deterministic wallets. Options include:
|
||||
- `wallet`: the name of the wallet
|
||||
- `walletpassphrase`: the passphrase for the wallet
|
||||
|
||||
```sh
|
||||
$ ethdo wallet seed --wallet="Personal wallet" --walletpassphrase="my wallet secret"
|
||||
decorate false mail domain gain later motion chair tank muffin smoke involve witness bean shell urge team solve share truly shadow decorate jeans hen
|
||||
```
|
||||
|
||||
### `account` commands
|
||||
|
||||
Account commands focus on information about local accounts, generally those used by Geth and Parity but also those from hardware devices.
|
||||
|
||||
#### `create`
|
||||
|
||||
`ethdo account create` creates a new account with the given parameters. Options for creating an account include:
|
||||
- `account`: the name of the account to create
|
||||
- `passphrase`: the passphrase for the account
|
||||
|
||||
Note that for hierarchical deterministic wallets you will also need to supply `--walletpassphrase` to unlock the wallet seed.
|
||||
|
||||
```sh
|
||||
$ ethdo account create --account="Personal wallet/Operations" --walletpassphrase="my wallet secret" --passphrase="my account secret"
|
||||
```
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo account info` provides information about the given account. Options include:
|
||||
- `account`: the name of the account on which to obtain information
|
||||
|
||||
```sh
|
||||
$ ethdo account info --account="Personal wallet/Operations"
|
||||
Public key: 0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670
|
||||
```
|
||||
|
||||
### `signature` commands
|
||||
|
||||
Signature commands focus on generation and verification of data signatures.
|
||||
|
||||
### `signature sign`
|
||||
|
||||
`ethdo signature sign` signs provided data. Options include:
|
||||
- `data`: the data to sign, as a hex string
|
||||
- `domain`: the domain in which to sign the data. This is an 8-byte hex string (default 0x0000000000000000)
|
||||
- `account`: the account to sign the data
|
||||
- `passphrase`: the passphrase for the account
|
||||
|
||||
```sh
|
||||
$ ethdo signature sign --data="0x08140077a94642919041503caf5cc1795b23ecf2" --account="Personal wallet/Operations" --passphrase="my account secret"
|
||||
0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b
|
||||
```
|
||||
|
||||
### `signature verify`
|
||||
|
||||
`ethdo signature verify` verifies signed data. Options include:
|
||||
- `data`: the data whose signature to verify, as a hex string
|
||||
- `signature`: the signature to verify, as a hex string
|
||||
- `account`: the account which signed the data (if available as an account)
|
||||
- `signer`: the public key of the account which signed the data (if not available as an account)
|
||||
|
||||
```sh
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1795b23ecf2" --signature="0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b" --account="Personal wallet/Operations"
|
||||
Verified
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1795b23ecf2" --signature="0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b" --account="Personal wallet/Auctions"
|
||||
Not verified
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1795b23ecf2" --signature="0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b" --signer="0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670"
|
||||
Verified
|
||||
```
|
||||
|
||||
The same rules apply to `ethereal signature verify` as those in `ethereal signature sign` above.
|
||||
|
||||
### `version`
|
||||
|
||||
`ethdo version` provides the current version of ethdo. For example:
|
||||
|
||||
```sh
|
||||
$ ethdo version
|
||||
1.0.0
|
||||
```
|
||||
There is a [HOWTO](https://github.com/wealdtech/ethdo/blob/master/docs/howto.md) that covers details about how to carry out various common tasks.
|
||||
|
||||
## Maintainers
|
||||
|
||||
Jim McDonald: [@mcdee](https://github.com/mcdee).
|
||||
|
||||
Special thanks to [@SuburbanDad](https://github.com/SuburbanDad) for updating xgo to allow for cross-compilation of `ethdo` releaes.
|
||||
|
||||
## Contribute
|
||||
|
||||
Contributions welcome. Please check out [the issues](https://github.com/wealdtech/ethdo/issues).
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](LICENSE) © 2019 Weald Technology Trading Ltd
|
||||
[Apache-2.0](LICENSE) © 2019, 2020 Weald Technology Trading Ltd
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,10 +14,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var accountCreateCmd = &cobra.Command{
|
||||
@@ -29,33 +34,83 @@ var accountCreateCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the account is created successfully, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(rootAccount != "", "--account is required")
|
||||
assert(rootAccountPassphrase != "", "--passphrase is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
w, err := walletFromPath(rootAccount)
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
|
||||
wallet, err := walletFromInput(ctx)
|
||||
errCheck(err, "Failed to access wallet")
|
||||
|
||||
if w.Type() == "hierarchical deterministic" {
|
||||
assert(rootWalletPassphrase != "", "--walletpassphrase is required to create new accounts with hierarchical deterministic wallets")
|
||||
outputIf(debug, fmt.Sprintf("Opened wallet %q of type %s", wallet.Name(), wallet.Type()))
|
||||
if wallet.Type() == "hierarchical deterministic" {
|
||||
assert(getWalletPassphrase() != "", "walletpassphrase is required to create new accounts with hierarchical deterministic wallets")
|
||||
}
|
||||
locker, isLocker := wallet.(e2wtypes.WalletLocker)
|
||||
if isLocker {
|
||||
errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet")
|
||||
}
|
||||
_, err = accountFromPath(rootAccount)
|
||||
assert(err != nil, "Account already exists")
|
||||
|
||||
err = w.Unlock([]byte(rootWalletPassphrase))
|
||||
errCheck(err, "Failed to unlock wallet")
|
||||
_, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account"))
|
||||
errCheck(err, "Failed to obtain account name")
|
||||
|
||||
_, accountName, err := walletAndAccountNamesFromPath(rootAccount)
|
||||
errCheck(err, "Failed to obtain accout name")
|
||||
|
||||
account, err := w.CreateAccount(accountName, []byte(rootAccountPassphrase))
|
||||
var account e2wtypes.Account
|
||||
if viper.GetUint("participants") > 0 {
|
||||
// Want a distributed account.
|
||||
distributedCreator, isDistributedCreator := wallet.(e2wtypes.WalletDistributedAccountCreator)
|
||||
assert(isDistributedCreator, "Wallet does not support distributed account creation")
|
||||
outputIf(debug, fmt.Sprintf("Distributed account has %d/%d threshold", viper.GetUint32("signing-threshold"), viper.GetUint32("participants")))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
account, err = distributedCreator.CreateDistributedAccount(ctx, accountName, viper.GetUint32("participants"), viper.GetUint32("signing-threshold"), []byte(getOptionalPassphrase()))
|
||||
} else {
|
||||
if viper.GetString("path") != "" {
|
||||
// Want a pathed account
|
||||
creator, isCreator := wallet.(e2wtypes.WalletPathedAccountCreator)
|
||||
assert(isCreator, "Wallet does not support account creation with an explicit path")
|
||||
var match bool
|
||||
match, err = regexp.Match("^m/[0-9]+/[0-9]+(/[0-9+])+", []byte(viper.GetString("path")))
|
||||
errCheck(err, "Unable to match path to regular expression")
|
||||
assert(match, "Path does not match expected format m/...")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
account, err = creator.CreatePathedAccount(ctx, viper.GetString("path"), accountName, []byte(getPassphrase()))
|
||||
} else {
|
||||
// Want a standard account.
|
||||
creator, isCreator := wallet.(e2wtypes.WalletAccountCreator)
|
||||
assert(isCreator, "Wallet does not support account creation")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
account, err = creator.CreateAccount(ctx, accountName, []byte(getPassphrase()))
|
||||
}
|
||||
}
|
||||
errCheck(err, "Failed to create account")
|
||||
|
||||
outputIf(verbose, fmt.Sprintf("0x%048x", account.PublicKey().Marshal()))
|
||||
os.Exit(_exit_success)
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
outputIf(verbose, fmt.Sprintf("%#x", pubKeyProvider.CompositePublicKey().Marshal()))
|
||||
} else if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
outputIf(verbose, fmt.Sprintf("%#x", pubKeyProvider.PublicKey().Marshal()))
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
accountCmd.AddCommand(accountCreateCmd)
|
||||
accountFlags(accountCreateCmd)
|
||||
accountCreateCmd.Flags().Uint32("participants", 0, "Number of participants (for distributed accounts)")
|
||||
accountCreateCmd.Flags().Uint32("signing-threshold", 0, "Signing threshold (for distributed accounts)")
|
||||
accountCreateCmd.Flags().String("path", "", "path of account (for hierarchical deterministic accounts)")
|
||||
}
|
||||
|
||||
func accountCreateBindings() {
|
||||
if err := viper.BindPFlag("participants", accountCreateCmd.Flags().Lookup("participants")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("signing-threshold", accountCreateCmd.Flags().Lookup("signing-threshold")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("path", accountCreateCmd.Flags().Lookup("path")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
types "github.com/wealdtech/go-eth2-wallet-types"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var accountImportKey string
|
||||
@@ -33,33 +36,43 @@ var accountImportCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the account is imported successfully, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(rootAccount != "", "--account is required")
|
||||
assert(rootAccountPassphrase != "", "--passphrase is required")
|
||||
assert(!remote, "account import not available with remote wallets")
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
passphrase := getPassphrase()
|
||||
assert(accountImportKey != "", "--key is required")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
key, err := bytesutil.FromHexString(accountImportKey)
|
||||
errCheck(err, "Invalid key")
|
||||
|
||||
w, err := walletFromPath(rootAccount)
|
||||
w, err := walletFromPath(ctx, viper.GetString("account"))
|
||||
errCheck(err, "Failed to access wallet")
|
||||
|
||||
_, ok := w.(types.WalletAccountImporter)
|
||||
_, ok := w.(e2wtypes.WalletAccountImporter)
|
||||
assert(ok, fmt.Sprintf("wallets of type %q do not allow importing accounts", w.Type()))
|
||||
|
||||
_, err = accountFromPath(rootAccount)
|
||||
_, _, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
assert(err != nil, "Account already exists")
|
||||
|
||||
err = w.Unlock([]byte(rootWalletPassphrase))
|
||||
errCheck(err, "Failed to unlock wallet")
|
||||
locker, isLocker := w.(e2wtypes.WalletLocker)
|
||||
if isLocker {
|
||||
errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet")
|
||||
}
|
||||
|
||||
_, accountName, err := walletAndAccountNamesFromPath(rootAccount)
|
||||
errCheck(err, "Failed to obtain accout name")
|
||||
_, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account"))
|
||||
errCheck(err, "Failed to obtain account name")
|
||||
|
||||
account, err := w.(types.WalletAccountImporter).ImportAccount(accountName, key, []byte(rootAccountPassphrase))
|
||||
account, err := w.(e2wtypes.WalletAccountImporter).ImportAccount(ctx, accountName, key, []byte(passphrase))
|
||||
errCheck(err, "Failed to create account")
|
||||
|
||||
outputIf(verbose, fmt.Sprintf("0x%048x", account.PublicKey().Marshal()))
|
||||
os.Exit(_exit_success)
|
||||
pubKey, err := bestPublicKey(account)
|
||||
if err == nil {
|
||||
outputIf(verbose, fmt.Sprintf("%#x", pubKey.Marshal()))
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,10 +14,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var accountInfoCmd = &cobra.Command{
|
||||
@@ -29,15 +34,51 @@ var accountInfoCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the account exists, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(rootAccount != "", "--account is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
account, err := accountFromPath(rootAccount)
|
||||
errCheck(err, "Failed to access wallet")
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
wallet, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
// Disallow wildcards (for now)
|
||||
assert(fmt.Sprintf("%s/%s", wallet.Name(), account.Name()) == viper.GetString("account"), "Mismatched account name")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
outputIf(verbose, fmt.Sprintf("UUID: %v", account.ID()))
|
||||
outputIf(!quiet, fmt.Sprintf("Public key: %#048x", account.PublicKey().Marshal()))
|
||||
outputIf(verbose && account.Path() != "", fmt.Sprintf("Path: %s", account.Path()))
|
||||
os.Exit(_exit_success)
|
||||
var withdrawalPubKey e2types.PublicKey
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
fmt.Printf("Public key: %#x\n", pubKeyProvider.PublicKey().Marshal())
|
||||
// May be overwritten later, but grab it for now.
|
||||
withdrawalPubKey = pubKeyProvider.PublicKey()
|
||||
}
|
||||
if distributedAccount, ok := account.(e2wtypes.DistributedAccount); ok {
|
||||
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
|
||||
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
|
||||
if verbose {
|
||||
fmt.Printf("Participants:\n")
|
||||
for k, v := range distributedAccount.Participants() {
|
||||
fmt.Printf(" %d: %s\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
withdrawalPubKey = distributedAccount.CompositePublicKey()
|
||||
}
|
||||
if verbose {
|
||||
withdrawalCredentials := util.SHA256(withdrawalPubKey.Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
fmt.Printf("Withdrawal credentials: %#x\n", withdrawalCredentials)
|
||||
}
|
||||
if pathProvider, ok := account.(e2wtypes.AccountPathProvider); ok {
|
||||
if pathProvider.Path() != "" {
|
||||
fmt.Printf("Path: %s\n", pathProvider.Path())
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
types "github.com/wealdtech/go-eth2-wallet-types"
|
||||
"github.com/spf13/viper"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// accountKeyCmd represents the account key command
|
||||
@@ -31,23 +33,38 @@ var accountKeyCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the key can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(rootAccount != "", "--account is required")
|
||||
account, err := accountFromPath(rootAccount)
|
||||
errCheck(err, "Failed to access account")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
_, ok := account.(types.AccountPrivateKeyProvider)
|
||||
assert(ok, fmt.Sprintf("account %q does not provide its private key", rootAccount))
|
||||
assert(!remote, "account keys not available with remote wallets")
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
|
||||
assert(rootAccountPassphrase != "", "--passphrase is required")
|
||||
err = account.Unlock([]byte(rootAccountPassphrase))
|
||||
errCheck(err, "Failed to unlock account to obtain private key")
|
||||
defer account.Lock()
|
||||
privateKey, err := account.(types.AccountPrivateKeyProvider).PrivateKey()
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
privateKeyProvider, isPrivateKeyProvider := account.(e2wtypes.AccountPrivateKeyProvider)
|
||||
assert(isPrivateKeyProvider, fmt.Sprintf("account %q does not provide its private key", viper.GetString("account")))
|
||||
|
||||
if locker, isLocker := account.(e2wtypes.AccountLocker); isLocker {
|
||||
unlocked, err := locker.IsUnlocked(ctx)
|
||||
errCheck(err, "Failed to find out if account is locked")
|
||||
if !unlocked {
|
||||
for _, passphrase := range getPassphrases() {
|
||||
err = locker.Unlock(ctx, []byte(passphrase))
|
||||
if err == nil {
|
||||
unlocked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(unlocked, "Failed to unlock account to obtain private key")
|
||||
defer relockAccount(locker)
|
||||
}
|
||||
privateKey, err := privateKeyProvider.PrivateKey(ctx)
|
||||
errCheck(err, "Failed to obtain private key")
|
||||
account.Lock()
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("%#064x", privateKey.Marshal()))
|
||||
os.Exit(_exit_success)
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", privateKey.Marshal()))
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
52
cmd/accountlock.go
Normal file
52
cmd/accountlock.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var accountLockCmd = &cobra.Command{
|
||||
Use: "lock",
|
||||
Short: "Lock a remote account",
|
||||
Long: `Lock a remote account. For example:
|
||||
|
||||
ethdo account lock --account="primary/my funds"
|
||||
|
||||
In quiet mode this will return 0 if the account is locked, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
locker, isLocker := account.(e2wtypes.AccountLocker)
|
||||
assert(isLocker, "Account does not support locking")
|
||||
|
||||
err = locker.Lock(ctx)
|
||||
errCheck(err, "Failed to lock account")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
accountCmd.AddCommand(accountLockCmd)
|
||||
accountFlags(accountLockCmd)
|
||||
}
|
||||
65
cmd/accountunlock.go
Normal file
65
cmd/accountunlock.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var accountUnlockCmd = &cobra.Command{
|
||||
Use: "unlock",
|
||||
Short: "Unlock a remote account",
|
||||
Long: `Unlock a remote account. For example:
|
||||
|
||||
ethdo account unlock --account="primary/my funds" --passphrase="secret"
|
||||
|
||||
In quiet mode this will return 0 if the account is unlocked, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
locker, isLocker := account.(e2wtypes.AccountLocker)
|
||||
assert(isLocker, "Account does not support unlocking")
|
||||
|
||||
unlocked := false
|
||||
for _, passphrase := range getPassphrases() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
err := locker.Unlock(ctx, []byte(passphrase))
|
||||
cancel()
|
||||
if err == nil {
|
||||
// Success.
|
||||
unlocked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert(unlocked, "Failed to unlock account")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
accountCmd.AddCommand(accountUnlockCmd)
|
||||
accountFlags(accountUnlockCmd)
|
||||
}
|
||||
32
cmd/attester.go
Normal file
32
cmd/attester.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// attesterCmd represents the attester command
|
||||
var attesterCmd = &cobra.Command{
|
||||
Use: "attester",
|
||||
Short: "Obtain information about Ethereum 2 attesters",
|
||||
Long: "Obtain information about Ethereum 2 attesters",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(attesterCmd)
|
||||
}
|
||||
|
||||
func attesterFlags(cmd *cobra.Command) {
|
||||
}
|
||||
156
cmd/attesterinclusion.go
Normal file
156
cmd/attesterinclusion.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var attesterInclusionCmd = &cobra.Command{
|
||||
Use: "inclusion",
|
||||
Short: "Obtain information about attester inclusion",
|
||||
Long: `Obtain information about attester inclusion. For example:
|
||||
|
||||
ethdo attester inclusion --account=Validators/00001 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if an attestation from the attester is found on the block fo the given epoch, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain block")
|
||||
|
||||
// Obtain the epoch.
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
outputIf(debug, "No epoch supplied; fetching current epoch")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
slotsPerEpoch := config["SlotsPerEpoch"].(uint64)
|
||||
secondsPerSlot := config["SecondsPerSlot"].(uint64)
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||
epoch = int64(time.Since(genesisTime).Seconds()) / int64(secondsPerSlot*slotsPerEpoch)
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Epoch is %d", epoch))
|
||||
|
||||
// Obtain the validator.
|
||||
account, err := attesterInclusionAccount()
|
||||
errCheck(err, "Failed to obtain account")
|
||||
validatorIndex, err := grpc.FetchValidatorIndex(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator")
|
||||
|
||||
// Find the attesting slot for the given epoch.
|
||||
committees, err := grpc.FetchValidatorCommittees(eth2GRPCConn, uint64(epoch))
|
||||
errCheck(err, "Failed to obtain validator committees")
|
||||
|
||||
slot := uint64(0)
|
||||
committeeIndex := uint64(0)
|
||||
validatorPositionInCommittee := uint64(0)
|
||||
found := false
|
||||
for searchSlot, committee := range committees {
|
||||
for searchCommitteeIndex, committeeValidatorIndices := range committee {
|
||||
for position, committeeValidatorIndex := range committeeValidatorIndices {
|
||||
if validatorIndex == committeeValidatorIndex {
|
||||
outputIf(verbose, fmt.Sprintf("Validator %d scheduled to attest at slot %d for epoch %d: entry %d in committee %d", validatorIndex, searchSlot, epoch, position, searchCommitteeIndex))
|
||||
slot = searchSlot
|
||||
committeeIndex = uint64(searchCommitteeIndex)
|
||||
validatorPositionInCommittee = uint64(position)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(found, "Failed to find attester duty for validator in the given epoch")
|
||||
|
||||
startSlot := slot + 1
|
||||
endSlot := startSlot + 32
|
||||
for curSlot := startSlot; curSlot < endSlot; curSlot++ {
|
||||
signedBlock, err := grpc.FetchBlock(eth2GRPCConn, curSlot)
|
||||
errCheck(err, "Failed to obtain block")
|
||||
if signedBlock == nil {
|
||||
outputIf(debug, fmt.Sprintf("No block at slot %d", curSlot))
|
||||
continue
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Fetched block %d", curSlot))
|
||||
for i, attestation := range signedBlock.Block.Body.Attestations {
|
||||
outputIf(debug, fmt.Sprintf("Attestation %d is for slot %d and committee %d", i, attestation.Data.Slot, attestation.Data.CommitteeIndex))
|
||||
if attestation.Data.Slot == slot &&
|
||||
attestation.Data.CommitteeIndex == committeeIndex &&
|
||||
attestation.AggregationBits.BitAt(validatorPositionInCommittee) {
|
||||
if verbose {
|
||||
fmt.Printf("Attestation included in block %d, attestation %d (inclusion delay %d)\n", curSlot, i, curSlot-slot)
|
||||
} else if !quiet {
|
||||
fmt.Printf("Attestation included in block %d (inclusion delay %d)\n", curSlot, curSlot-slot)
|
||||
}
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
outputIf(verbose, "Attestation not included on the chain")
|
||||
os.Exit(_exitFailure)
|
||||
},
|
||||
}
|
||||
|
||||
// attesterInclusionAccount obtains the account for the attester inclusion command.
|
||||
func attesterInclusionAccount() (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKey := viper.GetString("pubkey")
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", pubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
attesterCmd.AddCommand(attesterInclusionCmd)
|
||||
attesterFlags(attesterInclusionCmd)
|
||||
attesterInclusionCmd.Flags().Int64("epoch", -1, "the current epoch")
|
||||
attesterInclusionCmd.Flags().String("pubkey", "", "the public key of the attester")
|
||||
}
|
||||
|
||||
func attesterInclusionBindings() {
|
||||
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("pubkey", attesterInclusionCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
32
cmd/block.go
Normal file
32
cmd/block.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// blockCmd represents the block command
|
||||
var blockCmd = &cobra.Command{
|
||||
Use: "block",
|
||||
Short: "Obtain information about an Ethereum 2 block",
|
||||
Long: "Obtain information about an Ethereum 2 block",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(blockCmd)
|
||||
}
|
||||
|
||||
func blockFlags(cmd *cobra.Command) {
|
||||
}
|
||||
280
cmd/blockinfo.go
Normal file
280
cmd/blockinfo.go
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
var blockInfoSlot int64
|
||||
var blockInfoStream bool
|
||||
|
||||
var blockInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a block",
|
||||
Long: `Obtain information about a block. For example:
|
||||
|
||||
ethdo block info --slot=12345
|
||||
|
||||
In quiet mode this will return 0 if the block information is present and not skipped, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain block")
|
||||
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
slotsPerEpoch := config["SlotsPerEpoch"].(uint64)
|
||||
secondsPerSlot := config["SecondsPerSlot"].(uint64)
|
||||
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||
|
||||
assert(!blockInfoStream || blockInfoSlot == -1, "--slot and --stream are not supported together")
|
||||
|
||||
var slot uint64
|
||||
if blockInfoSlot < 0 {
|
||||
slot, err = grpc.FetchLatestFilledSlot(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain slot of latest block")
|
||||
} else {
|
||||
slot = uint64(blockInfoSlot)
|
||||
}
|
||||
signedBlock, err := grpc.FetchBlock(eth2GRPCConn, slot)
|
||||
errCheck(err, "Failed to obtain block")
|
||||
if signedBlock == nil {
|
||||
outputIf(!quiet, "No block at that slot")
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
outputBlock(signedBlock, genesisTime, secondsPerSlot, slotsPerEpoch)
|
||||
|
||||
if blockInfoStream {
|
||||
stream, err := grpc.StreamBlocks(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain block stream")
|
||||
for {
|
||||
fmt.Println()
|
||||
signedBlock, err := stream.Recv()
|
||||
errCheck(err, "Failed to obtain block")
|
||||
if signedBlock != nil {
|
||||
outputBlock(signedBlock, genesisTime, secondsPerSlot, slotsPerEpoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func outputBlock(signedBlock *ethpb.SignedBeaconBlock, genesisTime time.Time, secondsPerSlot uint64, slotsPerEpoch uint64) {
|
||||
block := signedBlock.Block
|
||||
body := block.Body
|
||||
|
||||
// General info.
|
||||
bodyRoot, err := ssz.HashTreeRoot(block)
|
||||
errCheck(err, "Failed to calculate block body root")
|
||||
fmt.Printf("Slot: %d\n", block.Slot)
|
||||
fmt.Printf("Epoch: %d\n", block.Slot/slotsPerEpoch)
|
||||
fmt.Printf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(block.Slot*secondsPerSlot), 0))
|
||||
fmt.Printf("Block root: %#x\n", bodyRoot)
|
||||
outputIf(verbose, fmt.Sprintf("Parent root: %#x", block.ParentRoot))
|
||||
outputIf(verbose, fmt.Sprintf("State root: %#x", block.StateRoot))
|
||||
if len(body.Graffiti) > 0 && hex.EncodeToString(body.Graffiti) != "0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
if utf8.Valid(body.Graffiti) {
|
||||
fmt.Printf("Graffiti: %s\n", string(body.Graffiti))
|
||||
} else {
|
||||
fmt.Printf("Graffiti: %#x\n", body.Graffiti)
|
||||
}
|
||||
}
|
||||
|
||||
// Eth1 data.
|
||||
eth1Data := body.Eth1Data
|
||||
outputIf(verbose, fmt.Sprintf("Ethereum 1 deposit count: %d", eth1Data.DepositCount))
|
||||
outputIf(verbose, fmt.Sprintf("Ethereum 1 deposit root: %#x", eth1Data.DepositRoot))
|
||||
outputIf(verbose, fmt.Sprintf("Ethereum 1 block hash: %#x", eth1Data.BlockHash))
|
||||
|
||||
validatorCommittees := make(map[uint64][][]uint64)
|
||||
|
||||
// Attestations.
|
||||
fmt.Printf("Attestations: %d\n", len(body.Attestations))
|
||||
if verbose {
|
||||
for i, att := range body.Attestations {
|
||||
fmt.Printf("\t%d:\n", i)
|
||||
|
||||
// Fetch committees for this epoch if not already obtained.
|
||||
committees, exists := validatorCommittees[att.Data.Slot]
|
||||
if !exists {
|
||||
attestationEpoch := att.Data.Slot / slotsPerEpoch
|
||||
epochCommittees, err := grpc.FetchValidatorCommittees(eth2GRPCConn, attestationEpoch)
|
||||
errCheck(err, "Failed to obtain committees")
|
||||
for k, v := range epochCommittees {
|
||||
validatorCommittees[k] = v
|
||||
}
|
||||
committees = validatorCommittees[att.Data.Slot]
|
||||
}
|
||||
|
||||
fmt.Printf("\t\tCommittee index: %d\n", att.Data.CommitteeIndex)
|
||||
fmt.Printf("\t\tAttesters: %d/%d\n", att.AggregationBits.Count(), att.AggregationBits.Len())
|
||||
fmt.Printf("\t\tAggregation bits: %s\n", bitsToString(att.AggregationBits))
|
||||
fmt.Printf("\t\tAttesting indices: %s\n", attestingIndices(att.AggregationBits, committees[att.Data.CommitteeIndex]))
|
||||
fmt.Printf("\t\tSlot: %d\n", att.Data.Slot)
|
||||
fmt.Printf("\t\tBeacon block root: %#x\n", att.Data.BeaconBlockRoot)
|
||||
fmt.Printf("\t\tSource epoch: %d\n", att.Data.Source.Epoch)
|
||||
fmt.Printf("\t\tSource root: %#x\n", att.Data.Source.Root)
|
||||
fmt.Printf("\t\tTarget epoch: %d\n", att.Data.Target.Epoch)
|
||||
fmt.Printf("\t\tTarget root: %#x\n", att.Data.Target.Root)
|
||||
}
|
||||
}
|
||||
|
||||
// Attester slashings.
|
||||
fmt.Printf("Attester slashings: %d\n", len(body.AttesterSlashings))
|
||||
if verbose {
|
||||
for i, slashing := range body.AttesterSlashings {
|
||||
// Say what was slashed.
|
||||
att1 := slashing.Attestation_1
|
||||
outputIf(debug, fmt.Sprintf("Attestation 1 attesting indices are %v", att1.AttestingIndices))
|
||||
att2 := slashing.Attestation_2
|
||||
outputIf(debug, fmt.Sprintf("Attestation 2 attesting indices are %v", att2.AttestingIndices))
|
||||
slashedIndices := intersection(att1.AttestingIndices, att2.AttestingIndices)
|
||||
if len(slashedIndices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("\t%d:\n", i)
|
||||
|
||||
fmt.Println("\t\tSlashed validators:")
|
||||
for _, slashedIndex := range slashedIndices {
|
||||
validator, err := grpc.FetchValidatorByIndex(eth2GRPCConn, slashedIndex)
|
||||
errCheck(err, "Failed to obtain validator information")
|
||||
fmt.Printf("\t\t\t%#x (%d)\n", validator.PublicKey, slashedIndex)
|
||||
}
|
||||
|
||||
// Say what caused the slashing.
|
||||
if att1.Data.Target.Epoch == att2.Data.Target.Epoch {
|
||||
fmt.Printf("\t\tDouble voted for same target epoch (%d):\n", att1.Data.Target.Epoch)
|
||||
if !bytes.Equal(att1.Data.Target.Root, att2.Data.Target.Root) {
|
||||
fmt.Printf("\t\t\tAttestation 1 target epoch root: %#x\n", att1.Data.Target.Root)
|
||||
fmt.Printf("\t\t\tAttestation 2target epoch root: %#x\n", att2.Data.Target.Root)
|
||||
}
|
||||
if !bytes.Equal(att1.Data.BeaconBlockRoot, att2.Data.BeaconBlockRoot) {
|
||||
fmt.Printf("\t\t\tAttestation 1 beacon block root: %#x\n", att1.Data.BeaconBlockRoot)
|
||||
fmt.Printf("\t\t\tAttestation 2 beacon block root: %#x\n", att2.Data.BeaconBlockRoot)
|
||||
}
|
||||
} else if att1.Data.Source.Epoch < att2.Data.Source.Epoch &&
|
||||
att1.Data.Target.Epoch > att2.Data.Target.Epoch {
|
||||
fmt.Printf("\t\tSurround voted:\n")
|
||||
fmt.Printf("\t\t\tAttestation 1 vote: %d->%d\n", att1.Data.Source.Epoch, att1.Data.Target.Epoch)
|
||||
fmt.Printf("\t\t\tAttestation 2 vote: %d->%d\n", att2.Data.Source.Epoch, att2.Data.Target.Epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Proposer slashings: %d\n", len(body.ProposerSlashings))
|
||||
// TODO verbose proposer slashings.
|
||||
|
||||
// Deposits.
|
||||
fmt.Printf("Deposits: %d\n", len(body.Deposits))
|
||||
if verbose {
|
||||
for i, deposit := range body.Deposits {
|
||||
data := deposit.Data
|
||||
fmt.Printf("\t%d:\n", i)
|
||||
fmt.Printf("\t\tPublic key: %#x\n", data.PublicKey)
|
||||
fmt.Printf("\t\tAmount: %s\n", string2eth.GWeiToString(data.Amount, true))
|
||||
fmt.Printf("\t\tWithdrawal credentials: %#x\n", data.WithdrawalCredentials)
|
||||
fmt.Printf("\t\tSignature: %#x\n", data.Signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Voluntary exits.
|
||||
fmt.Printf("Voluntary exits: %d\n", len(body.VoluntaryExits))
|
||||
if verbose {
|
||||
for i, voluntaryExit := range body.VoluntaryExits {
|
||||
fmt.Printf("\t%d:\n", i)
|
||||
validator, err := grpc.FetchValidatorByIndex(eth2GRPCConn, voluntaryExit.Exit.ValidatorIndex)
|
||||
errCheck(err, "Failed to obtain validator information")
|
||||
fmt.Printf("\t\tValidator: %#x (%d)\n", validator.PublicKey, voluntaryExit.Exit.ValidatorIndex)
|
||||
fmt.Printf("\t\tEpoch: %d\n", voluntaryExit.Exit.Epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intersection returns a list of items common between the two sets.
|
||||
func intersection(set1 []uint64, set2 []uint64) []uint64 {
|
||||
sort.Slice(set1, func(i, j int) bool { return set1[i] < set1[j] })
|
||||
sort.Slice(set2, func(i, j int) bool { return set2[i] < set2[j] })
|
||||
res := make([]uint64, 0)
|
||||
|
||||
set1Pos := 0
|
||||
set2Pos := 0
|
||||
for set1Pos < len(set1) && set2Pos < len(set2) {
|
||||
switch {
|
||||
case set1[set1Pos] < set2[set2Pos]:
|
||||
set1Pos++
|
||||
case set2[set2Pos] < set1[set1Pos]:
|
||||
set2Pos++
|
||||
default:
|
||||
res = append(res, set1[set1Pos])
|
||||
set1Pos++
|
||||
set2Pos++
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func bitsToString(input bitfield.Bitlist) string {
|
||||
bits := int(input.Len())
|
||||
|
||||
res := ""
|
||||
for i := 0; i < bits; i++ {
|
||||
if input.BitAt(uint64(i)) {
|
||||
res = fmt.Sprintf("%s✓", res)
|
||||
} else {
|
||||
res = fmt.Sprintf("%s✕", res)
|
||||
}
|
||||
if i%8 == 7 {
|
||||
res = fmt.Sprintf("%s ", res)
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(res)
|
||||
}
|
||||
|
||||
func attestingIndices(input bitfield.Bitlist, indices []uint64) string {
|
||||
bits := int(input.Len())
|
||||
res := ""
|
||||
for i := 0; i < bits; i++ {
|
||||
if input.BitAt(uint64(i)) {
|
||||
res = fmt.Sprintf("%s%d ", res, indices[i])
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(res)
|
||||
}
|
||||
|
||||
func init() {
|
||||
blockCmd.AddCommand(blockInfoCmd)
|
||||
blockFlags(blockInfoCmd)
|
||||
blockInfoCmd.Flags().Int64Var(&blockInfoSlot, "slot", -1, "the latest slot with a block")
|
||||
blockInfoCmd.Flags().BoolVar(&blockInfoStream, "stream", false, "continually stream blocks as they arrive")
|
||||
}
|
||||
32
cmd/chain.go
Normal file
32
cmd/chain.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// chainCmd represents the chain command
|
||||
var chainCmd = &cobra.Command{
|
||||
Use: "chain",
|
||||
Short: "Obtain information about an Ethereum 2 chain",
|
||||
Long: "Obtain information about an Ethereum 2 chain",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(chainCmd)
|
||||
}
|
||||
|
||||
func chainFlags(cmd *cobra.Command) {
|
||||
}
|
||||
82
cmd/chaininfo.go
Normal file
82
cmd/chaininfo.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
)
|
||||
|
||||
var chainInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a chain",
|
||||
Long: `Obtain information about a chain. For example:
|
||||
|
||||
ethdo chain info
|
||||
|
||||
In quiet mode this will return 0 if the chain information can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain genesis time")
|
||||
|
||||
genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain genesis validators root")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if genesisTime.Unix() == 0 {
|
||||
fmt.Println("Genesis time: undefined")
|
||||
} else {
|
||||
fmt.Printf("Genesis time: %s\n", genesisTime.Format(time.UnixDate))
|
||||
outputIf(verbose, fmt.Sprintf("Genesis timestamp: %v", genesisTime.Unix()))
|
||||
}
|
||||
fmt.Printf("Genesis validators root: %#x\n", genesisValidatorsRoot)
|
||||
fmt.Printf("Genesis fork version: %#x\n", config["GenesisForkVersion"].([]byte))
|
||||
fmt.Printf("Seconds per slot: %d\n", config["SecondsPerSlot"].(uint64))
|
||||
fmt.Printf("Slots per epoch: %d\n", config["SlotsPerEpoch"].(uint64))
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainInfoCmd)
|
||||
chainFlags(chainInfoCmd)
|
||||
}
|
||||
|
||||
func timestampToSlot(genesis int64, timestamp int64, secondsPerSlot uint64) uint64 {
|
||||
if timestamp < genesis {
|
||||
return 0
|
||||
}
|
||||
return uint64(timestamp-genesis) / secondsPerSlot
|
||||
}
|
||||
|
||||
func slotToTimestamp(genesis int64, slot uint64, secondsPerSlot uint64) int64 {
|
||||
return genesis + int64(slot*secondsPerSlot)
|
||||
}
|
||||
|
||||
func epochToTimestamp(genesis int64, slot uint64, secondsPerSlot uint64, slotsPerEpoch uint64) int64 {
|
||||
return genesis + int64(slot*secondsPerSlot*slotsPerEpoch)
|
||||
}
|
||||
112
cmd/chainstatus.go
Normal file
112
cmd/chainstatus.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
)
|
||||
|
||||
var chainStatusSlot bool
|
||||
|
||||
var chainStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Obtain status about a chain",
|
||||
Long: `Obtain status about a chain. For example:
|
||||
|
||||
ethdo chain status
|
||||
|
||||
In quiet mode this will return 0 if the chain status can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain genesis time")
|
||||
|
||||
info, err := grpc.FetchChainInfo(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain chain info")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
slot := timestampToSlot(genesisTime.Unix(), now.Unix(), config["SecondsPerSlot"].(uint64))
|
||||
if chainStatusSlot {
|
||||
fmt.Printf("Current slot: %d\n", slot)
|
||||
fmt.Printf("Justified slot: %d\n", info.GetJustifiedSlot())
|
||||
if verbose {
|
||||
distance := slot - info.GetJustifiedSlot()
|
||||
fmt.Printf("Justified slot distance: %d\n", distance)
|
||||
}
|
||||
fmt.Printf("Finalized slot: %d\n", info.GetFinalizedSlot())
|
||||
if verbose {
|
||||
distance := slot - info.GetFinalizedSlot()
|
||||
fmt.Printf("Finalized slot distance: %d\n", distance)
|
||||
}
|
||||
if verbose {
|
||||
fmt.Printf("Prior justified slot: %d\n", info.GetFinalizedSlot())
|
||||
distance := slot - info.GetPreviousJustifiedSlot()
|
||||
fmt.Printf("Prior justified slot distance: %d\n", distance)
|
||||
}
|
||||
} else {
|
||||
slotsPerEpoch := config["SlotsPerEpoch"].(uint64)
|
||||
epoch := slot / slotsPerEpoch
|
||||
fmt.Printf("Current epoch: %d\n", epoch)
|
||||
fmt.Printf("Justified epoch: %d\n", info.GetJustifiedEpoch())
|
||||
if verbose {
|
||||
distance := (slot - info.GetJustifiedSlot()) / slotsPerEpoch
|
||||
fmt.Printf("Justified epoch distance: %d\n", distance)
|
||||
}
|
||||
fmt.Printf("Finalized epoch: %d\n", info.GetFinalizedEpoch())
|
||||
if verbose {
|
||||
distance := (slot - info.GetFinalizedSlot()) / slotsPerEpoch
|
||||
fmt.Printf("Finalized epoch distance: %d\n", distance)
|
||||
}
|
||||
if verbose {
|
||||
fmt.Printf("Prior justified epoch: %d\n", info.GetPreviousJustifiedEpoch())
|
||||
distance := (slot - info.GetPreviousJustifiedSlot()) / slotsPerEpoch
|
||||
fmt.Printf("Prior justified epoch distance: %d\n", distance)
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
slotsPerEpoch := config["SlotsPerEpoch"].(uint64)
|
||||
secondsPerSlot := config["SecondsPerSlot"].(uint64)
|
||||
epochStartSlot := (slot / slotsPerEpoch) * slotsPerEpoch
|
||||
fmt.Printf("Epoch slots: %d-%d\n", epochStartSlot, epochStartSlot+slotsPerEpoch-1)
|
||||
nextSlot := slotToTimestamp(genesisTime.Unix(), slot+1, secondsPerSlot)
|
||||
fmt.Printf("Time until next slot: %2.1fs\n", float64(time.Until(time.Unix(nextSlot, 0)).Milliseconds())/1000)
|
||||
nextEpoch := epochToTimestamp(genesisTime.Unix(), slot/slotsPerEpoch+1, secondsPerSlot, slotsPerEpoch)
|
||||
fmt.Printf("Slots until next epoch: %d\n", (slot/slotsPerEpoch+1)*slotsPerEpoch-slot)
|
||||
fmt.Printf("Time until next epoch: %2.1fs\n", float64(time.Until(time.Unix(nextEpoch, 0)).Milliseconds())/1000)
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainStatusCmd)
|
||||
chainFlags(chainStatusCmd)
|
||||
chainStatusCmd.Flags().BoolVar(&chainStatusSlot, "slot", false, "Print slot-based values")
|
||||
|
||||
}
|
||||
@@ -14,6 +14,6 @@
|
||||
package cmd
|
||||
|
||||
const (
|
||||
_exit_success = 0
|
||||
_exit_failure = 1
|
||||
_exitSuccess = 0
|
||||
_exitFailure = 1
|
||||
)
|
||||
|
||||
32
cmd/deposit.go
Normal file
32
cmd/deposit.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// depositCmd represents the deposit command
|
||||
var depositCmd = &cobra.Command{
|
||||
Use: "deposit",
|
||||
Short: "Manage Ethereum 2 deposits",
|
||||
Long: `Manage Ethereum 2 deposits.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(depositCmd)
|
||||
}
|
||||
|
||||
func depositFlags(cmd *cobra.Command) {
|
||||
}
|
||||
227
cmd/depositverify.go
Normal file
227
cmd/depositverify.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
eth2util "github.com/wealdtech/go-eth2-util"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
var depositVerifyData string
|
||||
var depositVerifyWithdrawalPubKey string
|
||||
var depositVerifyValidatorPubKey string
|
||||
var depositVerifyDepositAmount string
|
||||
|
||||
var depositVerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify deposit data matches the provided data",
|
||||
Long: `Verify deposit data matches the provided input data. For example:
|
||||
|
||||
ethdo deposit verify --data=depositdata.json --withdrawalaccount=primary/current --value="32 Ether"
|
||||
|
||||
The deposit data is compared to the supplied withdrawal account/public key, validator public key, and value to ensure they match.
|
||||
|
||||
In quiet mode this will return 0 if the the data is verified correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(depositVerifyData != "", "--data is required")
|
||||
var data []byte
|
||||
var err error
|
||||
// Input could be JSON or a path to JSON.
|
||||
switch {
|
||||
case strings.HasPrefix(depositVerifyData, "0x"):
|
||||
// Looks like raw binary.
|
||||
data = []byte(depositVerifyData)
|
||||
case strings.HasPrefix(depositVerifyData, "{"):
|
||||
// Looks like JSON.
|
||||
data = []byte("[" + depositVerifyData + "]")
|
||||
case strings.HasPrefix(depositVerifyData, "["):
|
||||
// Looks like JSON array.
|
||||
data = []byte(depositVerifyData)
|
||||
default:
|
||||
// Assume it's a path to JSON.
|
||||
data, err = ioutil.ReadFile(depositVerifyData)
|
||||
errCheck(err, "Failed to read deposit data file")
|
||||
if data[0] == '{' {
|
||||
data = []byte("[" + string(data) + "]")
|
||||
}
|
||||
}
|
||||
|
||||
deposits, err := util.DepositInfoFromJSON(data)
|
||||
errCheck(err, "Failed to fetch deposit data")
|
||||
|
||||
var withdrawalCredentials []byte
|
||||
if depositVerifyWithdrawalPubKey != "" {
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(depositVerifyWithdrawalPubKey, "0x"))
|
||||
errCheck(err, "Invalid withdrawal public key")
|
||||
assert(len(withdrawalPubKeyBytes) == 48, "Public key should be 48 bytes")
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
errCheck(err, "Value supplied with --withdrawalpubkey is not a valid public key")
|
||||
withdrawalCredentials = eth2util.SHA256(withdrawalPubKey.Marshal())
|
||||
withdrawalCredentials[0] = 0 // BLS_WITHDRAWAL_PREFIX
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
|
||||
depositAmount := uint64(0)
|
||||
if depositVerifyDepositAmount != "" {
|
||||
depositAmount, err = string2eth.StringToGWei(depositVerifyDepositAmount)
|
||||
errCheck(err, "Invalid value")
|
||||
assert(depositAmount >= 1000000000, "deposit amount must be at least 1 Ether") // MIN_DEPOSIT_AMOUNT
|
||||
}
|
||||
|
||||
validatorPubKeys := make(map[[48]byte]bool)
|
||||
if depositVerifyValidatorPubKey != "" {
|
||||
validatorPubKeys, err = validatorPubKeysFromInput(depositVerifyValidatorPubKey)
|
||||
errCheck(err, "Failed to obtain validator public key(s))")
|
||||
}
|
||||
|
||||
failures := false
|
||||
for i, deposit := range deposits {
|
||||
if deposit.Amount == 0 {
|
||||
deposit.Amount = depositAmount
|
||||
}
|
||||
verified, err := verifyDeposit(deposit, withdrawalCredentials, validatorPubKeys, depositAmount)
|
||||
errCheck(err, fmt.Sprintf("Error attempting to verify deposit %d", i))
|
||||
if !verified {
|
||||
failures = true
|
||||
outputIf(!quiet, fmt.Sprintf("Deposit %q failed verification", deposit.Name))
|
||||
} else {
|
||||
outputIf(quiet, fmt.Sprintf("Deposit %q verified", deposit.Name))
|
||||
}
|
||||
}
|
||||
|
||||
if failures {
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) {
|
||||
pubKeys := make(map[[48]byte]bool)
|
||||
var err error
|
||||
var data []byte
|
||||
// Input could be a public key or a path to public keys.
|
||||
if strings.HasPrefix(input, "0x") {
|
||||
// Looks like a public key.
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "public key is not a hex string")
|
||||
}
|
||||
if len(pubKeyBytes) != 48 {
|
||||
return nil, errors.New("public key should be 48 bytes")
|
||||
}
|
||||
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid public key")
|
||||
}
|
||||
var key [48]byte
|
||||
copy(key[:], pubKey.Marshal())
|
||||
pubKeys[key] = true
|
||||
} else {
|
||||
// Assume it's a path to a file of public keys.
|
||||
data, err = ioutil.ReadFile(input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find public key file")
|
||||
}
|
||||
lines := bytes.Split(bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1), []byte("\n"))
|
||||
if len(lines) == 0 {
|
||||
return nil, errors.New("file has no public keys")
|
||||
}
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(line), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "public key is not a hex string")
|
||||
}
|
||||
if len(pubKeyBytes) != 48 {
|
||||
return nil, errors.New("public key should be 48 bytes")
|
||||
}
|
||||
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid public key")
|
||||
}
|
||||
var key [48]byte
|
||||
copy(key[:], pubKey.Marshal())
|
||||
pubKeys[key] = true
|
||||
}
|
||||
}
|
||||
|
||||
return pubKeys, nil
|
||||
}
|
||||
|
||||
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
|
||||
if withdrawalCredentials != nil {
|
||||
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
|
||||
return false, errors.New("withdrawal credentials incorrect")
|
||||
}
|
||||
outputIf(verbose, "Withdrawal credentials verified")
|
||||
}
|
||||
if amount != 0 {
|
||||
if deposit.Amount != amount {
|
||||
return false, errors.New("deposit value incorrect")
|
||||
}
|
||||
outputIf(verbose, "Amount verified")
|
||||
}
|
||||
|
||||
if len(validatorPubKeys) != 0 {
|
||||
var key [48]byte
|
||||
copy(key[:], deposit.PublicKey)
|
||||
if _, exists := validatorPubKeys[key]; !exists {
|
||||
return false, errors.New("validator public key incorrect")
|
||||
}
|
||||
outputIf(verbose, "Validator public key verified")
|
||||
}
|
||||
|
||||
depositData := ðpb.Deposit_Data{
|
||||
PublicKey: deposit.PublicKey,
|
||||
WithdrawalCredentials: deposit.WithdrawalCredentials,
|
||||
Amount: deposit.Amount,
|
||||
Signature: deposit.Signature,
|
||||
}
|
||||
depositDataRoot, err := depositData.HashTreeRoot()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to generate deposit data root")
|
||||
}
|
||||
if !bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
|
||||
return false, errors.New("deposit data root incorrect")
|
||||
}
|
||||
outputIf(debug, "Deposit data root verified")
|
||||
|
||||
outputIf(verbose, "Deposit verified")
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
depositCmd.AddCommand(depositVerifyCmd)
|
||||
depositFlags(depositVerifyCmd)
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyData, "data", "", "JSON data, or path to JSON data")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyWithdrawalPubKey, "withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyDepositAmount, "depositvalue", "", "Value of the amount to be deposited")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyValidatorPubKey, "validatorpubkey", "", "Public key(s) of the account(s) that will be carrying out validation")
|
||||
}
|
||||
@@ -57,10 +57,10 @@ func assert(condition bool, msg string) {
|
||||
|
||||
// die prints an error and quits
|
||||
func die(msg string) {
|
||||
if !quiet {
|
||||
if msg != "" && !quiet {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
}
|
||||
os.Exit(1)
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
|
||||
// warnCheck checks for an error and warns if it is present
|
||||
|
||||
32
cmd/exit.go
Normal file
32
cmd/exit.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// exitCmd represents the exit command
|
||||
var exitCmd = &cobra.Command{
|
||||
Use: "exit",
|
||||
Short: "Manage Ethereum 2 voluntary exits",
|
||||
Long: `Manage Ethereum 2 voluntary exits.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(exitCmd)
|
||||
}
|
||||
|
||||
func exitFlags(cmd *cobra.Command) {
|
||||
}
|
||||
138
cmd/exitverify.go
Normal file
138
cmd/exitverify.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var exitVerifyPubKey string
|
||||
|
||||
var exitVerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify exit data is valid",
|
||||
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
||||
|
||||
ethdo exit verify --data=exitdata.json --account=primary/current
|
||||
|
||||
In quiet mode this will return 0 if the the exit is verified correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("account") != "" || exitVerifyPubKey != "", "account or public key is required")
|
||||
account, err := exitVerifyAccount(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
assert(viper.GetString("exit.data") != "", "exit data is required")
|
||||
data, err := obtainExitData(viper.GetString("exit.Data"))
|
||||
errCheck(err, "Failed to obtain exit data")
|
||||
|
||||
// Confirm signature is good.
|
||||
err = connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node")
|
||||
genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn)
|
||||
outputIf(debug, fmt.Sprintf("Genesis validators root is %#x", genesisValidatorsRoot))
|
||||
errCheck(err, "Failed to obtain genesis validators root")
|
||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion, genesisValidatorsRoot)
|
||||
exit := ðpb.VoluntaryExit{
|
||||
Epoch: data.Epoch,
|
||||
ValidatorIndex: data.ValidatorIndex,
|
||||
}
|
||||
sig, err := e2types.BLSSignatureFromBytes(data.Signature)
|
||||
errCheck(err, "Invalid signature")
|
||||
verified, err := verifyStruct(account, exit, domain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
assert(verified, "Voluntary exit failed to verify")
|
||||
|
||||
// TODO confirm fork version is valid (once we have a way of obtaining the current fork version).
|
||||
|
||||
outputIf(verbose, "Verified")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// obtainExitData obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||
func obtainExitData(input string) (*validatorExitData, error) {
|
||||
var err error
|
||||
var data []byte
|
||||
// Input could be JSON or a path to JSON
|
||||
if strings.HasPrefix(input, "{") {
|
||||
// Looks like JSON
|
||||
data = []byte(input)
|
||||
} else {
|
||||
// Assume it's a path to JSON
|
||||
data, err = ioutil.ReadFile(input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||
}
|
||||
}
|
||||
exitData := &validatorExitData{}
|
||||
err = json.Unmarshal([]byte(data), exitData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "data is not valid JSON")
|
||||
}
|
||||
|
||||
return exitData, nil
|
||||
}
|
||||
|
||||
// exitVerifyAccount obtains the account for the exitVerify command.
|
||||
func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(exitVerifyPubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", exitVerifyPubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", exitVerifyPubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
exitCmd.AddCommand(exitVerifyCmd)
|
||||
exitFlags(exitVerifyCmd)
|
||||
exitVerifyCmd.Flags().String("data", "", "JSON data, or path to JSON data")
|
||||
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
||||
}
|
||||
|
||||
func exitVerifyBindings() {
|
||||
if err := viper.BindPFlag("data", exitVerifyCmd.Flags().Lookup("data")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
47
cmd/networks.go
Normal file
47
cmd/networks.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
)
|
||||
|
||||
// networks is a map of deposit contract addresses to networks.
|
||||
var networks = map[string]string{
|
||||
"16e82d77882a663454ef92806b7deca1d394810f": "Altona",
|
||||
"0f0f0fc0530007361933eab5db97d09acdd6c1c8": "Onyx",
|
||||
"07b39f4fde4a38bace212b546dac87c58dfe3fdc": "Medalla",
|
||||
}
|
||||
|
||||
// network returns the name of the network, if known.
|
||||
func network() string {
|
||||
if err := connect(); err != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
depositContractAddress, err := grpc.FetchDepositContractAddress(eth2GRPCConn)
|
||||
if err != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Deposit contract is %x", depositContractAddress))
|
||||
|
||||
depositContract := fmt.Sprintf("%x", depositContractAddress)
|
||||
if network, exists := networks[depositContract]; exists {
|
||||
return network
|
||||
} else {
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
32
cmd/node.go
Normal file
32
cmd/node.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// nodeCmd represents the node command
|
||||
var nodeCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Obtain information about an Ethereum 2 node",
|
||||
Long: "Obtain information about an Ethereum 2 node",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(nodeCmd)
|
||||
}
|
||||
|
||||
func nodeFlags(cmd *cobra.Command) {
|
||||
}
|
||||
74
cmd/nodeinfo.go
Normal file
74
cmd/nodeinfo.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
)
|
||||
|
||||
var nodeInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a node",
|
||||
Long: `Obtain information about a node. For example:
|
||||
|
||||
ethdo node info
|
||||
|
||||
In quiet mode this will return 0 if the node information can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain genesis time")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
version, metadata, err := grpc.FetchVersion(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain version")
|
||||
fmt.Printf("Version: %s\n", version)
|
||||
if metadata != "" {
|
||||
fmt.Printf("Metadata: %s\n", metadata)
|
||||
}
|
||||
}
|
||||
syncing, err := grpc.FetchSyncing(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain syncing state")
|
||||
fmt.Printf("Syncing: %v\n", syncing)
|
||||
|
||||
if genesisTime.Unix() == 0 {
|
||||
fmt.Println("Not reached genesis")
|
||||
} else {
|
||||
slot := timestampToSlot(genesisTime.Unix(), time.Now().Unix(), config["SecondsPerSlot"].(uint64))
|
||||
fmt.Printf("Current slot: %d\n", slot)
|
||||
fmt.Printf("Current epoch: %d\n", slot/config["SlotsPerEpoch"].(uint64))
|
||||
outputIf(verbose, fmt.Sprintf("Genesis timestamp: %v", genesisTime.Unix()))
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
nodeCmd.AddCommand(nodeInfoCmd)
|
||||
nodeFlags(nodeInfoCmd)
|
||||
}
|
||||
49
cmd/passphrases.go
Normal file
49
cmd/passphrases.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
// getStorePassphrases() fetches the store passphrase supplied by the user.
|
||||
func getStorePassphrase() string {
|
||||
return viper.GetString("store-passphrase")
|
||||
}
|
||||
|
||||
// getWalletPassphrases() fetches the wallet passphrase supplied by the user.
|
||||
func getWalletPassphrase() string {
|
||||
return viper.GetString("wallet-passphrase")
|
||||
}
|
||||
|
||||
// getPassphrases() fetches the passphrases supplied by the user.
|
||||
func getPassphrases() []string {
|
||||
return viper.GetStringSlice("passphrase")
|
||||
}
|
||||
|
||||
// getPassphrase fetches the passphrase supplied by the user.
|
||||
func getPassphrase() string {
|
||||
passphrases := getPassphrases()
|
||||
assert(len(passphrases) != 0, "passphrase is required")
|
||||
assert(len(passphrases) == 1, "multiple passphrases supplied; cannot continue")
|
||||
return passphrases[0]
|
||||
}
|
||||
|
||||
// getOptionalPassphrase fetches the passphrase if supplied by the user.
|
||||
func getOptionalPassphrase() string {
|
||||
passphrases := getPassphrases()
|
||||
if len(passphrases) == 0 {
|
||||
return ""
|
||||
}
|
||||
assert(len(passphrases) == 1, "multiple passphrases supplied; cannot continue")
|
||||
return passphrases[0]
|
||||
}
|
||||
319
cmd/root.go
319
cmd/root.go
@@ -15,22 +15,25 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
types "github.com/wealdtech/go-eth2-types"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
dirk "github.com/wealdtech/go-eth2-wallet-dirk"
|
||||
filesystem "github.com/wealdtech/go-eth2-wallet-store-filesystem"
|
||||
s3 "github.com/wealdtech/go-eth2-wallet-store-s3"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
@@ -38,17 +41,16 @@ var quiet bool
|
||||
var verbose bool
|
||||
var debug bool
|
||||
|
||||
// For transaction commands
|
||||
var wait bool
|
||||
var generate bool
|
||||
|
||||
// Root variables, present for all commands
|
||||
// Root variables, present for all commands.
|
||||
var rootStore string
|
||||
var rootAccount string
|
||||
var rootStorePassphrase string
|
||||
var rootWalletPassphrase string
|
||||
var rootAccountPassphrase string
|
||||
|
||||
// Store for wallet actions.
|
||||
var store e2wtypes.Store
|
||||
|
||||
// Remote connection.
|
||||
var remote bool
|
||||
|
||||
// Prysm connection.
|
||||
var eth2GRPCConn *grpc.ClientConn
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
@@ -70,43 +72,55 @@ func persistentPreRun(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// We bind viper here so that we bind to the correct command
|
||||
// We bind viper here so that we bind to the correct command.
|
||||
quiet = viper.GetBool("quiet")
|
||||
verbose = viper.GetBool("verbose")
|
||||
debug = viper.GetBool("debug")
|
||||
rootStore = viper.GetString("store")
|
||||
rootAccount = viper.GetString("account")
|
||||
rootStorePassphrase = viper.GetString("storepassphrase")
|
||||
rootWalletPassphrase = viper.GetString("walletpassphrase")
|
||||
rootAccountPassphrase = viper.GetString("passphrase")
|
||||
|
||||
// ...lots of commands have transaction-related flags (e.g.) 'wait'
|
||||
// as options but we want to bind them to this particular command and
|
||||
// this is the first chance we get
|
||||
if cmd.Flags().Lookup("wait") != nil {
|
||||
err := viper.BindPFlag("wait", cmd.Flags().Lookup("wait"))
|
||||
errCheck(err, "Failed to set wait option")
|
||||
// Command-specific bindings.
|
||||
switch fmt.Sprintf("%s/%s", cmd.Parent().Name(), cmd.Name()) {
|
||||
case "account/create":
|
||||
accountCreateBindings()
|
||||
case "attester/inclusion":
|
||||
attesterInclusionBindings()
|
||||
case "exit/verify":
|
||||
exitVerifyBindings()
|
||||
case "wallet/create":
|
||||
walletCreateBindings()
|
||||
}
|
||||
wait = viper.GetBool("wait")
|
||||
if cmd.Flags().Lookup("generate") != nil {
|
||||
err := viper.BindPFlag("generate", cmd.Flags().Lookup("generate"))
|
||||
errCheck(err, "Failed to set generate option")
|
||||
}
|
||||
generate = viper.GetBool("generate")
|
||||
|
||||
if quiet && verbose {
|
||||
die("Cannot supply both quiet and verbose flags")
|
||||
fmt.Println("Cannot supply both quiet and verbose flags")
|
||||
}
|
||||
if quiet && debug {
|
||||
die("Cannot supply both quiet and debug flags")
|
||||
}
|
||||
if generate && wait {
|
||||
die("Cannot supply both generate and wait flags")
|
||||
fmt.Println("Cannot supply both quiet and debug flags")
|
||||
}
|
||||
|
||||
// Set up our wallet store
|
||||
err := wallet.SetStore(rootStore, []byte(rootStorePassphrase))
|
||||
errCheck(err, "Failed to set up wallet store")
|
||||
if viper.GetString("remote") == "" {
|
||||
// Set up our wallet store
|
||||
switch rootStore {
|
||||
case "s3":
|
||||
assert(viper.GetString("base-dir") == "", "--basedir does not apply for the s3 store")
|
||||
var err error
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(getStorePassphrase())))
|
||||
errCheck(err, "Failed to access Amazon S3 wallet store")
|
||||
case "filesystem":
|
||||
opts := make([]filesystem.Option, 0)
|
||||
if getStorePassphrase() != "" {
|
||||
opts = append(opts, filesystem.WithPassphrase([]byte(getStorePassphrase())))
|
||||
}
|
||||
if viper.GetString("base-dir") != "" {
|
||||
opts = append(opts, filesystem.WithLocation(viper.GetString("base-dir")))
|
||||
}
|
||||
store = filesystem.New(opts...)
|
||||
default:
|
||||
die(fmt.Sprintf("Unsupported wallet store %s", rootStore))
|
||||
}
|
||||
err := e2wallet.UseStore(store)
|
||||
errCheck(err, "Failed to use defined wallet store")
|
||||
} else {
|
||||
remote = true
|
||||
}
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
@@ -114,11 +128,17 @@ func persistentPreRun(cmd *cobra.Command, args []string) {
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(_exit_failure)
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Initialise our BLS library.
|
||||
if err := e2types.InitBLS(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ethdo.yaml)")
|
||||
@@ -134,15 +154,19 @@ func init() {
|
||||
if err := viper.BindPFlag("account", RootCmd.PersistentFlags().Lookup("account")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("basedir", "", "Base directory for filesystem wallets")
|
||||
if err := viper.BindPFlag("base-dir", RootCmd.PersistentFlags().Lookup("basedir")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("storepassphrase", "", "Passphrase for store (if applicable)")
|
||||
if err := viper.BindPFlag("storepassphrase", RootCmd.PersistentFlags().Lookup("storepassphrase")); err != nil {
|
||||
if err := viper.BindPFlag("store-passphrase", RootCmd.PersistentFlags().Lookup("storepassphrase")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("walletpassphrase", "", "Passphrase for wallet (if applicable)")
|
||||
if err := viper.BindPFlag("walletpassphrase", RootCmd.PersistentFlags().Lookup("walletpassphrase")); err != nil {
|
||||
if err := viper.BindPFlag("wallet-passphrase", RootCmd.PersistentFlags().Lookup("walletpassphrase")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("passphrase", "", "Passphrase for account (if applicable)")
|
||||
RootCmd.PersistentFlags().StringSlice("passphrase", nil, "Passphrase for account (if applicable)")
|
||||
if err := viper.BindPFlag("passphrase", RootCmd.PersistentFlags().Lookup("passphrase")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -158,7 +182,7 @@ func init() {
|
||||
if err := viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("connection", "", "connection to Ethereum 2 node via GRPC")
|
||||
RootCmd.PersistentFlags().String("connection", "localhost:4000", "connection to Ethereum 2 node via GRPC")
|
||||
if err := viper.BindPFlag("connection", RootCmd.PersistentFlags().Lookup("connection")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -166,6 +190,22 @@ func init() {
|
||||
if err := viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("remote", "", "connection to a remote wallet daemon")
|
||||
if err := viper.BindPFlag("remote", RootCmd.PersistentFlags().Lookup("remote")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("client-cert", "", "location of a client certificate file when connecting to the remote wallet daemon")
|
||||
if err := viper.BindPFlag("client-cert", RootCmd.PersistentFlags().Lookup("client-cert")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("client-key", "", "location of a client key file when connecting to the remote wallet daemon")
|
||||
if err := viper.BindPFlag("client-key", RootCmd.PersistentFlags().Lookup("client-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("server-ca-cert", "", "location of the server certificate authority certificate when connecting to the remote wallet daemon")
|
||||
if err := viper.BindPFlag("server-ca-cert", RootCmd.PersistentFlags().Lookup("server-ca-cert")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
@@ -176,10 +216,7 @@ func initConfig() {
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(_exit_failure)
|
||||
}
|
||||
errCheck(err, "could not find home directory")
|
||||
|
||||
// Search config in home directory with name ".ethdo" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
@@ -187,15 +224,13 @@ func initConfig() {
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("ETHDO")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
// Don't report lack of config file...
|
||||
if !strings.Contains(err.Error(), "Not Found") {
|
||||
fmt.Println(err)
|
||||
os.Exit(_exit_failure)
|
||||
}
|
||||
assert(strings.Contains(err.Error(), "Not Found"), "failed to read configuration")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,83 +244,100 @@ func outputIf(condition bool, msg string) {
|
||||
}
|
||||
}
|
||||
|
||||
// walletAndAccountNamesFromPath breaks a path in to wallet and account names.
|
||||
func walletAndAccountNamesFromPath(path string) (string, string, error) {
|
||||
if len(path) == 0 {
|
||||
return "", "", errors.New("invalid account format")
|
||||
// walletFromInput obtains a wallet given the information in the viper variable
|
||||
// "account", or if not present the viper variable "wallet".
|
||||
func walletFromInput(ctx context.Context) (e2wtypes.Wallet, error) {
|
||||
if viper.GetString("account") != "" {
|
||||
return walletFromPath(ctx, viper.GetString("account"))
|
||||
}
|
||||
index := strings.Index(path, "/")
|
||||
if index == -1 {
|
||||
// Just the wallet
|
||||
return path, "", nil
|
||||
}
|
||||
if index == len(path)-1 {
|
||||
// Trailing /
|
||||
return path[:index], "", nil
|
||||
}
|
||||
return path[:index], path[index+1:], nil
|
||||
return walletFromPath(ctx, viper.GetString("wallet"))
|
||||
}
|
||||
|
||||
// walletFromPath obtains a wallet given a path specification.
|
||||
func walletFromPath(path string) (wtypes.Wallet, error) {
|
||||
walletName, _, err := walletAndAccountNamesFromPath(path)
|
||||
func walletFromPath(ctx context.Context, path string) (e2wtypes.Wallet, error) {
|
||||
walletName, _, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := wallet.OpenWallet(walletName)
|
||||
if viper.GetString("remote") != "" {
|
||||
assert(viper.GetString("client-cert") != "", "remote connections require client-cert")
|
||||
assert(viper.GetString("client-key") != "", "remote connections require client-key")
|
||||
credentials, err := dirk.ComposeCredentials(ctx, viper.GetString("client-cert"), viper.GetString("client-key"), viper.GetString("server-ca-cert"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build dirk credentials")
|
||||
}
|
||||
|
||||
endpoints, err := remotesToEndpoints([]string{viper.GetString("remote")})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse remote servers")
|
||||
}
|
||||
|
||||
return dirk.OpenWallet(ctx, walletName, credentials, endpoints)
|
||||
}
|
||||
wallet, err := e2wallet.OpenWallet(walletName)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "failed to decrypt wallet") {
|
||||
return nil, errors.New("Incorrect store passphrase")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return w, nil
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
// accountFromPath obtains an account given a path specification.
|
||||
func accountFromPath(path string) (wtypes.Account, error) {
|
||||
wallet, err := walletFromPath(path)
|
||||
// walletAndAccountFromInput obtains the wallet and account given the information in the viper variable "account".
|
||||
func walletAndAccountFromInput(ctx context.Context) (e2wtypes.Wallet, e2wtypes.Account, error) {
|
||||
return walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
}
|
||||
|
||||
// walletAndAccountFromPath obtains the wallet and account given a path specification.
|
||||
func walletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet, e2wtypes.Account, error) {
|
||||
wallet, err := walletFromPath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, errors.Wrap(err, "faild to open wallet for account")
|
||||
}
|
||||
_, accountName, err := walletAndAccountNamesFromPath(path)
|
||||
_, accountName, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain accout name")
|
||||
}
|
||||
if accountName == "" {
|
||||
return nil, errors.New("no account name")
|
||||
return nil, nil, errors.New("no account name")
|
||||
}
|
||||
|
||||
if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") && rootWalletPassphrase != "" {
|
||||
err = wallet.Unlock([]byte(rootWalletPassphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid wallet passphrase")
|
||||
if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") {
|
||||
assert(getWalletPassphrase() != "", "--walletpassphrase is required for direct path derivations")
|
||||
|
||||
locker, isLocker := wallet.(e2wtypes.WalletLocker)
|
||||
if isLocker {
|
||||
err = locker.Unlock(ctx, []byte(viper.GetString("wallet-passphrase")))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("failed to unlock wallet")
|
||||
}
|
||||
defer relockAccount(locker)
|
||||
}
|
||||
defer wallet.Lock()
|
||||
}
|
||||
return wallet.AccountByName(accountName)
|
||||
|
||||
accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider)
|
||||
if !isAccountByNameProvider {
|
||||
return nil, nil, errors.New("wallet cannot obtain accounts by name")
|
||||
}
|
||||
account, err := accountByNameProvider.AccountByName(ctx, accountName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
return wallet, account, nil
|
||||
}
|
||||
|
||||
// accountsFromPath obtains 0 or more accounts given a path specification.
|
||||
func accountsFromPath(path string) ([]wtypes.Account, error) {
|
||||
accounts := make([]wtypes.Account, 0)
|
||||
|
||||
// Quick check to see if it's a single account
|
||||
account, err := accountFromPath(path)
|
||||
if err == nil && account != nil {
|
||||
accounts = append(accounts, account)
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
wallet, err := walletFromPath(path)
|
||||
// walletAndAccountsFromPath obtains the wallet and matching accounts given a path specification.
|
||||
func walletAndAccountsFromPath(ctx context.Context, path string) (e2wtypes.Wallet, []e2wtypes.Account, error) {
|
||||
wallet, err := walletFromPath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, accountSpec, err := walletAndAccountNamesFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, errors.Wrap(err, "faild to open wallet for account")
|
||||
}
|
||||
|
||||
_, accountSpec, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain account specification")
|
||||
}
|
||||
if accountSpec == "" {
|
||||
accountSpec = "^.*$"
|
||||
} else {
|
||||
@@ -293,7 +345,8 @@ func accountsFromPath(path string) ([]wtypes.Account, error) {
|
||||
}
|
||||
re := regexp.MustCompile(accountSpec)
|
||||
|
||||
for account := range wallet.Accounts() {
|
||||
accounts := make([]e2wtypes.Account, 0)
|
||||
for account := range wallet.Accounts(ctx) {
|
||||
if re.Match([]byte(account.Name())) {
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
@@ -304,26 +357,16 @@ func accountsFromPath(path string) ([]wtypes.Account, error) {
|
||||
return accounts[i].Name() < accounts[j].Name()
|
||||
})
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// sign signs data in a domain.
|
||||
func sign(account wtypes.Account, data []byte, domain uint64) (types.Signature, error) {
|
||||
if !account.IsUnlocked() {
|
||||
return nil, errors.New("account must be unlocked to sign")
|
||||
}
|
||||
|
||||
return account.Sign(data, domain)
|
||||
}
|
||||
|
||||
// addTransactionFlags adds flags used in all transactions.
|
||||
func addTransactionFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("generate", false, "Do not send the transaction; generate and output as a hex string only")
|
||||
cmd.Flags().Bool("wait", false, "wait for the transaction to be mined before returning")
|
||||
return wallet, accounts, nil
|
||||
}
|
||||
|
||||
// connect connects to an Ethereum 2 endpoint.
|
||||
func connect() error {
|
||||
if eth2GRPCConn != nil {
|
||||
// Already connected.
|
||||
return nil
|
||||
}
|
||||
|
||||
connection := ""
|
||||
if viper.GetString("connection") != "" {
|
||||
connection = viper.GetString("connection")
|
||||
@@ -342,3 +385,43 @@ func connect() error {
|
||||
eth2GRPCConn, err = grpc.DialContext(ctx, connection, opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// bestPublicKey returns the best public key for operations.
|
||||
// It prefers the composite public key if present, otherwise the public key.
|
||||
func bestPublicKey(account e2wtypes.Account) (e2types.PublicKey, error) {
|
||||
var pubKey e2types.PublicKey
|
||||
publicKeyProvider, isCompositePublicKeyProvider := account.(e2wtypes.AccountCompositePublicKeyProvider)
|
||||
if isCompositePublicKeyProvider {
|
||||
pubKey = publicKeyProvider.CompositePublicKey()
|
||||
} else {
|
||||
publicKeyProvider, isPublicKeyProvider := account.(e2wtypes.AccountPublicKeyProvider)
|
||||
if isPublicKeyProvider {
|
||||
pubKey = publicKeyProvider.PublicKey()
|
||||
} else {
|
||||
return nil, errors.New("account does not provide a public key")
|
||||
}
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// remotesToEndpoints generates endpoints from remote addresses.
|
||||
func remotesToEndpoints(remotes []string) ([]*dirk.Endpoint, error) {
|
||||
endpoints := make([]*dirk.Endpoint, 0)
|
||||
for _, remote := range remotes {
|
||||
parts := strings.Split(remote, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid remote %q", remote)
|
||||
}
|
||||
port, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid port in remote %q", remote))
|
||||
}
|
||||
endpoints = append(endpoints, dirk.NewEndpoint(parts[0], uint32(port)))
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// relockAccount locks an account; generally called as a defer after an account is unlocked.
|
||||
func relockAccount(locker e2wtypes.AccountLocker) {
|
||||
errCheck(locker.Lock(context.Background()), "failed to re-lock account")
|
||||
}
|
||||
|
||||
@@ -15,11 +15,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var signatureData string
|
||||
var signatureDomain string
|
||||
|
||||
// signatureCmd represents the signature command
|
||||
var signatureCmd = &cobra.Command{
|
||||
Use: "signature",
|
||||
@@ -32,7 +31,23 @@ func init() {
|
||||
RootCmd.AddCommand(signatureCmd)
|
||||
}
|
||||
|
||||
var dataFlag *pflag.Flag
|
||||
var domainFlag *pflag.Flag
|
||||
|
||||
func signatureFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&signatureData, "data", "", "the hex string of data")
|
||||
cmd.Flags().StringVar(&signatureDomain, "domain", "", "the hex string of the BLS domain (defaults to 0x0000000000000000)")
|
||||
if dataFlag == nil {
|
||||
cmd.Flags().String("data", "", "the data, as a hex string")
|
||||
dataFlag = cmd.Flags().Lookup("data")
|
||||
if err := viper.BindPFlag("signature-data", dataFlag); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cmd.Flags().String("domain", "0x0000000000000000000000000000000000000000000000000000000000000000", "the BLS domain, as a hex string")
|
||||
domainFlag = cmd.Flags().Lookup("domain")
|
||||
if err := viper.BindPFlag("signature-domain", domainFlag); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
cmd.Flags().AddFlag(dataFlag)
|
||||
cmd.Flags().AddFlag(domainFlag)
|
||||
}
|
||||
}
|
||||
|
||||
110
cmd/signatureaggregate.go
Normal file
110
cmd/signatureaggregate.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright © 2017-2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/herumi/bls-eth-go-binary/bls"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
var signatureAggregateSignatures []string
|
||||
|
||||
// signatureAggregateCmd represents the signature aggregate command
|
||||
var signatureAggregateCmd = &cobra.Command{
|
||||
Use: "aggregate",
|
||||
Short: "Aggregate signatures",
|
||||
Long: `Aggregate signatures, either threshold or absolute. For example:
|
||||
|
||||
ethdo signature aggregate --signatures=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7,
|
||||
|
||||
Signatures are specified as "signature" for simple aggregation, and as "id:signature" for threshold aggregation.
|
||||
|
||||
In quiet mode this will return 0 if the signatures can be aggregated, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(len(signatureAggregateSignatures) > 1, "multiple signatures required to aggregate")
|
||||
var signature *bls.Sign
|
||||
var err error
|
||||
if strings.Contains(signatureAggregateSignatures[0], ":") {
|
||||
signature, err = generateThresholdSignature()
|
||||
} else {
|
||||
signature, err = generateAggregateSignature()
|
||||
}
|
||||
errCheck(err, "Failed to aggregate signature")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Serialize()))
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func generateThresholdSignature() (*bls.Sign, error) {
|
||||
ids := make([]bls.ID, len(signatureAggregateSignatures))
|
||||
sigs := make([]bls.Sign, len(signatureAggregateSignatures))
|
||||
|
||||
for i := range signatureAggregateSignatures {
|
||||
parts := strings.Split(signatureAggregateSignatures[i], ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("invalid threshold signature format")
|
||||
}
|
||||
id, err := strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid threshold signature ID")
|
||||
}
|
||||
ids[i] = *util.BLSID(id)
|
||||
sigBytes, err := hex.DecodeString(strings.TrimPrefix(parts[1], "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid threshold signature ID")
|
||||
}
|
||||
if err := sigs[i].Deserialize(sigBytes); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid signature")
|
||||
}
|
||||
}
|
||||
|
||||
var compositeSig bls.Sign
|
||||
if err := compositeSig.Recover(sigs, ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &compositeSig, nil
|
||||
}
|
||||
|
||||
func generateAggregateSignature() (*bls.Sign, error) {
|
||||
sigs := make([]bls.Sign, len(signatureAggregateSignatures))
|
||||
for i := range signatureAggregateSignatures {
|
||||
sigBytes, err := hex.DecodeString(strings.TrimPrefix(signatureAggregateSignatures[i], "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode signature")
|
||||
}
|
||||
if err := sigs[i].Deserialize(sigBytes); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid signature")
|
||||
}
|
||||
}
|
||||
var aggregateSig bls.Sign
|
||||
aggregateSig.Aggregate(sigs)
|
||||
|
||||
return &aggregateSig, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
signatureCmd.AddCommand(signatureAggregateCmd)
|
||||
signatureAggregateCmd.Flags().StringArrayVar(&signatureAggregateSignatures, "signature", nil, "a signature to aggregate (supply once for each signature)")
|
||||
signatureFlags(signatureAggregateCmd)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2017-2019 Weald Technology Trading
|
||||
// Copyright © 2017-2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,48 +14,53 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
types "github.com/wealdtech/go-eth2-types"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
)
|
||||
|
||||
// signatureSignCmd represents the signature sign command
|
||||
var signatureSignCmd = &cobra.Command{
|
||||
Use: "sign",
|
||||
Short: "Sign data",
|
||||
Short: "Sign a 32-byte piece of data",
|
||||
Long: `Sign presented data. For example:
|
||||
|
||||
ethereal signature sign --data="0x5FfC014343cd971B7eb70732021E26C35B744cc4" --account="Personal wallet/Operations" --passphrase="my account passphrase"
|
||||
ethdo signature sign --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --account="Personal wallet/Operations" --passphrase="my account passphrase"
|
||||
|
||||
In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(signatureData != "", "--data is required")
|
||||
data, err := bytesutil.FromHexString(signatureData)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("signature-data") != "", "--data is required")
|
||||
data, err := bytesutil.FromHexString(viper.GetString("signature-data"))
|
||||
errCheck(err, "Failed to parse data")
|
||||
assert(len(data) == 32, "data to sign must be 32 bytes")
|
||||
|
||||
domain := types.Domain([]byte{0, 0, 0, 0}, []byte{0, 0, 0, 0})
|
||||
if signatureDomain != "" {
|
||||
domainBytes, err := bytesutil.FromHexString(signatureDomain)
|
||||
domain := e2types.Domain(e2types.DomainType([4]byte{0, 0, 0, 0}), e2types.ZeroForkVersion, e2types.ZeroGenesisValidatorsRoot)
|
||||
if viper.GetString("signature-domain") != "" {
|
||||
domain, err = bytesutil.FromHexString(viper.GetString("signature-domain"))
|
||||
errCheck(err, "Failed to parse domain")
|
||||
assert(len(domainBytes) == 8, "Domain data invalid")
|
||||
assert(len(domain) == 32, "Domain data invalid")
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Domain is %#x", domain))
|
||||
|
||||
assert(rootAccount != "", "--account is required")
|
||||
account, err := accountFromPath(rootAccount)
|
||||
errCheck(err, "Failed to access account for signing")
|
||||
assert(viper.GetString("account") != "", "--account is required")
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
err = account.Unlock([]byte(rootAccountPassphrase))
|
||||
errCheck(err, "Failed to unlock account for signing")
|
||||
defer account.Lock()
|
||||
var fixedSizeData [32]byte
|
||||
copy(fixedSizeData[:], data)
|
||||
signature, err := signRoot(account, fixedSizeData, domain)
|
||||
errCheck(err, "Failed to sign")
|
||||
|
||||
signature, err := account.Sign(data, domain)
|
||||
errCheck(err, "Failed to sign data")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("0x%096x", signature.Marshal()))
|
||||
os.Exit(_exit_success)
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Marshal()))
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2017-2019 Weald Technology Trading
|
||||
// Copyright © 2017-2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,15 +14,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
types "github.com/wealdtech/go-eth2-types"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var signatureVerifySignature string
|
||||
var signatureVerifyPubKey string
|
||||
var signatureVerifySigner string
|
||||
|
||||
// signatureVerifyCmd represents the signature verify command
|
||||
var signatureVerifyCmd = &cobra.Command{
|
||||
@@ -30,52 +38,69 @@ var signatureVerifyCmd = &cobra.Command{
|
||||
Short: "Verify signed data",
|
||||
Long: `Verify signed data. For example:
|
||||
|
||||
ethereal signature verify --data="0x5FfC014343cd971B7eb70732021E26C35B744cc4" --signature="0x8888..." --account="Personal wallet/Operations"
|
||||
ethdo signature verify --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --signature=0x8888... --account="Personal wallet/Operations"
|
||||
|
||||
In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(signatureData != "", "--data is required")
|
||||
data, err := bytesutil.FromHexString(signatureData)
|
||||
assert(viper.GetString("signature-data") != "", "--data is required")
|
||||
data, err := bytesutil.FromHexString(viper.GetString("signature-data"))
|
||||
errCheck(err, "Failed to parse data")
|
||||
assert(len(data) == 32, "data to verify must be 32 bytes")
|
||||
|
||||
assert(signatureVerifySignature != "", "--signature is required")
|
||||
signatureBytes, err := bytesutil.FromHexString(signatureVerifySignature)
|
||||
errCheck(err, "Failed to parse signature")
|
||||
signature, err := types.BLSSignatureFromBytes(signatureBytes)
|
||||
signature, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
||||
errCheck(err, "Invalid signature")
|
||||
|
||||
domain := types.Domain([]byte{0, 0, 0, 0}, []byte{0, 0, 0, 0})
|
||||
if signatureDomain != "" {
|
||||
domainBytes, err := bytesutil.FromHexString(signatureDomain)
|
||||
domain := e2types.Domain(e2types.DomainType([4]byte{0, 0, 0, 0}), e2types.ZeroForkVersion, e2types.ZeroGenesisValidatorsRoot)
|
||||
if viper.GetString("signature-domain") != "" {
|
||||
domain, err = bytesutil.FromHexString(viper.GetString("signature-domain"))
|
||||
errCheck(err, "Failed to parse domain")
|
||||
assert(len(domainBytes) == 8, "Domain data invalid")
|
||||
assert(len(domain) == 32, "Domain data invalid")
|
||||
}
|
||||
|
||||
var pubKey types.PublicKey
|
||||
assert(signatureVerifyPubKey == "" || rootAccount == "", "Either --pubkey or --account should be supplied")
|
||||
if rootAccount != "" {
|
||||
account, err := accountFromPath(rootAccount)
|
||||
errCheck(err, "Unknown account")
|
||||
pubKey = account.PublicKey()
|
||||
} else {
|
||||
pubKeyBytes, err := bytesutil.FromHexString(signatureVerifyPubKey)
|
||||
errCheck(err, "Invalid public key")
|
||||
pubKey, err = types.BLSPublicKeyFromBytes(pubKeyBytes)
|
||||
errCheck(err, "Invalid public key")
|
||||
}
|
||||
verified := signature.Verify(data, pubKey, domain)
|
||||
if !verified {
|
||||
outputIf(!quiet, "Not verified")
|
||||
os.Exit(_exit_failure)
|
||||
}
|
||||
outputIf(!quiet, "Verified")
|
||||
os.Exit(_exit_success)
|
||||
account, err := signatureVerifyAccount()
|
||||
errCheck(err, "Failed to obtain account")
|
||||
outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
|
||||
|
||||
var root [32]byte
|
||||
copy(root[:], data)
|
||||
verified, err := verifyRoot(account, root, domain, signature)
|
||||
errCheck(err, "Failed to verify data")
|
||||
assert(verified, "Failed to verify")
|
||||
|
||||
outputIf(verbose, "Verified")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// signatureVerifyAccount obtains the account for the signature verify command.
|
||||
func signatureVerifyAccount() (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(signatureVerifySigner, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", signatureVerifySigner))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", signatureVerifySigner))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
func init() {
|
||||
signatureCmd.AddCommand(signatureVerifyCmd)
|
||||
signatureFlags(signatureVerifyCmd)
|
||||
signatureVerifyCmd.Flags().StringVar(&signatureVerifySignature, "signature", "", "the signature to verify")
|
||||
signatureVerifyCmd.Flags().StringVar(&signatureVerifyPubKey, "signer", "", "the public key of the signer (only if --account is not supplied)")
|
||||
signatureVerifyCmd.Flags().StringVar(&signatureVerifySigner, "signer", "", "the public key of the signer (only if --account is not supplied)")
|
||||
}
|
||||
|
||||
195
cmd/signing.go
Normal file
195
cmd/signing.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/viper"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// signStruct signs an arbitrary structure.
|
||||
func signStruct(account wtypes.Account, data interface{}, domain []byte) (e2types.Signature, error) {
|
||||
objRoot, err := ssz.HashTreeRoot(data)
|
||||
outputIf(debug, fmt.Sprintf("Object root is %#x", objRoot))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signRoot(account, objRoot, domain)
|
||||
}
|
||||
|
||||
// verifyStruct verifies the signature of an arbitrary structure.
|
||||
func verifyStruct(account wtypes.Account, data interface{}, domain []byte, signature e2types.Signature) (bool, error) {
|
||||
objRoot, err := ssz.HashTreeRoot(data)
|
||||
outputIf(debug, fmt.Sprintf("Object root is %#x", objRoot))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return verifyRoot(account, objRoot, domain, signature)
|
||||
}
|
||||
|
||||
// SigningContainer is the container for signing roots with a domain.
|
||||
// Contains SSZ sizes to allow for correct calculation of root.
|
||||
type signingContainer struct {
|
||||
Root []byte `ssz-size:"32"`
|
||||
Domain []byte `ssz-size:"32"`
|
||||
}
|
||||
|
||||
// signRoot signs a root.
|
||||
func signRoot(account wtypes.Account, root [32]byte, domain []byte) (e2types.Signature, error) {
|
||||
if _, isProtectingSigner := account.(e2wtypes.AccountProtectingSigner); isProtectingSigner {
|
||||
// Signer signs the data to sign itself.
|
||||
return signGeneric(account, root[:], domain)
|
||||
}
|
||||
|
||||
// Build the signing data manually.
|
||||
container := &signingContainer{
|
||||
Root: root[:],
|
||||
Domain: domain,
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.Root, container.Domain))
|
||||
signingRoot, err := ssz.HashTreeRoot(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
return sign(account, signingRoot[:])
|
||||
}
|
||||
|
||||
func verifyRoot(account wtypes.Account, root [32]byte, domain []byte, signature e2types.Signature) (bool, error) {
|
||||
// Build the signing data manually.
|
||||
container := &signingContainer{
|
||||
Root: root[:],
|
||||
Domain: domain,
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.Root, container.Domain))
|
||||
signingRoot, err := ssz.HashTreeRoot(container)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
return verify(account, signingRoot[:], signature)
|
||||
}
|
||||
|
||||
func signGeneric(account wtypes.Account, data []byte, domain []byte) (e2types.Signature, error) {
|
||||
alreadyUnlocked, err := unlock(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
signer, isProtectingSigner := account.(e2wtypes.AccountProtectingSigner)
|
||||
if !isProtectingSigner {
|
||||
return nil, errors.New("account does not provide generic signing")
|
||||
}
|
||||
|
||||
signature, err := signer.SignGeneric(ctx, data, domain)
|
||||
errCheck(err, "failed to sign")
|
||||
if !alreadyUnlocked {
|
||||
if err := lock(account); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lock account")
|
||||
}
|
||||
}
|
||||
return signature, err
|
||||
}
|
||||
|
||||
// sign signs arbitrary data, handling unlocking and locking as required.
|
||||
func sign(account wtypes.Account, data []byte) (e2types.Signature, error) {
|
||||
alreadyUnlocked, err := unlock(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
signer, isSigner := account.(e2wtypes.AccountSigner)
|
||||
if !isSigner {
|
||||
return nil, errors.New("account does not provide signing")
|
||||
}
|
||||
|
||||
signature, err := signer.Sign(ctx, data)
|
||||
errCheck(err, "failed to sign")
|
||||
if !alreadyUnlocked {
|
||||
if err := lock(account); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lock account")
|
||||
}
|
||||
}
|
||||
return signature, err
|
||||
}
|
||||
|
||||
// verify the signature of arbitrary data.
|
||||
func verify(account wtypes.Account, data []byte, signature e2types.Signature) (bool, error) {
|
||||
pubKey, err := bestPublicKey(account)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain account public key")
|
||||
}
|
||||
return signature.Verify(data, pubKey), nil
|
||||
}
|
||||
|
||||
// unlock attempts to unlock an account. It returns true if the account was already unlocked.
|
||||
func unlock(account e2wtypes.Account) (bool, error) {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
if !isAccountLocker {
|
||||
outputIf(debug, "Account does not support unlocking")
|
||||
// This account doesn't support unlocking; return okay.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
alreadyUnlocked, err := locker.IsUnlocked(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "unable to ascertain if account is unlocked")
|
||||
}
|
||||
|
||||
if alreadyUnlocked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Not already unlocked; attempt to unlock it.
|
||||
for _, passphrase := range getPassphrases() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
err = locker.Unlock(ctx, []byte(passphrase))
|
||||
cancel()
|
||||
if err == nil {
|
||||
// Unlocked.
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to unlock it.
|
||||
return false, errors.New("failed to unlock account")
|
||||
}
|
||||
|
||||
// lock attempts to lock an account.
|
||||
func lock(account e2wtypes.Account) error {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
if !isAccountLocker {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
return locker.Lock(ctx)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,21 +14,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/cobra"
|
||||
types "github.com/wealdtech/go-eth2-types"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
var validatorDepositDataValidatorAccount string
|
||||
var validatorDepositDataWithdrawalAccount string
|
||||
var validatorDepositDataWithdrawalPubKey string
|
||||
var validatorDepositDataDepositValue string
|
||||
var validatorDepositDataRaw bool
|
||||
var validatorDepositDataForkVersion string
|
||||
var validatorDepositDataLaunchpad bool
|
||||
|
||||
var validatorDepositDataCmd = &cobra.Command{
|
||||
Use: "depositdata",
|
||||
@@ -43,57 +50,89 @@ The information generated can be passed to ethereal to create a deposit from the
|
||||
|
||||
In quiet mode this will return 0 if the the data can be generated correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(validatorDepositDataValidatorAccount != "", "--validatoraccount is required")
|
||||
validatorWallet, err := walletFromPath(validatorDepositDataValidatorAccount)
|
||||
errCheck(err, "Failed to obtain validator wallet")
|
||||
validatorAccounts, err := accountsFromPath(validatorDepositDataValidatorAccount)
|
||||
errCheck(err, "Failed to obtain validator account")
|
||||
validatorWallet, validatorAccounts, err := walletAndAccountsFromPath(ctx, validatorDepositDataValidatorAccount)
|
||||
errCheck(err, "Failed to obtain validator accounts")
|
||||
assert(len(validatorAccounts) > 0, "Failed to obtain validator account")
|
||||
if len(validatorAccounts) == 1 {
|
||||
outputIf(debug, fmt.Sprintf("Validator public key is %048x", validatorAccounts[0].PublicKey().Marshal()))
|
||||
} else {
|
||||
for _, validatorAccount := range validatorAccounts {
|
||||
outputIf(verbose, fmt.Sprintf("Creating deposit for %s/%s", validatorWallet.Name(), validatorAccount.Name()))
|
||||
outputIf(debug, fmt.Sprintf("Validator public key is %048x", validatorAccount.PublicKey().Marshal()))
|
||||
}
|
||||
|
||||
for _, validatorAccount := range validatorAccounts {
|
||||
outputIf(verbose, fmt.Sprintf("Creating deposit for %s/%s", validatorWallet.Name(), validatorAccount.Name()))
|
||||
pubKey, err := bestPublicKey(validatorAccount)
|
||||
errCheck(err, "Validator account does not provide a public key")
|
||||
outputIf(debug, fmt.Sprintf("Validator public key is %#x", pubKey.Marshal()))
|
||||
}
|
||||
|
||||
assert(validatorDepositDataWithdrawalAccount != "", "--withdrawalaccount is required")
|
||||
withdrawalAccount, err := accountFromPath(validatorDepositDataWithdrawalAccount)
|
||||
errCheck(err, "Failed to obtain withdrawal account")
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal public key is %048x", withdrawalAccount.PublicKey().Marshal()))
|
||||
|
||||
withdrawalCredentials := util.SHA256(withdrawalAccount.PublicKey().Marshal())
|
||||
errCheck(err, "Failed to hash withdrawal credentials")
|
||||
withdrawalCredentials[0] = byte(0) // BLSWithdrawalPrefix
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %032x", withdrawalCredentials))
|
||||
assert(validatorDepositDataWithdrawalAccount != "" || validatorDepositDataWithdrawalPubKey != "", "--withdrawalaccount or --withdrawalpubkey is required")
|
||||
var withdrawalCredentials []byte
|
||||
if validatorDepositDataWithdrawalAccount != "" {
|
||||
_, withdrawalAccount, err := walletAndAccountFromPath(ctx, validatorDepositDataWithdrawalAccount)
|
||||
errCheck(err, "Failed to obtain withdrawal account")
|
||||
pubKey, err := bestPublicKey(withdrawalAccount)
|
||||
errCheck(err, "Withdrawal account does not provide a public key")
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal public key is %#x", pubKey.Marshal()))
|
||||
withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
errCheck(err, "Failed to hash withdrawal credentials")
|
||||
} else {
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(validatorDepositDataWithdrawalPubKey, "0x"))
|
||||
errCheck(err, "Invalid withdrawal public key")
|
||||
assert(len(withdrawalPubKeyBytes) == 48, "Public key should be 48 bytes")
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
errCheck(err, "Value supplied with --withdrawalpubkey is not a valid public key")
|
||||
withdrawalCredentials = util.SHA256(withdrawalPubKey.Marshal())
|
||||
errCheck(err, "Failed to hash withdrawal credentials")
|
||||
}
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
|
||||
assert(validatorDepositDataDepositValue != "", "--depositvalue is required")
|
||||
val, err := string2eth.StringToGWei(validatorDepositDataDepositValue)
|
||||
errCheck(err, "Invalid value")
|
||||
assert(val >= 1000000000, "deposit value must be at least 1 Ether")
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
assert(val >= 1000000000, "deposit value must be at least 1 Ether") // MIN_DEPOSIT_AMOUNT
|
||||
|
||||
// For each key, generate deposit data
|
||||
outputs := make([]string, 0)
|
||||
for _, validatorAccount := range validatorAccounts {
|
||||
validatorPubKey, err := bestPublicKey(validatorAccount)
|
||||
errCheck(err, "Validator account does not provide a public key")
|
||||
depositData := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
}{
|
||||
PubKey: validatorAccount.PublicKey().Marshal(),
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
}
|
||||
signingRoot, err := ssz.HashTreeRoot(depositData)
|
||||
errCheck(err, "Failed to generate deposit data signing root")
|
||||
outputIf(debug, fmt.Sprintf("Signing root is %x", signingRoot))
|
||||
domain := types.Domain(types.DomainDeposit, []byte{0, 0, 0, 0})
|
||||
err = validatorAccount.Unlock([]byte(rootAccountPassphrase))
|
||||
errCheck(err, "Failed to unlock validator account")
|
||||
signature, err := validatorAccount.Sign(signingRoot[:], domain)
|
||||
validatorAccount.Lock()
|
||||
errCheck(err, "Failed to sign deposit data signing root")
|
||||
outputIf(debug, fmt.Sprintf("Deposit data:\n\tPublic key: %x\n\tWithdrawal credentials: %x\n\tValue: %d", depositData.PubKey, depositData.WithdrawalCredentials, depositData.Value))
|
||||
|
||||
var forkVersion []byte
|
||||
if validatorDepositDataForkVersion != "" {
|
||||
forkVersion, err = hex.DecodeString(strings.TrimPrefix(validatorDepositDataForkVersion, "0x"))
|
||||
errCheck(err, fmt.Sprintf("Failed to decode fork version %s", validatorDepositDataForkVersion))
|
||||
assert(len(forkVersion) == 4, "Fork version must be exactly four bytes")
|
||||
} else {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to connect to beacon node")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
if err != nil {
|
||||
outputIf(!quiet, "Could not connect to beacon node; supply a connection with --connection or provide a fork version with --forkversion to generate a deposit")
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
genesisForkVersion, exists := config["GenesisForkVersion"]
|
||||
assert(exists, "Failed to obtain genesis fork version")
|
||||
forkVersion = genesisForkVersion.([]byte)
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Fork version is %x", forkVersion))
|
||||
|
||||
domain := e2types.Domain(e2types.DomainDeposit, forkVersion, e2types.ZeroGenesisValidatorsRoot)
|
||||
outputIf(debug, fmt.Sprintf("Domain is %x", domain))
|
||||
signature, err := signStruct(validatorAccount, depositData, domain)
|
||||
errCheck(err, "Failed to generate deposit data signature")
|
||||
|
||||
signedDepositData := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
@@ -101,19 +140,25 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth
|
||||
Value uint64
|
||||
Signature []byte `ssz-size:"96"`
|
||||
}{
|
||||
PubKey: validatorAccount.PublicKey().Marshal(),
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
Signature: signature.Marshal(),
|
||||
}
|
||||
|
||||
outputIf(debug, fmt.Sprintf("Deposit data signature is %x", signedDepositData.Signature))
|
||||
if debug {
|
||||
fmt.Printf("Signed deposit data:\n")
|
||||
fmt.Printf(" Public key: %#x\n", signedDepositData.PubKey)
|
||||
fmt.Printf(" Withdrawal credentials: %#x\n", signedDepositData.WithdrawalCredentials)
|
||||
fmt.Printf(" Value: %d\n", signedDepositData.Value)
|
||||
fmt.Printf(" Signature: %#x\n", signedDepositData.Signature)
|
||||
}
|
||||
|
||||
depositDataRoot, err := ssz.HashTreeRoot(signedDepositData)
|
||||
errCheck(err, "Failed to generate deposit data root")
|
||||
outputIf(debug, fmt.Sprintf("Deposit data root is %x", depositDataRoot))
|
||||
|
||||
if validatorDepositDataRaw {
|
||||
switch {
|
||||
case validatorDepositDataRaw:
|
||||
// Build a raw transaction by hand
|
||||
txData := []byte{0x22, 0x89, 0x51, 0x18}
|
||||
// Pointer to validator public key
|
||||
@@ -126,7 +171,7 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth
|
||||
txData = append(txData, depositDataRoot[:]...)
|
||||
// Validator public key (pad to 32-byte boundary)
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30}...)
|
||||
txData = append(txData, validatorAccount.PublicKey().Marshal()...)
|
||||
txData = append(txData, validatorPubKey.Marshal()...)
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
|
||||
// Withdrawal credentials
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20}...)
|
||||
@@ -135,8 +180,21 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60}...)
|
||||
txData = append(txData, signedDepositData.Signature...)
|
||||
outputs = append(outputs, fmt.Sprintf("%#x", txData))
|
||||
} else {
|
||||
outputs = append(outputs, fmt.Sprintf(`{"account":"%s","pubkey":"%048x","withdrawal_credentials":"%032x","signature":"%096x","value":%d,"deposit_data_root":"%032x"}`, fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()), signedDepositData.PubKey, signedDepositData.WithdrawalCredentials, signedDepositData.Signature, val, depositDataRoot))
|
||||
case validatorDepositDataLaunchpad:
|
||||
depositMessage := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
}{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
}
|
||||
depositMessageRoot, err := ssz.HashTreeRoot(depositMessage)
|
||||
errCheck(err, "Failed to generate deposit message root")
|
||||
outputs = append(outputs, fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x"}`, signedDepositData.PubKey, signedDepositData.WithdrawalCredentials, val, signedDepositData.Signature, depositMessageRoot, depositDataRoot, forkVersion))
|
||||
default:
|
||||
outputs = append(outputs, fmt.Sprintf(`{"name":"Deposit for %s","account":"%s","pubkey":"%#x","withdrawal_credentials":"%#x","signature":"%#x","value":%d,"deposit_data_root":"%#x","version":2}`, fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()), fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()), signedDepositData.PubKey, signedDepositData.WithdrawalCredentials, signedDepositData.Signature, val, depositDataRoot))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +217,9 @@ func init() {
|
||||
validatorFlags(validatorDepositDataCmd)
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataValidatorAccount, "validatoraccount", "", "Account of the account carrying out the validation")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataWithdrawalAccount, "withdrawalaccount", "", "Account of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataWithdrawalPubKey, "withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataDepositValue, "depositvalue", "", "Value of the amount to be deposited")
|
||||
validatorDepositDataCmd.Flags().BoolVar(&validatorDepositDataRaw, "raw", false, "Print raw deposit data transaction data")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataForkVersion, "forkversion", "", "Use a hard-coded fork version (default is to fetch it from the node)")
|
||||
validatorDepositDataCmd.Flags().BoolVar(&validatorDepositDataLaunchpad, "launchpad", false, "Print launchpad-compatible JSON")
|
||||
}
|
||||
|
||||
@@ -15,19 +15,27 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
types "github.com/wealdtech/go-eth2-types"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var validatorExitEpoch int64
|
||||
var validatorExitKey string
|
||||
var validatorExitJSON string
|
||||
var validatorExitJSONOutput bool
|
||||
|
||||
var validatorExitCmd = &cobra.Command{
|
||||
Use: "exit",
|
||||
@@ -36,79 +44,220 @@ var validatorExitCmd = &cobra.Command{
|
||||
|
||||
ethdo validator exit --account=primary/validator --passphrase=secret
|
||||
|
||||
In quiet mode this will return 0 if the transaction has been sent, otherwise 1.`,
|
||||
In quiet mode this will return 0 if the transaction has been generated, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Sanity checking and setup.
|
||||
assert(rootAccount != "", "--account is required")
|
||||
account, err := accountFromPath(rootAccount)
|
||||
errCheck(err, "Failed to access account")
|
||||
err = connect()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connect to Ethereum 2 beacon chain node")
|
||||
|
||||
// Beacon chain config required for later work.
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
exit, signature, forkVersion := validatorExitHandleInput(ctx)
|
||||
validatorExitHandleExit(ctx, exit, signature, forkVersion)
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// Fetch the validator's index.
|
||||
index, err := grpc.FetchValidatorIndex(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator index")
|
||||
outputIf(debug, fmt.Sprintf("Validator index is %d", index))
|
||||
func validatorExitHandleInput(ctx context.Context) (*ethpb.VoluntaryExit, e2types.Signature, []byte) {
|
||||
if validatorExitJSON != "" {
|
||||
return validatorExitHandleJSONInput(validatorExitJSON)
|
||||
}
|
||||
if viper.GetString("account") != "" {
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
outputIf(debug, fmt.Sprintf("Account %s obtained", account.Name()))
|
||||
return validatorExitHandleAccountInput(ctx, account)
|
||||
}
|
||||
if validatorExitKey != "" {
|
||||
privKeyBytes, err := hex.DecodeString(strings.TrimPrefix(validatorExitKey, "0x"))
|
||||
errCheck(err, fmt.Sprintf("Failed to decode key %s", validatorExitKey))
|
||||
account, err := util.NewScratchAccount(privKeyBytes, nil)
|
||||
errCheck(err, "Invalid private key")
|
||||
return validatorExitHandleAccountInput(ctx, account)
|
||||
}
|
||||
die("one of --json, --account or --key is required")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Ensure the validator is active.
|
||||
state, err := grpc.FetchValidatorState(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator state")
|
||||
outputIf(debug, fmt.Sprintf("Validator state is %v", state))
|
||||
assert(state == ethpb.ValidatorStatus_ACTIVE, "Validator must be active to exit")
|
||||
func validatorExitHandleJSONInput(input string) (*ethpb.VoluntaryExit, e2types.Signature, []byte) {
|
||||
data := &validatorExitData{}
|
||||
err := json.Unmarshal([]byte(input), data)
|
||||
errCheck(err, "Invalid JSON input")
|
||||
exit := ðpb.VoluntaryExit{
|
||||
Epoch: data.Epoch,
|
||||
ValidatorIndex: data.ValidatorIndex,
|
||||
}
|
||||
signature, err := e2types.BLSSignatureFromBytes(data.Signature)
|
||||
errCheck(err, "Invalid signature")
|
||||
return exit, signature, data.ForkVersion
|
||||
}
|
||||
|
||||
func validatorExitHandleAccountInput(ctx context.Context, account e2wtypes.Account) (*ethpb.VoluntaryExit, e2types.Signature, []byte) {
|
||||
exit := ðpb.VoluntaryExit{}
|
||||
|
||||
// Beacon chain config required for later work.
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain beacon chain configuration")
|
||||
secondsPerSlot, ok := config["SecondsPerSlot"].(uint64)
|
||||
assert(ok, "Failed to obtain seconds per slot from chain")
|
||||
slotsPerEpoch, ok := config["SlotsPerEpoch"].(uint64)
|
||||
assert(ok, "Failed to obtain slots per epoch from chain")
|
||||
secondsPerEpoch := secondsPerSlot * slotsPerEpoch
|
||||
|
||||
// Fetch the validator's index.
|
||||
index, err := grpc.FetchValidatorIndex(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator index")
|
||||
outputIf(debug, fmt.Sprintf("Validator index is %d", index))
|
||||
exit.ValidatorIndex = index
|
||||
|
||||
// Ensure the validator is active.
|
||||
state, err := grpc.FetchValidatorState(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator state")
|
||||
outputIf(debug, fmt.Sprintf("Validator state is %v", state))
|
||||
assert(state == ethpb.ValidatorStatus_ACTIVE, "Validator must be active to exit")
|
||||
|
||||
if validatorExitEpoch < 0 {
|
||||
// Ensure the validator has been active long enough to exit.
|
||||
validator, err := grpc.FetchValidator(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator information")
|
||||
outputIf(debug, fmt.Sprintf("Activation epoch is %v", validator.ActivationEpoch))
|
||||
earliestExitEpoch := validator.ActivationEpoch + config["PersistentCommitteePeriod"].(uint64)
|
||||
shardCommitteePeriod, ok := config["ShardCommitteePeriod"].(uint64)
|
||||
assert(ok, "Failed to obtain shard committee period from chain")
|
||||
earliestExitEpoch := validator.ActivationEpoch + shardCommitteePeriod
|
||||
|
||||
secondsPerEpoch := config["SecondsPerSlot"].(uint64) * config["SlotsPerEpoch"].(uint64)
|
||||
genesisTime, err := grpc.FetchGenesis(eth2GRPCConn)
|
||||
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
|
||||
errCheck(err, "Failed to obtain genesis time")
|
||||
|
||||
currentEpoch := uint64(time.Since(genesisTime).Seconds()) / secondsPerEpoch
|
||||
assert(currentEpoch >= earliestExitEpoch, fmt.Sprintf("Validator cannot exit until %s ( epoch %d)", genesisTime.Add(time.Duration(secondsPerEpoch*earliestExitEpoch)*time.Second).Format(time.Stamp), earliestExitEpoch))
|
||||
assert(currentEpoch >= earliestExitEpoch, fmt.Sprintf("Validator cannot exit until %s ( epoch %d); transaction not sent", genesisTime.Add(time.Duration(secondsPerEpoch*earliestExitEpoch)*time.Second).Format(time.UnixDate), earliestExitEpoch))
|
||||
outputIf(verbose, "Validator confirmed to be in a suitable state")
|
||||
exit.Epoch = currentEpoch
|
||||
} else {
|
||||
// User-specified epoch; no checks.
|
||||
exit.Epoch = uint64(validatorExitEpoch)
|
||||
}
|
||||
|
||||
// Set up the transaction.
|
||||
exit := ðpb.VoluntaryExit{
|
||||
Epoch: currentEpoch,
|
||||
ValidatorIndex: index,
|
||||
// TODO fetch current fork version from config (currently using genesis fork version)
|
||||
forkVersion := config["GenesisForkVersion"].([]byte)
|
||||
outputIf(debug, fmt.Sprintf("Current fork version is %x", forkVersion))
|
||||
genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn)
|
||||
outputIf(debug, fmt.Sprintf("Genesis validators root is %x", genesisValidatorsRoot))
|
||||
errCheck(err, "Failed to obtain genesis validators root")
|
||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, forkVersion, genesisValidatorsRoot)
|
||||
|
||||
alreadyUnlocked, err := unlock(account)
|
||||
errCheck(err, "Failed to unlock account; please confirm passphrase is correct")
|
||||
signature, err := signStruct(account, exit, domain)
|
||||
if !alreadyUnlocked {
|
||||
errCheck(lock(account), "Failed to re-lock account")
|
||||
}
|
||||
errCheck(err, "Failed to sign exit proposal")
|
||||
|
||||
return exit, signature, forkVersion
|
||||
}
|
||||
|
||||
// validatorExitHandleExit handles the exit request.
|
||||
func validatorExitHandleExit(ctx context.Context, exit *ethpb.VoluntaryExit, signature e2types.Signature, forkVersion []byte) {
|
||||
if validatorExitJSONOutput {
|
||||
data := &validatorExitData{
|
||||
Epoch: exit.Epoch,
|
||||
ValidatorIndex: exit.ValidatorIndex,
|
||||
Signature: signature.Marshal(),
|
||||
ForkVersion: forkVersion,
|
||||
}
|
||||
root, err := ssz.HashTreeRoot(exit)
|
||||
errCheck(err, "Failed to generate exit proposal root")
|
||||
// TODO fetch current fork version from config (currently using genesis fork version)
|
||||
currentForkVersion := config["GenesisForkVersion"].([]byte)
|
||||
domain := types.Domain(types.DomainVoluntaryExit, currentForkVersion)
|
||||
|
||||
err = account.Unlock([]byte(rootAccountPassphrase))
|
||||
errCheck(err, "Failed to unlock account; please confirm passphrase is correct")
|
||||
signature, err := sign(account, root[:], domain)
|
||||
errCheck(err, "Failed to sign exit proposal")
|
||||
|
||||
res, err := json.Marshal(data)
|
||||
errCheck(err, "Failed to generate JSON")
|
||||
outputIf(!quiet, string(res))
|
||||
} else {
|
||||
proposal := ðpb.SignedVoluntaryExit{
|
||||
Exit: exit,
|
||||
Signature: signature.Marshal(),
|
||||
}
|
||||
|
||||
validatorClient := ethpb.NewBeaconNodeValidatorClient(eth2GRPCConn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, err = validatorClient.ProposeExit(ctx, proposal)
|
||||
_, err := validatorClient.ProposeExit(ctx, proposal)
|
||||
errCheck(err, "Failed to propose exit")
|
||||
|
||||
outputIf(!quiet, "Validator exit transaction sent")
|
||||
os.Exit(_exit_success)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorExitCmd)
|
||||
validatorFlags(validatorExitCmd)
|
||||
validatorExitCmd.Flags().Int64Var(&validatorExitEpoch, "epoch", -1, "Epoch at which to exit (defaults to now)")
|
||||
addTransactionFlags(validatorExitCmd)
|
||||
validatorExitCmd.Flags().Int64Var(&validatorExitEpoch, "epoch", -1, "Epoch at which to exit (defaults to current epoch)")
|
||||
validatorExitCmd.Flags().StringVar(&validatorExitKey, "key", "", "Private key if account not known by ethdo")
|
||||
validatorExitCmd.Flags().BoolVar(&validatorExitJSONOutput, "json-output", false, "Print JSON transaction; do not broadcast to network")
|
||||
validatorExitCmd.Flags().StringVar(&validatorExitJSON, "json", "", "Use JSON as created by --json-output to exit")
|
||||
}
|
||||
|
||||
type validatorExitData struct {
|
||||
Epoch uint64 `json:"epoch"`
|
||||
ValidatorIndex uint64 `json:"validator_index"`
|
||||
Signature []byte `json:"signature"`
|
||||
ForkVersion []byte `json:"fork_version"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements custom JSON marshaller.
|
||||
func (d *validatorExitData) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`{"epoch":%d,"validator_index":%d,"signature":"%#x","fork_version":"%#x"}`, d.Epoch, d.ValidatorIndex, d.Signature, d.ForkVersion)), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements custom JSON unmarshaller.
|
||||
func (d *validatorExitData) UnmarshalJSON(data []byte) error {
|
||||
var v map[string]interface{}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if val, exists := v["epoch"]; exists {
|
||||
var ok bool
|
||||
epoch, ok := val.(float64)
|
||||
if !ok {
|
||||
return errors.New("epoch invalid")
|
||||
}
|
||||
d.Epoch = uint64(epoch)
|
||||
} else {
|
||||
return errors.New("epoch missing")
|
||||
}
|
||||
|
||||
if val, exists := v["validator_index"]; exists {
|
||||
var ok bool
|
||||
validatorIndex, ok := val.(float64)
|
||||
if !ok {
|
||||
return errors.New("validator_index invalid")
|
||||
}
|
||||
d.ValidatorIndex = uint64(validatorIndex)
|
||||
} else {
|
||||
return errors.New("validator_index missing")
|
||||
}
|
||||
|
||||
if val, exists := v["signature"]; exists {
|
||||
signatureBytes, ok := val.(string)
|
||||
if !ok {
|
||||
return errors.New("signature invalid")
|
||||
}
|
||||
signature, err := hex.DecodeString(strings.TrimPrefix(signatureBytes, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "signature invalid")
|
||||
}
|
||||
d.Signature = signature
|
||||
} else {
|
||||
return errors.New("signature missing")
|
||||
}
|
||||
|
||||
if val, exists := v["fork_version"]; exists {
|
||||
forkVersionBytes, ok := val.(string)
|
||||
if !ok {
|
||||
return errors.New("fork version invalid")
|
||||
}
|
||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(forkVersionBytes, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fork version invalid")
|
||||
}
|
||||
d.ForkVersion = forkVersion
|
||||
} else {
|
||||
return errors.New("fork version missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
201
cmd/validatorinfo.go
Normal file
201
cmd/validatorinfo.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
var validatorInfoPubKey string
|
||||
|
||||
var validatorInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a validator",
|
||||
Long: `Obtain information about validator. For example:
|
||||
|
||||
ethdo validator info --account=primary/validator
|
||||
|
||||
In quiet mode this will return 0 if the validator information can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(viper.GetString("account") != "" || validatorInfoPubKey != "", "--account or --pubkey is required")
|
||||
|
||||
err := connect()
|
||||
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node")
|
||||
|
||||
account, err := validatorInfoAccount()
|
||||
errCheck(err, "Failed to obtain validator account")
|
||||
|
||||
if verbose {
|
||||
network := network()
|
||||
outputIf(debug, fmt.Sprintf("Network is %s", network))
|
||||
pubKey, err := bestPublicKey(account)
|
||||
if err == nil {
|
||||
deposits, totalDeposited, err := graphData(network, pubKey.Marshal())
|
||||
if err == nil {
|
||||
fmt.Printf("Number of deposits: %d\n", deposits)
|
||||
fmt.Printf("Total deposited: %s\n", string2eth.GWeiToString(totalDeposited, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validatorInfo, err := grpc.FetchValidatorInfo(eth2GRPCConn, account)
|
||||
errCheck(err, "Failed to obtain validator information")
|
||||
validator, err := grpc.FetchValidator(eth2GRPCConn, account)
|
||||
if err != nil {
|
||||
// We can live with this.
|
||||
validator = nil
|
||||
}
|
||||
if validatorInfo.Status != ethpb.ValidatorStatus_DEPOSITED &&
|
||||
validatorInfo.Status != ethpb.ValidatorStatus_UNKNOWN_STATUS {
|
||||
errCheck(err, "Failed to obtain validator definition")
|
||||
}
|
||||
assert(validatorInfo.Status != ethpb.ValidatorStatus_UNKNOWN_STATUS, "Not known as a validator")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
outputIf(verbose, fmt.Sprintf("Epoch of data: %v", validatorInfo.Epoch))
|
||||
outputIf(verbose && validatorInfo.Status != ethpb.ValidatorStatus_DEPOSITED, fmt.Sprintf("Index: %v", validatorInfo.Index))
|
||||
outputIf(verbose, fmt.Sprintf("Public key: %#x", validatorInfo.PublicKey))
|
||||
fmt.Printf("Status: %s\n", strings.Title(strings.ToLower(validatorInfo.Status.String())))
|
||||
fmt.Printf("Balance: %s\n", string2eth.GWeiToString(validatorInfo.Balance, true))
|
||||
|
||||
if validatorInfo.Status == ethpb.ValidatorStatus_ACTIVE ||
|
||||
validatorInfo.Status == ethpb.ValidatorStatus_EXITING ||
|
||||
validatorInfo.Status == ethpb.ValidatorStatus_SLASHING {
|
||||
fmt.Printf("Effective balance: %s\n", string2eth.GWeiToString(validatorInfo.EffectiveBalance, true))
|
||||
}
|
||||
|
||||
if validator != nil {
|
||||
outputIf(verbose, fmt.Sprintf("Withdrawal credentials: %#x", validator.WithdrawalCredentials))
|
||||
}
|
||||
|
||||
transition := time.Unix(int64(validatorInfo.TransitionTimestamp), 0)
|
||||
transitionPassed := int64(validatorInfo.TransitionTimestamp) <= time.Now().Unix()
|
||||
switch validatorInfo.Status {
|
||||
case ethpb.ValidatorStatus_DEPOSITED:
|
||||
if validatorInfo.TransitionTimestamp != 0 {
|
||||
fmt.Printf("Inclusion in chain: %s\n", transition)
|
||||
}
|
||||
case ethpb.ValidatorStatus_PENDING:
|
||||
fmt.Printf("Activation: %s\n", transition)
|
||||
case ethpb.ValidatorStatus_EXITING, ethpb.ValidatorStatus_SLASHING:
|
||||
fmt.Printf("Attesting finishes: %s\n", transition)
|
||||
case ethpb.ValidatorStatus_EXITED:
|
||||
if transitionPassed {
|
||||
fmt.Printf("Funds withdrawable: Now\n")
|
||||
} else {
|
||||
fmt.Printf("Funds withdrawable: %s\n", transition)
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// validatorInfoAccount obtains the account for the validator info command.
|
||||
func validatorInfoAccount() (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(validatorInfoPubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", validatorInfoPubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", validatorInfoPubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// graphData returns data from the graph about number and amount of deposits
|
||||
func graphData(network string, validatorPubKey []byte) (uint64, uint64, error) {
|
||||
subgraph := fmt.Sprintf("attestantio/eth2deposits-%s", strings.ToLower(network))
|
||||
query := fmt.Sprintf(`{"query": "{deposits(where: {validatorPubKey:\"%#x\"}) { id amount withdrawalCredentials }}"}`, validatorPubKey)
|
||||
url := fmt.Sprintf("https://api.thegraph.com/subgraphs/name/%s", subgraph)
|
||||
graphResp, err := http.Post(url, "application/json", bytes.NewBufferString(query))
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrap(err, "failed to check if there is already a deposit for this validator")
|
||||
}
|
||||
defer graphResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(graphResp.Body)
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrap(err, "bad information returned from existing deposit check")
|
||||
}
|
||||
|
||||
type graphDeposit struct {
|
||||
Index string `json:"index"`
|
||||
Amount string `json:"amount"`
|
||||
WithdrawalCredentials string `json:"withdrawalCredentials"`
|
||||
}
|
||||
type graphData struct {
|
||||
Deposits []*graphDeposit `json:"deposits,omitempty"`
|
||||
}
|
||||
type graphResponse struct {
|
||||
Data *graphData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
var response graphResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return 0, 0, errors.Wrap(err, "invalid data returned from existing deposit check")
|
||||
}
|
||||
deposits := uint64(0)
|
||||
totalDeposited := uint64(0)
|
||||
if response.Data != nil && len(response.Data.Deposits) > 0 {
|
||||
for _, deposit := range response.Data.Deposits {
|
||||
deposits++
|
||||
depositAmount, err := strconv.ParseUint(deposit.Amount, 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrap(err, fmt.Sprintf("invalid deposit amount from pre-existing deposit %s", deposit.Amount))
|
||||
}
|
||||
totalDeposited += depositAmount
|
||||
}
|
||||
}
|
||||
return deposits, totalDeposited, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorInfoCmd)
|
||||
validatorInfoCmd.Flags().StringVar(&validatorInfoPubKey, "pubkey", "", "Public key for which to obtain status")
|
||||
validatorFlags(validatorInfoCmd)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -22,15 +22,17 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var ReleaseVersion = "local build from v1.6.0"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Version of Ethdo",
|
||||
Long: `Obtain the version of Ethdo. For example:
|
||||
Short: "Version of ethdo",
|
||||
Long: `Obtain the version of ethdo. For example:
|
||||
|
||||
ethdo version.`,
|
||||
ethdo version`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("1.2.1")
|
||||
fmt.Println(ReleaseVersion)
|
||||
if viper.GetBool("verbose") {
|
||||
buildInfo, ok := dbg.ReadBuildInfo()
|
||||
if ok {
|
||||
@@ -44,7 +46,7 @@ var versionCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Exit(_exit_success)
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var walletWallet string
|
||||
|
||||
// walletCmd represents the wallet command
|
||||
var walletCmd = &cobra.Command{
|
||||
Use: "wallet",
|
||||
@@ -30,6 +30,16 @@ func init() {
|
||||
RootCmd.AddCommand(walletCmd)
|
||||
}
|
||||
|
||||
var walletFlag *pflag.Flag
|
||||
|
||||
func walletFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&walletWallet, "wallet", "", "Name of the wallet")
|
||||
if walletFlag == nil {
|
||||
cmd.Flags().String("wallet", "", "Name of the wallet")
|
||||
walletFlag = cmd.Flags().Lookup("wallet")
|
||||
if err := viper.BindPFlag("wallet", walletFlag); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
cmd.Flags().AddFlag(walletFlag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var walletAccountsCmd = &cobra.Command{
|
||||
@@ -29,27 +35,74 @@ var walletAccountsCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the wallet holds any addresses, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletWallet != "", "wallet is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
wallet, err := walletFromPath(walletWallet)
|
||||
errCheck(err, "Failed to access wallet")
|
||||
assert(viper.GetString("wallet") != "", "wallet is required")
|
||||
|
||||
hasAccounts := false
|
||||
for account := range wallet.Accounts() {
|
||||
hasAccounts = true
|
||||
wallet, err := walletFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain wallet")
|
||||
|
||||
accounts := make([]e2wtypes.Account, 0, 128)
|
||||
for account := range wallet.Accounts(ctx) {
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
assert(len(accounts) > 0, "")
|
||||
|
||||
if _, isPathProvider := accounts[0].(e2wtypes.AccountPathProvider); isPathProvider {
|
||||
// Order accounts by their path components.
|
||||
sort.Slice(accounts, func(i int, j int) bool {
|
||||
iBits := strings.Split(accounts[i].(e2wtypes.AccountPathProvider).Path(), "/")
|
||||
jBits := strings.Split(accounts[j].(e2wtypes.AccountPathProvider).Path(), "/")
|
||||
for index := range iBits {
|
||||
if iBits[index] == "m" && jBits[index] == "m" {
|
||||
continue
|
||||
}
|
||||
if len(jBits) <= index {
|
||||
return false
|
||||
}
|
||||
iBit, err := strconv.ParseUint(iBits[index], 10, 64)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
jBit, err := strconv.ParseUint(jBits[index], 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if iBit < jBit {
|
||||
return true
|
||||
}
|
||||
if iBit > jBit {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(jBits) > len(iBits)
|
||||
})
|
||||
} else {
|
||||
// Order accounts by their name.
|
||||
sort.Slice(accounts, func(i int, j int) bool {
|
||||
return strings.Compare(accounts[i].Name(), accounts[j].Name()) < 0
|
||||
})
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
outputIf(!quiet, account.Name())
|
||||
if verbose {
|
||||
fmt.Printf("%s\n\tUUID:\t\t%s\n\tPublic key:\t0x%048x\n", account.Name(), account.ID(), account.PublicKey().Marshal())
|
||||
} else if !quiet {
|
||||
fmt.Printf("%s\n", account.Name())
|
||||
fmt.Printf(" UUID: %v\n", account.ID())
|
||||
if pathProvider, isProvider := account.(e2wtypes.AccountPathProvider); isProvider {
|
||||
if pathProvider.Path() != "" {
|
||||
fmt.Printf("Path: %s\n", pathProvider.Path())
|
||||
}
|
||||
}
|
||||
if pubKeyProvider, isProvider := account.(e2wtypes.AccountPublicKeyProvider); isProvider {
|
||||
fmt.Printf(" Public key: %#x\n", pubKeyProvider.PublicKey().Marshal())
|
||||
}
|
||||
if compositePubKeyProvider, isProvider := account.(e2wtypes.AccountCompositePublicKeyProvider); isProvider {
|
||||
fmt.Printf(" Composite public key: %#x\n", compositePubKeyProvider.CompositePublicKey().Marshal())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if quiet {
|
||||
if hasAccounts {
|
||||
os.Exit(_exit_success)
|
||||
}
|
||||
os.Exit(_exit_failure)
|
||||
}
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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
|
||||
@@ -14,14 +14,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
"github.com/spf13/viper"
|
||||
bip39 "github.com/tyler-smith/go-bip39"
|
||||
distributed "github.com/wealdtech/go-eth2-wallet-distributed"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
hd "github.com/wealdtech/go-eth2-wallet-hd/v2"
|
||||
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
var walletCreateType string
|
||||
|
||||
var walletCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a wallet",
|
||||
@@ -31,16 +40,28 @@ var walletCreateCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the wallet is created successfully, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletWallet != "", "--wallet is required")
|
||||
assert(walletCreateType != "", "--type is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("remote") == "", "wallet create not available with remote wallets")
|
||||
assert(viper.GetString("wallet") != "", "--wallet is required")
|
||||
assert(viper.GetString("type") != "", "--type is required")
|
||||
|
||||
var err error
|
||||
switch strings.ToLower(walletCreateType) {
|
||||
switch strings.ToLower(viper.GetString("type")) {
|
||||
case "non-deterministic", "nd":
|
||||
_, err = wallet.CreateWallet(walletWallet, wallet.WithType("nd"))
|
||||
assert(viper.GetString("mnemonic") == "", "--mnemonic is not allowed with non-deterministic wallets")
|
||||
err = walletCreateND(ctx, viper.GetString("wallet"))
|
||||
case "hierarchical deterministic", "hd":
|
||||
assert(rootWalletPassphrase != "", "--walletpassphrase is required for hierarchical deterministic wallets")
|
||||
_, err = wallet.CreateWallet(walletWallet, wallet.WithType("hd"), wallet.WithPassphrase([]byte(rootWalletPassphrase)))
|
||||
if quiet {
|
||||
fmt.Printf("Creation of hierarchical deterministic wallets prints its mnemonic, so cannot be run with the --quiet flag")
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
assert(getWalletPassphrase() != "", "--walletpassphrase is required for hierarchical deterministic wallets")
|
||||
err = walletCreateHD(ctx, viper.GetString("wallet"), getWalletPassphrase(), viper.GetString("mnemonic"))
|
||||
case "distributed":
|
||||
assert(viper.GetString("mnemonic") == "", "--mnemonic is not allowed with distributed wallets")
|
||||
err = walletCreateDistributed(ctx, viper.GetString("wallet"))
|
||||
default:
|
||||
die("unknown wallet type")
|
||||
}
|
||||
@@ -48,8 +69,84 @@ In quiet mode this will return 0 if the wallet is created successfully, otherwis
|
||||
},
|
||||
}
|
||||
|
||||
// walletCreateND creates a non-deterministic wallet.
|
||||
func walletCreateND(ctx context.Context, name string) error {
|
||||
_, err := nd.CreateWallet(ctx, name, store, keystorev4.New())
|
||||
return err
|
||||
}
|
||||
|
||||
// walletCreateDistributed creates a distributed wallet.
|
||||
func walletCreateDistributed(ctx context.Context, name string) error {
|
||||
_, err := distributed.CreateWallet(ctx, name, store, keystorev4.New())
|
||||
return err
|
||||
}
|
||||
|
||||
// walletCreateHD creates a hierarchical-deterministic wallet.
|
||||
func walletCreateHD(ctx context.Context, name string, passphrase string, mnemonic string) error {
|
||||
encryptor := keystorev4.New()
|
||||
|
||||
printMnemonic := mnemonic == ""
|
||||
mnemonicPassphrase := ""
|
||||
|
||||
if mnemonic == "" {
|
||||
// Create a new random mnemonic.
|
||||
entropy := make([]byte, 32)
|
||||
_, err := rand.Read(entropy)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate entropy for wallet mnemonic")
|
||||
}
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate wallet mnemonic")
|
||||
}
|
||||
} else {
|
||||
// We have an existing mnemonic. If there are more than 24 words we treat the additional characters as the passphrase.
|
||||
mnemonicParts := strings.Split(mnemonic, " ")
|
||||
if len(mnemonicParts) > 24 {
|
||||
mnemonic = strings.Join(mnemonicParts[:24], " ")
|
||||
mnemonicPassphrase = strings.Join(mnemonicParts[24:], " ")
|
||||
}
|
||||
}
|
||||
// Normalise the input.
|
||||
mnemonic = string(norm.NFKD.Bytes([]byte(mnemonic)))
|
||||
mnemonicPassphrase = string(norm.NFKD.Bytes([]byte(mnemonicPassphrase)))
|
||||
|
||||
// Ensure the mnemonic is valid
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return errors.New("mnemonic is not valid")
|
||||
}
|
||||
|
||||
// Create seed from mnemonic and passphrase.
|
||||
seed := bip39.NewSeed(mnemonic, mnemonicPassphrase)
|
||||
|
||||
_, err := hd.CreateWallet(ctx, name, []byte(passphrase), store, encryptor, seed)
|
||||
|
||||
if printMnemonic {
|
||||
fmt.Printf(`The following phrase is your mnemonic for this wallet:
|
||||
|
||||
%s
|
||||
|
||||
Anyone with access to this mnemonic can recreate the accounts in this wallet, so please store this mnemonic safely. More information about mnemonics can be found at https://support.mycrypto.com/general-knowledge/cryptography/how-do-mnemonic-phrases-work
|
||||
|
||||
Please note this mnemonic is not stored within the wallet, so cannot be retrieved or displayed again. As such, this mnemonic should be written down or otherwise protected before proceeding.
|
||||
`, mnemonic)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
walletCmd.AddCommand(walletCreateCmd)
|
||||
walletFlags(walletCreateCmd)
|
||||
walletCreateCmd.Flags().StringVar(&walletCreateType, "type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
|
||||
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
|
||||
walletCreateCmd.Flags().String("mnemonic", "", "The 24-word mnemonic for a hierarchical deterministic wallet")
|
||||
}
|
||||
|
||||
func walletCreateBindings() {
|
||||
if err := viper.BindPFlag("type", walletCreateCmd.Flags().Lookup("type")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("mnemonic", walletCreateCmd.Flags().Lookup("mnemonic")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
60
cmd/walletdelete.go
Normal file
60
cmd/walletdelete.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var walletDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a wallet",
|
||||
Long: `Delete a wallet. For example:
|
||||
|
||||
ethdo wallet delete --wallet=primary
|
||||
|
||||
In quiet mode this will return 0 if the wallet has been deleted, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(viper.GetString("remote") == "", "wallet delete not available with remote wallets")
|
||||
assert(viper.GetString("wallet") != "", "--wallet is required")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
|
||||
errCheck(err, "Failed to access wallet")
|
||||
|
||||
storeProvider, ok := wallet.(wtypes.StoreProvider)
|
||||
assert(ok, "Cannot obtain store for the wallet")
|
||||
store := storeProvider.Store()
|
||||
storeLocationProvider, ok := store.(wtypes.StoreLocationProvider)
|
||||
assert(ok, "Cannot obtain store location for the wallet")
|
||||
walletLocation := filepath.Join(storeLocationProvider.Location(), wallet.ID().String())
|
||||
err = os.RemoveAll(walletLocation)
|
||||
errCheck(err, "Failed to delete wallet")
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
walletCmd.AddCommand(walletDeleteCmd)
|
||||
walletFlags(walletDeleteCmd)
|
||||
}
|
||||
@@ -14,11 +14,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
types "github.com/wealdtech/go-eth2-wallet-types"
|
||||
"github.com/spf13/viper"
|
||||
types "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var walletExportPassphrase string
|
||||
@@ -32,20 +34,24 @@ var walletExportCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the wallet is able to be exported, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletWallet != "", "--wallet is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(viper.GetString("remote") == "", "wallet export not available with remote wallets")
|
||||
assert(viper.GetString("wallet") != "", "--wallet is required")
|
||||
assert(walletExportPassphrase != "", "--exportpassphrase is required")
|
||||
|
||||
wallet, err := walletFromPath(walletWallet)
|
||||
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
|
||||
errCheck(err, "Failed to access wallet")
|
||||
|
||||
_, ok := wallet.(types.WalletExporter)
|
||||
assert(ok, fmt.Sprintf("wallets of type %q do not allow exporting accounts", wallet.Type()))
|
||||
|
||||
exportData, err := wallet.(types.WalletExporter).Export([]byte(walletExportPassphrase))
|
||||
exportData, err := wallet.(types.WalletExporter).Export(ctx, []byte(walletExportPassphrase))
|
||||
errCheck(err, "Failed to export wallet")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("0x%x", exportData))
|
||||
os.Exit(_exit_success)
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
"github.com/wealdtech/go-ecodec"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
)
|
||||
|
||||
var walletImportData string
|
||||
var walletImportPassphrase string
|
||||
var walletImportVerify bool
|
||||
|
||||
var walletImportCmd = &cobra.Command{
|
||||
Use: "import",
|
||||
@@ -36,8 +41,10 @@ var walletImportCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the wallet is imported successfully, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletImportData != "", "--walletimportdata is required")
|
||||
assert(viper.GetString("remote") == "", "wallet import not available with remote wallets")
|
||||
assert(walletImportData != "", "--importdata is required")
|
||||
assert(walletImportPassphrase != "", "--importpassphrase is required")
|
||||
assert(viper.GetString("wallet") == "", "--wallet is not allowed (the wallet will retain its name)")
|
||||
|
||||
if !strings.HasPrefix(walletImportData, "0x") {
|
||||
outputIf(debug, fmt.Sprintf("Reading wallet import from file %s", walletImportData))
|
||||
@@ -50,10 +57,44 @@ In quiet mode this will return 0 if the wallet is imported successfully, otherwi
|
||||
importData, err := bytesutil.FromHexString(walletImportData)
|
||||
errCheck(err, "Failed to decode wallet data")
|
||||
|
||||
_, err = wallet.ImportWallet(importData, []byte(walletImportPassphrase))
|
||||
errCheck(err, "Failed to import wallet")
|
||||
if walletImportVerify {
|
||||
type accountInfo struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type walletInfo struct {
|
||||
ID uuid.UUID `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type export struct {
|
||||
Wallet *walletInfo `json:"wallet"`
|
||||
Accounts []*accountInfo `json:"accounts"`
|
||||
}
|
||||
|
||||
os.Exit(_exit_success)
|
||||
data, err := ecodec.Decrypt(importData, []byte(walletImportPassphrase))
|
||||
errCheck(err, "Failed to decrypt wallet")
|
||||
ext := &export{}
|
||||
err = json.Unmarshal(data, ext)
|
||||
errCheck(err, "Failed to read wallet")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("Wallet name: %s", ext.Wallet.Name))
|
||||
outputIf(!quiet, fmt.Sprintf("Wallet type: %s", ext.Wallet.Type))
|
||||
outputIf(verbose, fmt.Sprintf("Wallet UUID: %s", ext.Wallet.ID))
|
||||
if verbose {
|
||||
fmt.Printf("Wallet accounts:\n")
|
||||
for _, account := range ext.Accounts {
|
||||
outputIf(verbose, fmt.Sprintf(" %s", account.Name))
|
||||
}
|
||||
} else {
|
||||
outputIf(!quiet, fmt.Sprintf("Wallet accounts: %d", len(ext.Accounts)))
|
||||
}
|
||||
|
||||
} else {
|
||||
_, err = e2wallet.ImportWallet(importData, []byte(walletImportPassphrase))
|
||||
errCheck(err, "Failed to import wallet")
|
||||
}
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -62,4 +103,5 @@ func init() {
|
||||
walletFlags(walletImportCmd)
|
||||
walletImportCmd.Flags().StringVar(&walletImportData, "importdata", "", "The data to import, or the name of a file to read")
|
||||
walletImportCmd.Flags().StringVar(&walletImportPassphrase, "importpassphrase", "", "Passphrase protecting the data to import")
|
||||
walletImportCmd.Flags().BoolVar(&walletImportVerify, "verify", false, "Verify the wallet can be imported, but do not import it")
|
||||
}
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types"
|
||||
"github.com/spf13/viper"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var walletInfoCmd = &cobra.Command{
|
||||
@@ -30,9 +33,13 @@ var walletInfoCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if the wallet exists, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletWallet != "", "Wallet is required")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
wallet, err := walletFromPath(walletWallet)
|
||||
assert(viper.GetString("remote") == "", "wallet info not available with remote wallets")
|
||||
assert(viper.GetString("wallet") != "", "--wallet is required")
|
||||
|
||||
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
|
||||
errCheck(err, "unknown wallet")
|
||||
|
||||
if quiet {
|
||||
@@ -46,14 +53,14 @@ In quiet mode this will return 0 if the wallet exists, otherwise 1.`,
|
||||
store := storeProvider.Store()
|
||||
fmt.Printf("Store: %s\n", store.Name())
|
||||
if storeLocationProvider, ok := store.(wtypes.StoreLocationProvider); ok {
|
||||
fmt.Printf("Location: %s\n", storeLocationProvider.Location())
|
||||
fmt.Printf("Location: %s\n", filepath.Join(storeLocationProvider.Location(), wallet.ID().String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count the accounts.
|
||||
accounts := 0
|
||||
for range wallet.Accounts() {
|
||||
for range wallet.Accounts(ctx) {
|
||||
accounts++
|
||||
}
|
||||
fmt.Printf("Accounts: %d\n", accounts)
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
"github.com/spf13/viper"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
)
|
||||
|
||||
var walletListCmd = &cobra.Command{
|
||||
@@ -30,17 +31,20 @@ var walletListCmd = &cobra.Command{
|
||||
|
||||
In quiet mode this will return 0 if any wallets are found, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(viper.GetString("remote") == "", "wallet list not available with remote wallets")
|
||||
assert(viper.GetString("wallet") == "", "wallet list does not take a --wallet parameter")
|
||||
|
||||
walletsFound := false
|
||||
for w := range wallet.Wallets() {
|
||||
for w := range e2wallet.Wallets() {
|
||||
walletsFound = true
|
||||
outputIf(!quiet && !verbose, w.Name())
|
||||
outputIf(verbose, fmt.Sprintf("%s\n\tUUID:\t\t%s", w.Name(), w.ID().String()))
|
||||
outputIf(verbose, fmt.Sprintf("%s\n UUID: %s", w.Name(), w.ID().String()))
|
||||
}
|
||||
|
||||
if !walletsFound {
|
||||
os.Exit(_exit_failure)
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
os.Exit(_exit_success)
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
bip39 "github.com/FactomProject/go-bip39"
|
||||
"github.com/spf13/cobra"
|
||||
types "github.com/wealdtech/go-eth2-wallet-types"
|
||||
)
|
||||
|
||||
var walletSeedCmd = &cobra.Command{
|
||||
Use: "seed",
|
||||
Short: "Display the seed of a wallet",
|
||||
Long: `Display the seed for an hierarchical deterministic wallet. For example:
|
||||
|
||||
ethdo wallet seed --wallet=primary
|
||||
|
||||
In quiet mode this will return 0 if the wallet is a hierarchical deterministic wallet, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
assert(walletWallet != "", "--wallet is required")
|
||||
assert(rootWalletPassphrase != "", "--walletpassphrase is required")
|
||||
|
||||
wallet, err := walletFromPath(walletWallet)
|
||||
errCheck(err, "Failed to access wallet")
|
||||
_, ok := wallet.(types.WalletKeyProvider)
|
||||
assert(ok, fmt.Sprintf("wallets of type %q do not provide keys", wallet.Type()))
|
||||
|
||||
err = wallet.Unlock([]byte(rootWalletPassphrase))
|
||||
errCheck(err, "Failed to unlock wallet")
|
||||
seed, err := wallet.(types.WalletKeyProvider).Key()
|
||||
errCheck(err, "Failed to obtain wallet key")
|
||||
outputIf(debug, fmt.Sprintf("Seed is %#0x", seed))
|
||||
seedStr, err := bip39.NewMnemonic(seed)
|
||||
errCheck(err, "Failed to generate seed mnemonic")
|
||||
|
||||
outputIf(!quiet, seedStr)
|
||||
os.Exit(_exit_success)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
walletCmd.AddCommand(walletSeedCmd)
|
||||
walletFlags(walletSeedCmd)
|
||||
}
|
||||
85
docs/howto.md
Normal file
85
docs/howto.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# How to achieve common tasks with ethdo
|
||||
|
||||
## Find out what ethdo can do
|
||||
|
||||
To find a list of topics that ethdo can carry out with the `ethdo help` command.
|
||||
|
||||
If you want more detailed information about the commands in a topic, they can be seen with the `ethdo help <topic>` command, for example:
|
||||
|
||||
```sh
|
||||
ethdo help wallet
|
||||
```
|
||||
|
||||
## List my wallets
|
||||
|
||||
The wallets you can currently access can be seen with the `ethdo wallet list` command.
|
||||
|
||||
## Create a new wallet
|
||||
|
||||
New wallets can be created with the `ethdo wallet create` command. Each wallet has to have a unique name, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet create --wallet="My wallet"
|
||||
```
|
||||
|
||||
Additional options are available to decide the type of wallet and encryption.
|
||||
|
||||
## Create an HD wallet from an existing seed
|
||||
|
||||
HD wallets can be created from an existing seed by adding the `--seed` parameter to `ethdo wallet create`, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet create --wallet="Recreated wallet" --type=hd --seed="tooth moon mad fun romance athlete envelope next mix divert tip top symbol resemble stock family melody desk sheriff drift bargain need jaguar method"
|
||||
```
|
||||
|
||||
The seed for an existing HD wallet can be obtained with `ethdo wallet seed`.
|
||||
|
||||
## Back up a wallet
|
||||
|
||||
A wallet can be backed up with the `ethdo wallet export` command. This creates an encrypted backup of the wallet, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet export --wallet="My wallet" --exportpassphrase="export secret" >export.dat
|
||||
```
|
||||
|
||||
Note that by default the wallet backup is printed to the console, hence the `>export.dat` to redirect it to a file.
|
||||
|
||||
## Restore a wallet
|
||||
|
||||
A backed up wallet can be restored with the `ethdo wallet import` command, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet import --importdata=export.dat --importpassphrase="export secret"
|
||||
```
|
||||
|
||||
In this example the wallet to be imported is being read from the `export.dat` file.
|
||||
|
||||
Note that if a wallet with the same name already exists it cannot be imported.
|
||||
|
||||
## Where is my wallet?
|
||||
|
||||
Details of the location of a wallet can be found with the `ethdo wallet info` command, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet info --verbose --wallet="My wallet"
|
||||
```
|
||||
|
||||
This will provide, amongst other information, a `Location` line giving the directory where the wallet information resides.
|
||||
|
||||
## Recreate launchpad wallet and accounts
|
||||
|
||||
Recreating launchpad accounts requires two steps: recreating the wallet, and recreating the individual accounts. All that is required is the mnemonic from the launchpad process.
|
||||
|
||||
To recreate the wallet with the given mnemonic run the following command (changing the wallet name, passphrase and mnemonic as required):
|
||||
|
||||
```sh
|
||||
ethdo wallet create --wallet="Launchpad" --type=hd --walletpassphrase=walletsecret --mnemonic="faculty key lamp panel appear choose express off absent dance strike twenty elephant expect swift that resist bicycle kind sun favorite evoke engage thumb"
|
||||
```
|
||||
|
||||
Launchpad accounts are identified by their path. The path can be seen in the filename of the keystore, for example the filename `keystore-m_12381_3600_1_0_0-1596891358.json` relates to a path of `m/12381/3600/1/0/0`. It is also present directly in the keystore under the `path` key.
|
||||
|
||||
To create an account corresponding to this key with the account name "Account 1" you would use the command:
|
||||
|
||||
```sh
|
||||
ethdo account create --account="Launchpad/Account 1" --walletpassphrase=walletsecret --passphrase=secret --path=m/12381/3600/1/0/0
|
||||
```
|
||||
232
docs/prysm.md
Normal file
232
docs/prysm.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Using ethdo with Prysm
|
||||
|
||||
## Installing ethdo
|
||||
|
||||
1. To install `ethdo`, use the instructions on the [main page](https://github.com/wealdtech/ethdo).
|
||||
|
||||
## Typical validating setups
|
||||
|
||||
This section outlines the process of setting up a configuration with two validators and a single withdrawal account using ethdo.
|
||||
|
||||
### Generating a wallet
|
||||
|
||||
To create a non-deterministic wallet, where keys generated from random data, issue the command:
|
||||
```sh
|
||||
ethdo wallet create --wallet=Validators
|
||||
```
|
||||
|
||||
If you prefer to have a hierarchical deterministic wallet, where keys are generated from a seed, issue the command:
|
||||
|
||||
```sh
|
||||
ethdo wallet create --wallet=Validators --type=hd --walletpassphrase=walletsecret
|
||||
```
|
||||
|
||||
This creates a wallet called "Validators" in the default wallet directory (see https://github.com/wealdtech/ethdo/#wallets-and-accounts for details) which contains the newly generated seed data.
|
||||
|
||||
> The `--walletpassphrase` flag and input is required to protect the seed. It is critical that you keep it private and secure.
|
||||
|
||||
Once the wallet is created, fetch its data to ensure it exists by issuing the following command:
|
||||
|
||||
```sh
|
||||
ethdo wallet info --wallet=Validators
|
||||
```
|
||||
|
||||
This command will produce output like so:
|
||||
|
||||
```sh
|
||||
Type: non-deterministic
|
||||
Accounts: 0
|
||||
```
|
||||
|
||||
### Generating accounts
|
||||
|
||||
To create two separate accounts with different passphrases, issue the command:
|
||||
```sh
|
||||
ethdo account create --account=Validators/1 --passphrase=validator1secret
|
||||
ethdo account create --account=Validators/2 --passphrase=validator2secret
|
||||
```
|
||||
|
||||
> The two accounts are given different passphrases in the above example. This is not required; all accounts can have the same password if you prefer.
|
||||
|
||||
### Creating a withdrawal wallet and account
|
||||
|
||||
It is recommended to set up separate wallets for withdrawals and validator nodes. This allows users to have a validator wallet actively running on the node, while a second wallet can be kept securely offline in cold storage.
|
||||
|
||||
Creating a withdrawal wallet and account is very similar to the process above to generate the validator wallet. For example:
|
||||
|
||||
```sh
|
||||
ethdo wallet create --wallet=Withdrawal
|
||||
ethdo account create --account=Withdrawal/Primary --passphrase=withdrawalsecret
|
||||
```
|
||||
|
||||
This creates a wallet called "Withdrawal" and within it an account called "Primary". It is also possible to apply additional protection to the Withdrawal wallet if desired; see the `ethdo` documentation for details.
|
||||
|
||||
### Depositing funds for a validator
|
||||
|
||||
The validator now requires deposited funds. If you do not have any Göerli Ether, the best approach is to follow the steps at https://prylabs.net/participate to use the faucet and make a deposit -- **however**, for step 3, do not run the commands provided. Instead, run the following command to generate the deposit data requested:
|
||||
|
||||
```sh
|
||||
ethdo validator depositdata \
|
||||
--validatoraccount=Validators/1 \
|
||||
--withdrawalaccount=Withdrawal/Primary \
|
||||
--depositvalue=32Ether \
|
||||
--passphrase=validator1secret \
|
||||
--raw
|
||||
```
|
||||
|
||||
The raw data output of this command can be pasted in to the webpage above to generate the required transaction for validator 1 (and can be repeated for validator 2, or as many validators as you wish).
|
||||
|
||||
Alternatively, if you have your own Göerli ETH, you can send deposit transactions directly to the Göerli testnet. You can create JSON output containing the deposit data:
|
||||
|
||||
```sh
|
||||
ethdo validator depositdata \
|
||||
--validatoraccount=Validators/1 \
|
||||
--withdrawalaccount=Withdrawal/Primary \
|
||||
--depositvalue=32Ether \
|
||||
--passphrase=validator1secret
|
||||
{"account":"Validators/1","pubkey":"a9ca9cf7fa2d0ab1d5d52d2d8f79f68c50c5296bfce81546c254df68eaac0418717b2f9fc6655cbbddb145daeb282c00","withdrawal_credentials":"0059a28dc2db987d59bdfc4ab20b9ad4c83888bcd32456a629aece07de6895aa","signature":"9335b872253fdab328678bd3636115681d52b42fe826c6acb7f1cd1327c6bba48e3231d054e4f274cc7c1c184f28263b13083e01db8c08c17b59f22277dff341f7c96e7a0407a0a31c8563bcf479d31136c833712ae3bfd93ee9ea6abdfa52d4","value":3200000000,"deposit_data_root":"14278c9345eeeb7b2d5307a36ed1c72eea5ed09a30cf7c47525e34f39f564ef5"}
|
||||
```
|
||||
|
||||
This can be passed to [ethereal](https://github.com/wealdtech/ethereal) to send the deposit (replacing `0x21A1A52aba41DB18F9F1D2625e1b19A251F3e0A9` below with your local Göerli account containing the funds and `eth1secret` with that account's passphrase; `ethereal --network=goerli account list --verbose` will provide details of your local accounts and their current funds).
|
||||
|
||||
on Linux/OSX:
|
||||
|
||||
```sh
|
||||
DEPOSITDATA=`ethdo validator depositdata \
|
||||
--validatoraccount=Validators/1 \
|
||||
--withdrawalaccount=Withdrawal/Primary \
|
||||
--depositvalue=32Ether \
|
||||
--passphrase=validator1secret`
|
||||
ethereal beacon deposit \
|
||||
--network=goerli \
|
||||
--data="${DEPOSITDATA}" \
|
||||
--from=0x21A1A52aba41DB18F9F1D2625e1b19A251F3e0A9 \
|
||||
--passphrase=eth1secret
|
||||
```
|
||||
|
||||
or on Windows:
|
||||
|
||||
```sh
|
||||
ethdo validator depositdata \
|
||||
--validatoraccount=Validators/1 \
|
||||
--withdrawalaccount=Withdrawal/Primary \
|
||||
--depositvalue=32Ether \
|
||||
--passphrase=validator1secret >depositdata.json
|
||||
ethereal beacon deposit \
|
||||
--network=goerli \
|
||||
--data=depositdata.json \
|
||||
--from=0x21A1A52aba41DB18F9F1D2625e1b19A251F3e0A9 \
|
||||
--passphrase=eth1secret
|
||||
erase depositdata.json
|
||||
```
|
||||
|
||||
The `ethereal` command can either take a `passphrase`, if the `from` address is a local account (confirm with `ethereal --network=goerli account list`) or a `privatekey` if not.
|
||||
|
||||
### Validating
|
||||
|
||||
The next step is to start the validator using the validating keys that have been created.
|
||||
|
||||
#### Keymanager options
|
||||
|
||||
Although options for the wallet keymanager can be supplied directly on the command-line this is not considered best practice, as it exposes sensitive information such as passphrases, so it is better to create a file that contains this information and reference that file.
|
||||
|
||||
To create the relevant directory run the following for Linux/OSX:
|
||||
|
||||
```sh
|
||||
mkdir -p ${HOME}/prysm/validator
|
||||
```
|
||||
|
||||
or for Windows:
|
||||
|
||||
```sh
|
||||
mkdir %APPDATA%\prysm\validator
|
||||
```
|
||||
|
||||
|
||||
and then use your favourite text editor to create a file in this directory called `wallet.json` with the following contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
"Validators/1",
|
||||
"Validators/2"
|
||||
],
|
||||
"passphrases": [
|
||||
"validator1secret",
|
||||
"validator2secret"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Starting the validator with Bazel
|
||||
|
||||
To start the validator you must supply the desired keymanager and the location of the keymanager options file. Run the following command for Linux/OSX:
|
||||
|
||||
```sh
|
||||
bazel run //validator:validator -- --keymanager=wallet --keymanageropts=${HOME}/prysm/validator/wallet.json
|
||||
```
|
||||
|
||||
or for Windows:
|
||||
|
||||
```sh
|
||||
bazel run //validator:validator -- --keymanager=wallet --keymanageropts=%APPDATA%\prysm\validator\wallet.json
|
||||
```
|
||||
|
||||
#### Starting the validator with Docker
|
||||
|
||||
Docker will not have direct access to the wallet created above, and requires the keymanager to be informed of the mapped location of the wallet. Edit the `wallet.json` file to include a location entry, as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"location": "/wallets",
|
||||
"accounts": [
|
||||
"Validators/1",
|
||||
"Validators/2"
|
||||
],
|
||||
"passphrases": [
|
||||
"validator1secret",
|
||||
"validator2secret"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then run the validator by issuing the following command on Linux:
|
||||
|
||||
```sh
|
||||
docker run -v "${HOME}/prysm/validator:/data" \
|
||||
-v "${HOME}/.config/ethereum2/wallets:/wallets" \
|
||||
gcr.io/prysmaticlabs/prysm/validator:latest \
|
||||
--keymanager=wallet \
|
||||
--keymanageropts=/data/wallet.json
|
||||
```
|
||||
|
||||
or for OSX:
|
||||
|
||||
```sh
|
||||
docker run -v "${HOME}/prysm/validator:/data" \
|
||||
-v "${HOME}/Library/Application Support/ethereum2/wallets:/wallets" \
|
||||
gcr.io/prysmaticlabs/prysm/validator:latest \
|
||||
--keymanager=wallet \
|
||||
--keymanageropts=/data/wallet.json
|
||||
```
|
||||
|
||||
or for Windows:
|
||||
|
||||
```sh
|
||||
docker run -v %APPDATA%\prysm\validator:/data" \
|
||||
-v %APPDATA%\ethereum2\wallets:/wallets" \
|
||||
gcr.io/prysmaticlabs/prysm/validator:latest \
|
||||
--keymanager=wallet \
|
||||
--keymanageropts=/data/wallet.json
|
||||
```
|
||||
|
||||
#### Confirming validation
|
||||
|
||||
When the validator is operational, you should see output similar to:
|
||||
|
||||
```text
|
||||
[2020-02-07 10:00:59] INFO node: Validating for public key pubKey=0x85016bd4ca67e57e1438308fdb3d98b74b81428fb09e6d16d2dcbc72f240be090d5faebb63f84d6f35a950fdbb36f910
|
||||
[2020-02-07 10:00:59] INFO node: Validating for public key pubKey=0x8de04b4cd3f0947f4e76fa2f86fa1cfd33cc2500688f2757e406448c36f0f1255758874b46d72002ad206ed560975d39
|
||||
```
|
||||
|
||||
where each line states a public key that is being used for validating. Confirm that these values match your expectations.
|
||||
39
docs/troubleshooting.md
Normal file
39
docs/troubleshooting.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Compilation problems on Linux
|
||||
|
||||
### gcc not found
|
||||
### cannot find -lstdc++
|
||||
|
||||
If you receive errors of this type your computer is missing some files to allow `ethdo` to build. To resolve this, run the following command:
|
||||
|
||||
```sh
|
||||
sudo apt install build-essential libstdc++6
|
||||
```
|
||||
|
||||
and then try to install `ethdo` again.
|
||||
|
||||
## Compilation problems on Windows
|
||||
|
||||
### gcc not found
|
||||
|
||||
If you receive errors of this type your computer is missing some files to allow `ethdo` to build. To resolve this install gcc by following the instructions at http://mingw-w64.org/doku.php
|
||||
|
||||
## ethdo not found after installing
|
||||
|
||||
This is usually due to an incorrectly set path. Go installs its binaries (such as `ethdo`) in a particular location. The defaults are:
|
||||
|
||||
- Linux, Mac: `$HOME/go/bin`
|
||||
- Windows: `%USERPROFILE%\go\bin`
|
||||
|
||||
You must add these paths to be able to access `ethdo`. To add the path on linux or OSX type:
|
||||
|
||||
```sh
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
```
|
||||
|
||||
and on Windows type:
|
||||
|
||||
```sh
|
||||
setx /M path "%PATH%;%USERPROFILE%\go\bin"
|
||||
```
|
||||
462
docs/usage.md
Normal file
462
docs/usage.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# ethdo commands
|
||||
|
||||
ethdo provides features to manage wallets and accounts, as well as interacting with Ethereum 2 nodes and remote signers. Below are a list of all available commands.
|
||||
|
||||
Note that the below provides a list of commands rather than a howto guide. Please follow the
|
||||
|
||||
### `wallet` commands
|
||||
|
||||
#### `accounts`
|
||||
|
||||
`ethdo wallet accounts` lists the accounts within a wallet.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet accounts --wallet="Personal wallet"
|
||||
Auctions
|
||||
Operations
|
||||
Spending
|
||||
```
|
||||
|
||||
With the `--verbose` flag this will provide the public key of the accounts.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet accounts --wallet="Personal wallet" --verbose
|
||||
Auctions: 0x812f340269c315c1d882ae7c13cdaddf862dbdbd482b1836798b2070160dd1e194088cc6f39347782028d1e56bd18674
|
||||
Operations: 0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670
|
||||
Spending: 0x85dfc6dcee4c9da36f6473ec02fda283d6c920c641fc8e3a76113c5c227d4aeeb100efcfec977b12d20d571907d05650
|
||||
```
|
||||
#### `create`
|
||||
|
||||
`ethdo wallet create` creates a new wallet with the given parameters. Options for creating a wallet include:
|
||||
- `wallet`: the name of the wallet to create
|
||||
- `type`: the type of wallet to create. This can be either "nd" for a non-deterministic wallet, where private keys are generated randomly, or "hd" for a hierarchical deterministic wallet, where private keys are generated from a seed and path as per [ERC-2333](https://github.com/CarlBeek/EIPs/blob/bls_path/EIPS/eip-2334.md) (defaults to "nd")
|
||||
- `walletpassphrase`: the passphrase for of the wallet. This is required for hierarchical deterministic wallets, to protect the seed
|
||||
- `mnemonic`: for hierarchical deterministic wallets only, use a pre-defined 24-word [BIP-39 seed phrase](https://en.bitcoin.it/wiki/Seed_phrase) to create the wallet, along with an additional "seed extension" phrase if required. **Warning** The same mnemonic can be used to create multiple wallets, in which case they will generate the same keys.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet create --wallet="Personal wallet" --type="hd" --walletpassphrase="my wallet secret"
|
||||
```
|
||||
|
||||
#### `delete`
|
||||
`ethdo wallet delete` deletes a wallet. Options for deleting a wallet include:
|
||||
- `wallet`: the name of the wallet to delete
|
||||
|
||||
```sh
|
||||
$ ethdo wallet delete --wallet="Old wallet"
|
||||
```
|
||||
|
||||
**Warning** Deleting a wallet is permanent. Only use this command if you really don't want the wallet, or you have securely backed the wallet up using `wallet export`.
|
||||
|
||||
#### `export`
|
||||
|
||||
`ethdo wallet export` exports the wallet and all of its accounts. Options for exporting a wallet include:
|
||||
- `wallet`: the name of the wallet to export (defaults to "primary")
|
||||
- `exportpassphrase`: the passphrase with which to encrypt the wallet backup
|
||||
|
||||
```sh
|
||||
$ ethdo wallet export --wallet="Personal wallet" --exportpassphrase="my export secret"
|
||||
0x01c7a27ad40d45b4ae5be5f...
|
||||
```
|
||||
|
||||
The encrypted wallet export is written to the console; it can be redirected to store it in a file.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet export --wallet="Personal wallet" --exportpassphrase="my export secret" >export.dat
|
||||
```
|
||||
|
||||
#### `import`
|
||||
|
||||
`ethdo wallet import` imports a wallet and all of its accounts exported by `ethdo wallet export`. Options for importing a wallet include:
|
||||
- `importdata`: the data exported by `ethdo wallet export`
|
||||
- `importpassphrase`: the passphrase that was provided to `ethdo wallet export` to encrypt the data
|
||||
- `verify`: confirm information about the wallet import without importing it
|
||||
|
||||
```sh
|
||||
$ ethdo wallet import --importdata="0x01c7a27ad40d45b4ae5be5f..." --importpassphrase="my export secret"
|
||||
```
|
||||
|
||||
The encrypted wallet export can be read from a file. For example with Unix systems:
|
||||
|
||||
```sh
|
||||
$ ethdo wallet import --importdata=`cat export.dat` --importpassphrase="my export secret"
|
||||
```
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo wallet info` provides information about a given wallet. Options include:
|
||||
- `wallet`: the name of the wallet
|
||||
|
||||
```sh
|
||||
$ ethdo wallet info --wallet="Personal wallet"
|
||||
Type: hierarchical deterministic
|
||||
Accounts: 3
|
||||
```
|
||||
|
||||
#### `list`
|
||||
|
||||
`ethdo wallet list` lists all wallets in the store.
|
||||
|
||||
```sh
|
||||
$ ethdo wallet list
|
||||
Personal wallet
|
||||
```
|
||||
|
||||
**N.B.** encrypted wallets will not show up in this list unless the correct passphrase for the store is supplied.
|
||||
|
||||
### `account` commands
|
||||
|
||||
Account commands focus on information about local accounts, generally those used by Geth and Parity but also those from hardware devices.
|
||||
|
||||
#### `create`
|
||||
|
||||
`ethdo account create` creates a new account with the given parameters. Options for creating an account include:
|
||||
- `account`: the name of the account to create
|
||||
- `passphrase`: the passphrase for the account
|
||||
- `path`: the HD path for the account (only for hierarchical deterministic accounts)
|
||||
|
||||
Note that for hierarchical deterministic wallets you will also need to supply `--walletpassphrase` to unlock the wallet seed.
|
||||
|
||||
For distributed accounts you will also need to supply `--participants` and `--signing-threshold`.
|
||||
|
||||
```sh
|
||||
$ ethdo account create --account="Personal wallet/Operations" --walletpassphrase="my wallet secret" --passphrase="my account secret"
|
||||
```
|
||||
#### `import`
|
||||
|
||||
`ethdo account import` creates a new account by importing its private key. Options for creating the account include:
|
||||
- `account`: the name of the account to create
|
||||
- `passphrase`: the passphrase for the account
|
||||
- `key`: the private key to import
|
||||
|
||||
```sh
|
||||
$ ethdo account import --account=Validators/123 --key=6dd12d588d1c05ba40e80880ac7e894aa20babdbf16da52eae26b3f267d68032 --passphrase="my account secret"
|
||||
```
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo account info` provides information about the given account. Options include:
|
||||
- `account`: the name of the account on which to obtain information
|
||||
|
||||
```sh
|
||||
$ ethdo account info --account="Personal wallet/Operations"
|
||||
Public key: 0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670
|
||||
```
|
||||
|
||||
#### `key`
|
||||
|
||||
`ethdo account key` provides the private key for an account. Options include:
|
||||
- `account`: the name of the account on which to obtain information
|
||||
- `passphrase`: the passphrase for the account
|
||||
|
||||
```sh
|
||||
$ ethdo account key --account=interop/00001 --passphrase=secret
|
||||
0x51d0b65185db6989ab0b560d6deed19c7ead0e24b9b6372cbecb1f26bdfad000
|
||||
```
|
||||
|
||||
#### `lock`
|
||||
|
||||
`ethdo account lock` manually locks an account on a remote signer. Locked accounts cannot carry out signing requests. Options include:
|
||||
- `account`: the name of the account to lock
|
||||
|
||||
Note that this command only works with remote signers; it has no effect on local accounts.
|
||||
|
||||
```sh
|
||||
$ ethdo account lock --account=Validators/123
|
||||
```
|
||||
|
||||
#### `unlock`
|
||||
|
||||
`ethdo account unlock` manually unlocks an account on a remote signer. Unlocked accounts cannot carry out signing requests. Options include:
|
||||
- `account`: the name of the account to unlock
|
||||
- `passphrase`: the passphrase for the account
|
||||
|
||||
Note that this command only works with remote signers; it has no effect on local accounts.
|
||||
|
||||
```sh
|
||||
$ ethdo account unlock --account=Validators/123 --passphrase="my secret passphrase"
|
||||
```
|
||||
|
||||
### `signature` commands
|
||||
|
||||
Signature commands focus on generation and verification of data signatures.
|
||||
|
||||
#### `signature sign`
|
||||
|
||||
`ethdo signature sign` signs provided data. Options include:
|
||||
- `data`: the data to sign, as a hex string
|
||||
- `domain`: the domain in which to sign the data. This is a 32-byte hex string
|
||||
- `account`: the account to sign the data
|
||||
- `passphrase`: the passphrase for the account
|
||||
|
||||
```sh
|
||||
$ ethdo signature sign --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --account="Personal wallet/Operations" --passphrase="my account secret"
|
||||
0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130
|
||||
```
|
||||
|
||||
#### `signature verify`
|
||||
|
||||
`ethdo signature verify` verifies signed data. Options include:
|
||||
- `data`: the data whose signature to verify, as a hex string
|
||||
- `signature`: the signature to verify, as a hex string
|
||||
- `account`: the account which signed the data (if available as an account)
|
||||
- `signer`: the public key of the account which signed the data (if not available as an account)
|
||||
|
||||
```sh
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --account="Personal wallet/Operations"
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --account="Personal wallet/Auctions"
|
||||
Not verified
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b" --signer="0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670"
|
||||
$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --signer="0xad1868210a0cff7aff22633c003c503d4c199c8dcca13bba5b3232fc784d39d3855936e94ce184c3ce27bf15d4347695" --verbose
|
||||
Verified
|
||||
```
|
||||
|
||||
The same rules apply to `ethereal signature verify` as those in `ethereal signature sign` above.
|
||||
|
||||
### `version`
|
||||
|
||||
`ethdo version` provides the current version of ethdo. For example:
|
||||
|
||||
```sh
|
||||
$ ethdo version
|
||||
1.4.0
|
||||
```
|
||||
|
||||
### `block` commands
|
||||
|
||||
Block commands focus on providing information about Ethereum 2 blocks.
|
||||
#### `info`
|
||||
|
||||
`ethdo block info` obtains information about a block in Ethereum 2. Options include:
|
||||
- `slot`: the slot at which to attempt to fetch the block
|
||||
|
||||
```sh
|
||||
$ ethdo block info --slot=80
|
||||
Attestations: 1
|
||||
Attester slashings: 0
|
||||
Deposits: 0
|
||||
Voluntary exits: 0
|
||||
```
|
||||
|
||||
Additional information is supplied when using `--verbose`
|
||||
|
||||
```sh
|
||||
$ ethdo block info --slot=80 --verbose
|
||||
Parent root: 0x9a08aab7d5bbc816a9d2c20c79895519da2045e99ac6782ab3d05323a395fe51
|
||||
State root: 0xc6a2626ba5cb37f984bdc4da4dc93a5012be5b69fdcebc50be70a1181a290265
|
||||
Ethereum 1 deposit count: 512
|
||||
Ethereum 1 deposit root: 0x05b88acdde2092e1ecf35714dca0ccf82fb7e73180643f51d3139553136d125f
|
||||
Ethereum 1 block hash: 0x2b8d87e016376d83b2c04c1e626172a3f8bef3b4a37d7f2f3f76d0c62acdf573
|
||||
Attestations: 1
|
||||
0:
|
||||
Committee index: 0
|
||||
Attesters: 17
|
||||
Aggregation bits: ✓✓✓✓✓✓✓✓ ✓✓✓✓✓✓✓✓ ✕✕✕✕✕✕✕✓
|
||||
Slot: 79
|
||||
Beacon block root: 0x9a08aab7d5bbc816a9d2c20c79895519da2045e99ac6782ab3d05323a395fe51
|
||||
Source epoch: 0
|
||||
Source root: 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
Target epoch: 2
|
||||
Target root: 0xb93273c516fc817e64fab53ff4093f295e5da463582e85e1ca60800e9464faf2
|
||||
Attester slashings: 0
|
||||
Deposits: 0
|
||||
Voluntary exits: 0
|
||||
```
|
||||
|
||||
### `chain` commands
|
||||
|
||||
Chain commands focus on providing information about Ethereum 2 chains.
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo chain info` obtains information about an Ethereum 2 chain.
|
||||
|
||||
```sh
|
||||
$ ethdo chain info
|
||||
Genesis time: Thu Apr 16 08:02:43 BST 2020
|
||||
```
|
||||
|
||||
Additional information is supplied when using `--verbose`
|
||||
|
||||
```sh
|
||||
$ ethdo chain info --verbose
|
||||
Genesis time: Thu Apr 16 08:02:43 BST 2020
|
||||
Genesis fork version: 00000000
|
||||
Seconds per slot: 12
|
||||
Slots per epoch: 32
|
||||
```
|
||||
|
||||
#### `status`
|
||||
|
||||
`ethdo chain status` obtains the status of an Ethereum 2 chain from the node's point of view. Options include:
|
||||
- `slot` show output in terms of slots rather than epochs
|
||||
|
||||
```sh
|
||||
$ ethdo chain status
|
||||
Current epoch: 5
|
||||
Justified epoch: 4
|
||||
Finalized epoch: 3
|
||||
```
|
||||
|
||||
Additional information is supplied when using `--verbose`
|
||||
|
||||
```sh
|
||||
$ ethdo chain status --verbose
|
||||
Current epoch: 5
|
||||
Justified epoch: 4
|
||||
Justified epoch distance 1
|
||||
Finalized epoch: 3
|
||||
Finalized epoch distance: 2
|
||||
Prior justified epoch: 3
|
||||
Prior justified epoch distance: 4
|
||||
```
|
||||
|
||||
### `deposit` comands
|
||||
|
||||
Deposit commands focus on information about deposit data information in a JSON file generated by the `ethdo validator depositdata` command.
|
||||
|
||||
#### `verify`
|
||||
|
||||
`ethdo deposit verify` verifies one or more deposit data information in a JSON file generated by the `ethdo validator depositdata` command. Options include:
|
||||
- `data`: either a path to the JSON file, the JSON itself, or a hex string representing a deposit transaction
|
||||
- `withdrawalpubkey`: the public key of the withdrawal for the deposit. If no value is supplied then withdrawal credentials for deposits will not be checked
|
||||
- `validatorpubkey`: the public key of the validator for the deposit. If no value is supplied then validator public keys will not be checked
|
||||
- `depositvalue`: the value of the Ether being deposited. If no value is supplied then deposit values will not be checked.
|
||||
|
||||
```sh
|
||||
$ ethdo deposit verify --data=${HOME}/depositdata.json --withdrawalpubkey=0xad1868210a0cff7aff22633c003c503d4c199c8dcca13bba5b3232fc784d39d3855936e94ce184c3ce27bf15d4347695 --validatorpubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c --depositvalue=32Ether
|
||||
```
|
||||
|
||||
### `exit` comands
|
||||
|
||||
Exit commands focus on information about validator exits generated by the `ethdo validator exit` command.
|
||||
|
||||
#### `verify`
|
||||
|
||||
`ethdo exit verify` verifies the validator exit information in a JSON file generated by the `ethdo validator exit` command. Options include:
|
||||
- `data`: either a path to the JSON file or the JSON itself
|
||||
- `account`: the account that generated the exit transaction (if available as an account)
|
||||
- `pubkey`: the public key of the account that generated the exit transaction
|
||||
|
||||
```sh
|
||||
$ ethdo exit verify --data=${HOME}/exit.json --pubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c
|
||||
```
|
||||
|
||||
### `node` commands
|
||||
|
||||
Node commands focus on information from an Ethereum 2 node.
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo node info` obtains the information about an Ethereum 2 node.
|
||||
|
||||
```sh
|
||||
$ ethdo node info
|
||||
Syncing: false
|
||||
Current slot: 178
|
||||
Current epoch: 5
|
||||
```
|
||||
|
||||
Additional information is supplied when using `--verbose`
|
||||
|
||||
```sh
|
||||
$ ethdo node info --verbose
|
||||
Version: Prysm/Git commit: b0aa6e22455e4d9cb8720a259771fbbbd22dc3ec. Built at: 2020-04-16T08:02:43+01:00
|
||||
Syncing: false
|
||||
Current slot: 178
|
||||
Current epoch: 5
|
||||
Genesis timestamp: 1587020563
|
||||
```
|
||||
|
||||
### `validator` commands
|
||||
|
||||
Validator commands focus on interaction with Ethereum 2 validators.
|
||||
|
||||
#### `depositdata`
|
||||
|
||||
`ethdo validator depositdata` generates the data required to deposit one or more Ethereum 2 validators. Options include:
|
||||
- `withdrawalaccount` specify the account to be used for the withdrawal credentials (if withdrawalpubkey is not supplied)
|
||||
- `withdrawalpubkey` specify the public key to be used for the withdrawal credentials (if withdrawalaccount is not supplied)
|
||||
- `validatoraccount` specify the account to be used for the validator
|
||||
- `depositvalue` specify the amount of the deposit
|
||||
- `forkversion` specify the fork version for the deposit signature; this should not be included unless the deposit is being generated offline. Note that supplying an incorrect value could result in the loss of your deposit, so only supply this value if you are sure you know what you are doing
|
||||
- `raw` generate raw hex output that can be supplied as the data to an Ethereum 1 deposit transaction
|
||||
|
||||
#### `exit`
|
||||
|
||||
`ethdo validator exit` sends a transaction to the chain to tell an active validator to exit the validation queue. Options include:
|
||||
- `epoch` specify an epoch before which this exit is not valid
|
||||
- `json-output` generate JSON output rather than sending a transaction immediately
|
||||
- `json` use JSON input created by the `--json-output` option rather than generate data from scratch
|
||||
- `forkversion` specify a specific fork version; default is to fetch it from the chain but this can be used when generating offline deposits
|
||||
|
||||
```sh
|
||||
$ ethdo validator exit --account=Validators/1 --passphrase="my validator secret"
|
||||
```
|
||||
|
||||
To send a transaction when the account is not accessible to ethdo accout you can use the validator's private key instead:
|
||||
|
||||
```sh
|
||||
$ ethdo validator exit --key=0x01e748d098d3bcb477d636f19d510399ae18205fadf9814ee67052f88c1f88c0
|
||||
```
|
||||
|
||||
#### `info`
|
||||
|
||||
`ethdo validator info` provides information for a given validator.
|
||||
|
||||
```sh
|
||||
$ ethdo validator info --account=Validators/1
|
||||
Status: Active
|
||||
Balance: 3.203823585 Ether
|
||||
Effective balance: 3.1 Ether
|
||||
```
|
||||
|
||||
Additional information is supplied when using `--verbose`
|
||||
|
||||
```sh
|
||||
$ ethdo validator info --account=Validators/1 --verbose
|
||||
Epoch of data: 3398
|
||||
Index: 26913
|
||||
Public key: 0xb3bb6b7a8d809e59544472853d219499765bf01d14de1e0549bd6fc2a86627ac9033264c84cd503b6339e3334726562f
|
||||
Status: Active
|
||||
Balance: 3.204026813 Ether
|
||||
Effective balance: 3.1 Ether
|
||||
Withdrawal credentials: 0x0033ef3cb10b36d0771ffe8a02bc5bfc7e64ea2f398ce77e25bb78989edbee36
|
||||
```
|
||||
|
||||
If the validator is not an account it can be queried directly with `--pubkey`.
|
||||
|
||||
```sh
|
||||
$ ethdo validator info --pubkey=0x842dd66cfeaeff4397fc7c94f7350d2131ca0c4ad14ff727963be9a1edb4526604970df6010c3da6474a9820fa81642b
|
||||
Status: Active
|
||||
Balance: 3.201850307 Ether
|
||||
Effective balance: 3.1 Ether
|
||||
```
|
||||
|
||||
### `attester` commands
|
||||
|
||||
Attester commands focus on Ethereum 2 validators' actions as attesters.
|
||||
|
||||
#### `inclusion`
|
||||
|
||||
`ethdo attester inclusion` finds the block with wihch an attestation is included on the chain. Options include:
|
||||
- `epoch` the epoch in which to obtain the inclusion information (defaults to current epoch)
|
||||
- `account` the account for which to fetch the inclusion information
|
||||
- `pubkey` the public key for which to fetch the inclusion information
|
||||
|
||||
```sh
|
||||
$ ethdo attester inclusion --account=Validators/1 --epoch=6484
|
||||
Attestation included in block 207492 (inclusion delay 1)
|
||||
```
|
||||
|
||||
## Maintainers
|
||||
|
||||
Jim McDonald: [@mcdee](https://github.com/mcdee).
|
||||
|
||||
## Contribute
|
||||
|
||||
Contributions welcome. Please check out [the issues](https://github.com/wealdtech/ethdo/issues).
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](LICENSE) © 2019, 2020 Weald Technology Trading Ltd
|
||||
|
||||
70
go.mod
70
go.mod
@@ -3,39 +3,49 @@ module github.com/wealdtech/ethdo
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/FactomProject/go-bip39 v0.3.5
|
||||
github.com/dgraph-io/ristretto v0.0.1 // indirect
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.2 // indirect
|
||||
github.com/minio/highwayhash v1.0.0 // indirect
|
||||
github.com/minio/sha256-simd v0.1.1 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/aws/aws-sdk-go v1.34.9 // indirect
|
||||
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.7 // indirect
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200722032157-41fc56eba7b4
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/protolambda/zssz v0.1.4 // indirect
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200207140607-467d1b67d8d0
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52 // indirect
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200101200214-e24db4d9e963
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.3.4 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.2
|
||||
github.com/wealdtech/go-bytesutil v1.1.0
|
||||
github.com/wealdtech/go-eth2-types v1.0.0
|
||||
github.com/wealdtech/go-eth2-util v1.1.0
|
||||
github.com/wealdtech/go-eth2-wallet v1.8.1
|
||||
github.com/wealdtech/go-eth2-wallet-hd v1.10.0 // indirect
|
||||
github.com/wealdtech/go-eth2-wallet-nd v1.8.0 // indirect
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.7.0 // indirect
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.6.0 // indirect
|
||||
github.com/wealdtech/go-eth2-wallet-types v1.10.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/tyler-smith/go-bip39 v1.0.2
|
||||
github.com/wealdtech/eth2-signer-api v1.5.2
|
||||
github.com/wealdtech/go-bytesutil v1.1.1
|
||||
github.com/wealdtech/go-ecodec v1.1.0
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.0
|
||||
github.com/wealdtech/go-eth2-util v1.6.0
|
||||
github.com/wealdtech/go-eth2-wallet v1.14.0
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.3
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.0
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.0
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.5.0
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.1
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.0
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0
|
||||
github.com/wealdtech/go-string2eth v1.1.0
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6 // indirect
|
||||
google.golang.org/grpc v1.27.1
|
||||
gopkg.in/ini.v1 v1.52.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
|
||||
golang.org/x/sys v0.0.0-20200821140526-fda516888d29 // indirect
|
||||
golang.org/x/text v0.3.3
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 // indirect
|
||||
google.golang.org/grpc v1.31.0
|
||||
gopkg.in/ini.v1 v1.60.1 // indirect
|
||||
)
|
||||
|
||||
575
go.sum
575
go.sum
@@ -1,95 +1,194 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/FactomProject/go-bip39 v0.3.5 h1:l9g92TeqCkC5NZhm72igTpf5yaYDp3Sy4CvnPYknp6U=
|
||||
github.com/FactomProject/go-bip39 v0.3.5/go.mod h1:ygPVOtW424QxnJMze9XYDeh4wT19V3iVDOqVUl/USkE=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.28.0 h1:NkmnHFVEMTRYTleRLm5xUaL1mHKKkYQl4rCd+jzD58c=
|
||||
github.com/aws/aws-sdk-go v1.28.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.28.1 h1:aWBD5EJrmGFuHFn9ZdaHqWWZGZYQ5Gzb3j9G0RppLpY=
|
||||
github.com/aws/aws-sdk-go v1.28.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.28.13 h1:JyCQQ86yil3hg7MtWdNH8Pbcgx92qlUV2v22Km63Mf4=
|
||||
github.com/aws/aws-sdk-go v1.28.13/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go v1.32.6 h1:HoswAabUWgnrUF7X/9dr4WRgrr8DyscxXvTDm7Qw/5c=
|
||||
github.com/aws/aws-sdk-go v1.32.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.5 h1:p2fr1ryvNTU6avUWLI+/H7FGv0TBIjzVM5WDgXBBv4U=
|
||||
github.com/aws/aws-sdk-go v1.33.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.33.11 h1:A7b3mNKbh/0zrhnNN/KxWD0YZJw2RImnjFXWOquYKB4=
|
||||
github.com/aws/aws-sdk-go v1.33.11/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.34.5 h1:FwubVVX9u+kW9qDCjVzyWOdsL+W5wPq683wMk2R2GXk=
|
||||
github.com/aws/aws-sdk-go v1.34.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.34.9 h1:cUGBW9CVdi0mS7K1hDzxIqTpfeWhpoQiguq81M1tjK0=
|
||||
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/ristretto v0.0.1 h1:cJwdnj42uV8Jg4+KLrYovLiCgIfz9wtWm6E6KA+1tLs=
|
||||
github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE=
|
||||
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
|
||||
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlespiau/covertool v0.0.0-20180314162135-b0c4c6d0583a/go.mod h1:/eQMcW3eA1bzKx23ZYI2H3tXPdJB5JWYTHzoUPBvQY4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200514094935-99fccaf93472 h1:maoKvILdMk6CSWHanFcUdxXIZGKD9YpWIaVbUQ/4kfg=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200514094935-99fccaf93472/go.mod h1:LlFXPmgrgVYsuoFDwV8rDJ9tvt1pLQdjKvU1b5IRES0=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200803113354-a18be873a4b6 h1:P440pnxSHIQI8gg7lQNNo+/F88EzUzS3Os0Xkhta7OQ=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200803113354-a18be873a4b6/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30 h1:Sbm5yhPObRc+RCZa9fAR/Px/j1qYwuqawF0X8dR2gAw=
|
||||
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/pprof v0.0.0-20190309163659-77426154d546/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.2 h1:D0EVSTwQoQOyfY35QNSuPJA4jpZRtkoGYWQMB7XNg5o=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6 h1:8ERzHx8aj1Sc47mu9n/AksaKCSWrMchFtkdrS4BIj5o=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.7 h1:Nk5kuHrnWUTf/0GL1a/vchH/om9Ap2/HnVna+jYZgTY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.7/go.mod h1:oYZKL012gGh6LMyg/xA7Q2yq6j8bu0wa+9w14EEthWU=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200621110855-298ffb6847bc h1:1ANh6XSZu8Quo4d03TDFhqiFjgUicMdavTtPm6B+RfE=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200621110855-298ffb6847bc/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200624084043-9b7da5962ccb h1:rVlcEzuK/AJKzJp890JoSpJdU3hgak53oMB9mypa05s=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200624084043-9b7da5962ccb/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200703070911-61704dac4ad1 h1:da7FT3Bhp8GCx6TwDPQio/7GLRLR5hcZvoRb89hfc08=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200703070911-61704dac4ad1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200706085701-832d8c2c0f7d h1:P8yaFmLwc5ZlUx2sHuawcdQvpv5/0GM+WEGJ07ljN3g=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200706085701-832d8c2c0f7d/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200719025738-3e30d132e8f6 h1:xOOHoKwCj0WXm60FqRxQ0u8cLr+kq5DJUlPspEPsu/s=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200719025738-3e30d132e8f6/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200721081051-e31ced8c0204 h1:owpf19DI+vvpSOetnfE1NsLykSxZt8dSag7SDsJd8F0=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200721081051-e31ced8c0204/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200722032157-41fc56eba7b4 h1:TfBVK1MJ9vhrMXWVHu5p/MlVHZTeCGgDAEu5RykVZeI=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20200722032157-41fc56eba7b4/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -99,38 +198,45 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA=
|
||||
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mmcloughlin/avo v0.0.0-20190318053554-7a0eb66183da/go.mod h1:lf5GMZxA5kz8dnCweJuER5Rmbx6dDu6qvw0fO3uYKK8=
|
||||
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/phoreproject/bls v0.0.0-20190821133044-da95d4798b09 h1:f0WZnMl5hMHNpfUPR+klp00ZaIL1dLPZigpJUWupreI=
|
||||
github.com/phoreproject/bls v0.0.0-20190821133044-da95d4798b09/go.mod h1:7pK0Ldy91shCmI47LLTn3i3rfTQcHiJJvPqGqzvN5nE=
|
||||
github.com/phoreproject/bls v0.0.0-20191113194321-fef763a1a842 h1:SFS72hX189sQC+kZzAcNv7ENTLJHddmokEM39RsLA24=
|
||||
github.com/phoreproject/bls v0.0.0-20191113194321-fef763a1a842/go.mod h1:xHJKf2TLXUA39Dhv8k5QmQOxLsbrb1KeTS/3ERfLeqc=
|
||||
github.com/phoreproject/bls v0.0.0-20191211001008-9d5f85bf4a9b h1:tE/F54uL3jp0ZGSKNMPGCTF003pSmtD/sQojpKADAxY=
|
||||
github.com/phoreproject/bls v0.0.0-20191211001008-9d5f85bf4a9b/go.mod h1:xHJKf2TLXUA39Dhv8k5QmQOxLsbrb1KeTS/3ERfLeqc=
|
||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.0 h1:J8lpUdobwIeCI7OiSxHqEwJUKvJwicL5+3v1oe2Yb4k=
|
||||
github.com/pkg/errors v0.9.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -141,95 +247,174 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/protolambda/zssz v0.1.4 h1:4jkt8sqwhOVR8B1JebREU/gVX0Ply4GypsV8+RWrDuw=
|
||||
github.com/protolambda/zssz v0.1.4/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200207140607-467d1b67d8d0 h1:kW97QgwiMpzsnQbFj9Y/jeGbD9gKtxKLktEzIqzkaNQ=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200207140607-467d1b67d8d0/go.mod h1:5OkRN6UmvgtP+kIewitcEKC7S5KOzLOGtya/Tz+HBns=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52 h1:kxZ+xSWX0qbxoiDXQBLztKeEmEQg6TgCYWAOa7gSGGU=
|
||||
github.com/protolambda/zssz v0.1.3/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
|
||||
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
|
||||
github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200619200018-174e3b90d786 h1:bJiOTV2sYykacsxViyRltztQY0DyjT/uFoVRZkEaxsY=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200619200018-174e3b90d786/go.mod h1:rs05kpTfWKl0KflsBWzBQFstoyPFMTWQTbxSAyGHe78=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200709024211-e8095222f77b h1:GjYix8Y4VpQhlsjA2ickr3HxjIns4bI36zOmC+lwaNw=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200709024211-e8095222f77b/go.mod h1:rs05kpTfWKl0KflsBWzBQFstoyPFMTWQTbxSAyGHe78=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3 h1:0f++UXRfp4/Mrmlfj3UaCnYj2lPr6El0gWWTBb9MD2Y=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200101200214-e24db4d9e963 h1:Th5ufPIaL5s/7i3gXHTgiTwfsUhWDP/PwFRiI6qV6v0=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200322041314-62c2aee71669/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65 h1:hJfAWrlxx7SKpn4S/h2JGl2HHwA1a2wSS3HAzzZ0F+U=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200101200214-e24db4d9e963/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae h1:7qd0Af1ozWKBU3c93YW2RH+/09hJns9+ftqWUZyts9c=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
||||
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk=
|
||||
github.com/spf13/afero v1.3.0/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
|
||||
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc=
|
||||
github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
|
||||
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8=
|
||||
github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/wealdtech/go-bytesutil v1.0.0/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s=
|
||||
github.com/wealdtech/go-bytesutil v1.0.1 h1:6xzMM+VEHf5WNh1PsIFcRwScgcno+CP8Rw1rGvT6Cew=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.0 h1:XkaEk7Y0vFbqUstHcCjVGssoqJy4zYnBcF76em1oWX8=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.0/go.mod h1:5wlLQ7NO7nbXo3znJOwIWHN8S4C3xHcZ0uOg9Ue4mvg=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.1 h1:RQb1xyZEcHGpVFNpTEKzLbGH/H04Ajb6y99b2sd8gVs=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.1/go.mod h1:5wlLQ7NO7nbXo3znJOwIWHN8S4C3xHcZ0uOg9Ue4mvg=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.2 h1:3jw8MW0r7KlX9bme0q6j+QMa8osRhEnKLkgkECH6xcU=
|
||||
github.com/wealdtech/eth2-signer-api v1.5.2/go.mod h1:5wlLQ7NO7nbXo3znJOwIWHN8S4C3xHcZ0uOg9Ue4mvg=
|
||||
github.com/wealdtech/go-bytesutil v1.0.1/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s=
|
||||
github.com/wealdtech/go-bytesutil v1.1.0 h1:6XrN7OIQhhBjQy/PZ1HZ3ySE8v8UDyxzERkOgmsIc1g=
|
||||
github.com/wealdtech/go-bytesutil v1.1.0/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s=
|
||||
github.com/wealdtech/go-bytesutil v1.1.1 h1:ocEg3Ke2GkZ4vQw5lp46rmO+pfqCCTgq35gqOy8JKVc=
|
||||
github.com/wealdtech/go-bytesutil v1.1.1/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s=
|
||||
github.com/wealdtech/go-ecodec v1.1.0 h1:yggrTSckcPJRaxxOxQF7FPm21kgE8WA6+f5jdq5Kr8o=
|
||||
github.com/wealdtech/go-ecodec v1.1.0/go.mod h1:PSdBFEB6cltdT7V4E1jbboufMZTZXcQOKG/2PeEjKK4=
|
||||
github.com/wealdtech/go-eth2-types v1.0.0 h1:ggrbQ5HeFcxVm20zxVWr8Sc3uCditaetzWB/Ax/4g0w=
|
||||
github.com/wealdtech/go-eth2-types v1.0.0/go.mod h1:fWUgtKQ7hiNVl6263bGeyjlydYuaxkxcUIPIopgz2CM=
|
||||
github.com/wealdtech/go-eth2-util v1.1.0 h1:bDgNEtH5LoxI1JaBPUgHoPKmfPwtGuLw487U7AoMO6E=
|
||||
github.com/wealdtech/go-eth2-util v1.1.0/go.mod h1:mX11133nOroRPHp3qXyAqT5iLmsZXQmpH2DZWWeFWP8=
|
||||
github.com/wealdtech/go-eth2-wallet v1.8.1 h1:w5fD2c0Q9WSYbTF7XpdBsiTcPUHfLlU1blAhH8ROffY=
|
||||
github.com/wealdtech/go-eth2-wallet v1.8.1/go.mod h1:Kvzo9EdxUvibt/KarimGTOHY3pEcQTyIV1A+XDmei48=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.4.2 h1:EkOvP8Ma0Ru7WIh0haoST97rc0PYm2AJpuWG1HzgfCI=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.4.2/go.mod h1:hhKa4ZFaNU2fwUjEh8GYr8wKg5D1W4QyxZ3xpsb/2hw=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.4.3 h1:VFYVYw9J2P/HFXi7T9HX7vzM+xx6BZ4od5PlVsUP5OE=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.4.3/go.mod h1:hhKa4ZFaNU2fwUjEh8GYr8wKg5D1W4QyxZ3xpsb/2hw=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.0 h1:L8sl3yoICAbn3134CBLNUt0o5h2voe0Es2KD5O9r8YQ=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.0/go.mod h1:321w9X26lAnNa/lQJi2A6Lap5IsNORoLwFPoJ1i8QvY=
|
||||
github.com/wealdtech/go-eth2-util v1.2.2 h1:LALunpMSJFvu89RHS1zl6RjZ52805utRvd12RtquB54=
|
||||
github.com/wealdtech/go-eth2-util v1.2.2/go.mod h1:R3VlTd69B2Jf58s62ChcyXt11ZK1/36CTplTuyR/6dE=
|
||||
github.com/wealdtech/go-eth2-util v1.3.0 h1:aX1+PnxB904GIf5JE9GRKYPuGQJsCT+Q7PG9BMeFN40=
|
||||
github.com/wealdtech/go-eth2-util v1.3.0/go.mod h1:nSHpt/mdwn1LyLiNzjGPH1DDIYdBENLFaY1fSRr+aKg=
|
||||
github.com/wealdtech/go-eth2-util v1.5.0 h1:b3fgyvoq/WocW9LkWT7zcO5VCKzKLCc97rPrk/B9oIc=
|
||||
github.com/wealdtech/go-eth2-util v1.5.0/go.mod h1:0PGWeWWc6qjky/aNjdPdguJdZ2HSEHHCA+3cTjvT+Hk=
|
||||
github.com/wealdtech/go-eth2-util v1.6.0 h1:l2OR0SqfYdEnb1I1Ggnk0w+B9/LA5aHdQ2KK2FPnGkY=
|
||||
github.com/wealdtech/go-eth2-util v1.6.0/go.mod h1:0PGWeWWc6qjky/aNjdPdguJdZ2HSEHHCA+3cTjvT+Hk=
|
||||
github.com/wealdtech/go-eth2-wallet v1.10.2 h1:oUgi6Ih5fA9thhIipzXMSaLkiwDQXwT8q3bCOLpCr7s=
|
||||
github.com/wealdtech/go-eth2-wallet v1.10.2/go.mod h1:8H9pgp5K7X1kU1cJMS/B3DrMZF74ZlwBThownrcRYgk=
|
||||
github.com/wealdtech/go-eth2-wallet v1.11.0 h1:2KfrWDqF4sWGgk4N5+DaYmh0hOnqiCl0P4vCz5mx17U=
|
||||
github.com/wealdtech/go-eth2-wallet v1.11.0/go.mod h1:E9ZRNO4JNdi27ys7oc+xWWucXu4IGfV5q1vWC9X3oqg=
|
||||
github.com/wealdtech/go-eth2-wallet v1.12.0 h1:nrwI3jPhehUhJGlBtNv/UmIo/57llvuVZZavLnfdQHI=
|
||||
github.com/wealdtech/go-eth2-wallet v1.12.0/go.mod h1:ouV+YSMbzk2dyecmofm8jhaMKdSigdIPMSnSqmWEfW8=
|
||||
github.com/wealdtech/go-eth2-wallet v1.13.0 h1:ayy/jBcKVZhqU4OMlZDieRzhwSiF3Ozmc3e3LrdpWro=
|
||||
github.com/wealdtech/go-eth2-wallet v1.13.0/go.mod h1:O4efkfrSBRRa7Q8vZQ8usUCBRki+/zE531b3JqScIII=
|
||||
github.com/wealdtech/go-eth2-wallet v1.14.0 h1:eZjopWDMlCHCE9SLydAXBr0Cqtm2HWL3O56ELfeGK9c=
|
||||
github.com/wealdtech/go-eth2-wallet v1.14.0/go.mod h1:HrJ4hLcTPZPjhdkZKZd07OFxA+r7d3i+7XKjYJbxdxk=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.0 h1:1QUcWILF3h4OLCgTPpWklvRSuPu0fqrt15jwSm7CSC4=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.0/go.mod h1:VTzjJ51dedvYPr4huI7g7KXZVTpGR6ZrCDQwBxJpLck=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.1 h1:YUE1QlJPun8b+xbz0JM71/3t1i9zp9KjcZdJvtJQL+E=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.1/go.mod h1:5jK/aEAjYAVRBKKjYAvJWSmOWxiECs4asYXHwloNI+w=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.2 h1:ZxAdF6iTOzYHtQlWd1nzVevZ+HtXS/LLn580t+NXT3A=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.2/go.mod h1:5jK/aEAjYAVRBKKjYAvJWSmOWxiECs4asYXHwloNI+w=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.3 h1:NWwxzYjFG3k7S9mzVOvj02A5S9QOak4uh+MJF2Ae37w=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.0.3/go.mod h1:WIy6Xx0FgTNG5bP7IytxsEt8y1Yl46HbsWVFbE4Yc94=
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.0.1 h1:3BxMII8T6t16g6lWcYWXjfdvaw8rXuwMQx9h0TG5wRg=
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.0.1/go.mod h1:Ha/8S+SCLEuSfXHdvhTLwnKaEF47o6gzQ+FURKwftvU=
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.0 h1:OZjjuxcIYo+EhAfph7lYP1z+VeNs9ruOI32kqtYe1Jg=
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.0/go.mod h1:8r06Vpg/315/7Hl9CXq0ShQP8/cgUrBGzKKo6ywA4yQ=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.0.0 h1:IcpS4VpXhYz+TVupB5n6C6IQzaKwG+Rc8nvgCa/da4c=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.0.0/go.mod h1:X8WRO5hEwbjx8ZOqoRmtS1ngyflKs25GkP7qGv7yOqE=
|
||||
github.com/wealdtech/go-eth2-wallet-hd v1.9.1 h1:u53yvtm4cJM3HZS0L9Lt1i3YjbxJ9y4SJvlcsp2nX6o=
|
||||
github.com/wealdtech/go-eth2-wallet-hd v1.9.1/go.mod h1:QGIkXF1AtI9cLByFlnnRXDoK01/SuunRfGLzhWDMnCU=
|
||||
github.com/wealdtech/go-eth2-wallet-hd v1.10.0 h1:rWpJRWXrnLGLadSOIhCqxAkSIOd93knxxUVxvScV/qw=
|
||||
github.com/wealdtech/go-eth2-wallet-hd v1.10.0/go.mod h1:LiuHHIG7pU/u598yZBqsjWk0p5LmclxdPQeUSKJJwkg=
|
||||
github.com/wealdtech/go-eth2-wallet-nd v1.7.1 h1:UtOUJaaVDYybYAZInCnKqVbiHEoqLzg6XlVs1qMoNgo=
|
||||
github.com/wealdtech/go-eth2-wallet-nd v1.7.1/go.mod h1:3vaCTMI2yPndhgjXqM6ynv3yrU0VvqksQ4MaQLCgZIo=
|
||||
github.com/wealdtech/go-eth2-wallet-nd v1.8.0 h1:DD7QRS3mPS6GH0vNFn2DcJ3mHRgc+gtQ0Uq189pWZ00=
|
||||
github.com/wealdtech/go-eth2-wallet-nd v1.8.0/go.mod h1:o0nR55vuaKZ4d+FuzzDhapu0LBQvk5CUER7ZCg1IYH8=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.5.0 h1:bVOahxWdk0qkZ6xseQBw8fNmCDIbmIM/4KHY+cUSWj8=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.5.0/go.mod h1:8zwb4tAWxGF4PZCNNYdkjlGQYbTEadkKwCkSNyCOfm4=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.7.0 h1:SJ1yx0K9bk/W7M7lSiOVIm2Trs0FTlQ3wkAfOIl82qk=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.7.0/go.mod h1:XM1bKpfV6dPdygA1HMO7vx1Vxlir8VMa649mcD2uudg=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.5.0 h1:ZbG99FcnBJrZhVWIfcWS/qe4C8dcog0Amjj6v+RraSE=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.5.0/go.mod h1:NTchwZDLDZhrzF3ENkC890m4SKt5SI8mHdn5XJryVL0=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.6.0 h1:Xuq7JJ1DE8eGY6n3E/a+lgyySB7mvudAFaNbdEo+Srk=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.6.0/go.mod h1:OjE3G9zCPgJs/W4EVpvIIg+d8X/I8uA1bR9ZVBwf22M=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.3.0 h1:iC0pZQY9zfh1onYHwnkiQa6V998LVK54U5ShUu7QTf8=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.3.0/go.mod h1:NaV/et5zTaqHhTpdpSveUYE2czCFAD8f07eABh/0pso=
|
||||
github.com/wealdtech/go-eth2-wallet-types v1.8.0 h1:6K/u5CTcUavgzoABeeuBAZ0jp1qEOdK/9U8gaMJUlVE=
|
||||
github.com/wealdtech/go-eth2-wallet-types v1.8.0/go.mod h1:5A83MUBhmLgxpg9X5eqvnDIOYfn329caf5DOcv7pcY0=
|
||||
github.com/wealdtech/go-eth2-wallet-types v1.10.0 h1:iQx3MxMQQwoEfyPDHy5vtKYVtrUjY4nzEODbUPcz2bg=
|
||||
github.com/wealdtech/go-eth2-wallet-types v1.10.0/go.mod h1:vwK05jlJM/ibZs/QCqJ4dOhsjjpNcIP4X7Vx6WYXLGI=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.0 h1:CWb82xeNaZQt1Z829RyDALUy7UZbc6VOfTS+82jRdEQ=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.0/go.mod h1:JelKMM10UzDJNXdIcojMj6SCIsHC8NYn4c1S2FFk7OQ=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.1.3/go.mod h1:STigKib4ZSefVvJjx88V2QpUGaoyUE1TiupcpsHpvKE=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.2.0 h1:L+yrAn8TC9DQUw+S7moOJxQTp2jrHCoAZLpI747Nx2g=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.2.0/go.mod h1:lhSwtkIO/Pfg5kz8k50yrDgj7ZQaElCPsXnixlrQn/I=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.3.0 h1:UORXUYRoUYgYF96Y+QiBq33OKQVtn/nEjnSoQbe1UOA=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.3.0/go.mod h1:Kc/8WcqMTczfH2xy5mDfCRd0NI/ca/j2jXmqJ7gz8yk=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.4.0 h1:bqG/i1fpDpnQFgJ+NBLgOLdmGhXiinwlrFVD1OAgZlo=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.4.0/go.mod h1:xBbzc+aRD06dTL5phnMshpM4sryyWYM8m86sjQKG5u0=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.5.0 h1:OhJm7hn6vlO+dazs5S1EBrZu/ZVQUQcaNw1ncfy0/xI=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.5.0/go.mod h1:LfgcOnQeBcqBEoHd4VNqwZhwqzz0Xh1DqnDmjHWONGs=
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.1.2/go.mod h1:IssxoHII0ewO1VysMfCmdJP1D00tRhRhXIhhaEXIOVE=
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.2.0 h1:h4eePfG0ANOJYMonmIYOvxJ9uLmBEX4APb2O8Vhtv6k=
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.2.0/go.mod h1:Un2EtseZWSObmTBjgkt7Qz2am54S/0115jrF83lto1U=
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.0 h1:L1aPK9nc+8Ctcw+8I05vM6408weFc4a5RtLQDUeS0eE=
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.0/go.mod h1:e2q2uuEdq5+B3GE7jk+Mi9oz9V5nPPKXcXRg1XYavsU=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.15.2 h1:Z4Pw7/Mlp6jJLoJnhgov8M1011HP/Pb3YYqcdYGCy6Q=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.15.2/go.mod h1:GSMbVCewjbxRrw32m6YCd9DOzCRjAXB3qUOQnr58JEs=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.0 h1:sWuSrAKdWSphiQCVcThozaFgTrwemXNXDI5CnFcP02s=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.0/go.mod h1:FvjUHDbBuZrytZGOfhLWgtBoxtrWhvkD47ABrUXvHs4=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.1 h1:l9YV6OBqcxp5fjscK63lzuCUIye8ANACjJdpm5ULGS8=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.1/go.mod h1:Zxhj/4i8nRpk4LTTqFKbfI2KyvO3uqLMerNXqKZKDK0=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.7.2 h1:a7GWfFd139CODvvkuTbRIuRwAAjb55sFDGRh177KXGk=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.7.2/go.mod h1:VWvXScZKUWHbhQpadLX8Yj+mc8U/i4zGthQJee+o3xg=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.8.0 h1:+q7p58NvOEfEDw8NgEoNaSG/s1eFHpyg91NEobA6RF0=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.8.0/go.mod h1:OxYD+d79StAOHigNaI5bWuvjhanEyrD4MqTj8hIvt2Y=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.0 h1:O5211UskLbK1WDecTXwugUlINDBQ26MqtiFn6u66fmA=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.0/go.mod h1:dcQPLsRRYDiMV0DFYzTX6HRpP9WP+gWreAX5SLBOJ0I=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.4.2 h1:GvG3ZuzxbqFjGUaGoa8Tz7XbPlDA33G6nHQbSZInC3g=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.4.2/go.mod h1:+TbqLmJuT98PWi/xW1bp5nwZbKz+SIJYVh/+NUkmnb4=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.5.0/go.mod h1:RMIIV5/N8TgukTVzyumQd7AplpC440ZXDSk8VffeEwQ=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.0/go.mod h1:XtXHbl4OV/XenQsvGmXbh+bVXaGS788oa30DB7kDInA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.2.0 h1:SfoBlW2LYjW05uHhnTZaezX37gbRsp+VYtxWT6SeAME=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.2.0/go.mod h1:XEvrlKFnHLbg1tj4Dep76XKASeS13TBpvdeXmvLiH+k=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.3.0-beta4 h1:VmpgUSr+aUexFmC2AYlQ7zpeAy0w0mcK58ihpDeMCL8=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.3.0-beta4/go.mod h1:5tVjyWK/jIzKaD+L8SCmHnc/eT9k+Fmm7zd8SwNB7jA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.3.0 h1:PsCvp/lw7+h8Q0V3jL0f+/w2VmgS7m0mH48lbv/c2LY=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.3.0/go.mod h1:SLST6Pw/2wOEfsMYvIQjWlxbWX+jaZu8jIEbZJc4K5Q=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.5.0 h1:J29mbkSCUMl2xdu8Lg6U+JptFGfmli6xl04DAHtq9aM=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.5.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.6.0 h1:vBrH5icPPSeb14cdShA7/P2PBZOgZscJ2IhBlTIaFrA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.6.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0 h1:pquFQdIWEiSYrpIpFuvsRuialI8t9KhFsPvbIBPnzic=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
|
||||
github.com/wealdtech/go-indexer v1.0.0 h1:/S4rfWQbSOnnYmwnvuTVatDibZ8o1s9bmTCHO16XINg=
|
||||
github.com/wealdtech/go-indexer v1.0.0/go.mod h1:u1cjsbsOXsm5jzJDyLmZY7GsrdX8KYXKBXkZcAmk3Zg=
|
||||
github.com/wealdtech/go-string2eth v1.1.0 h1:USJQmysUrBYYmZs7d45pMb90hRSyEwizP7lZaOZLDAw=
|
||||
@@ -237,113 +422,231 @@ github.com/wealdtech/go-string2eth v1.1.0/go.mod h1:RUzsLjJtbZaJ/3UKn9kY19a/vCCU
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
|
||||
golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59 h1:PyXRxSVbvzDGuqYXjHndV7xDzJ7w2K8KD9Ef8GB7KOE=
|
||||
golang.org/x/crypto v0.0.0-20191105034135-c7e5f84aec59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
|
||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
|
||||
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200208060501-ecb85df21340 h1:KOcEaR10tFr7gdJV2GCKw8Os5yED1u1aOqHjOAb6d2Y=
|
||||
golang.org/x/crypto v0.0.0-20200208060501-ecb85df21340/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190326090315-15845e8f865b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200620081246-981b61492c35 h1:wb/9mP8eUAmHfkM8RmpeLq6nUA7c2i5+bQOtcDftjaE=
|
||||
golang.org/x/sys v0.0.0-20200620081246-981b61492c35/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY=
|
||||
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200821140526-fda516888d29 h1:mNuhGagCf3lDDm5C0376C/sxh6V7fy9WbdEu/YDNA04=
|
||||
golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190106171756-3ef68632349c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190325223049-1d95b17f1b04/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6 h1:tirixpud1WdjE3/NrL9ar4ot0ADfwls8sOcIf1ivRDw=
|
||||
google.golang.org/genproto v0.0.0-20200207204624-4f3edf09f4f6/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200528191852-705c0b31589b/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200620020550-bd6e04640131 h1:IXNofpkLhv80L3TJQvj2YQLnMHZgAktycswvtXwQiRk=
|
||||
google.golang.org/genproto v0.0.0-20200620020550-bd6e04640131/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200710124503-20a17af7bd0e h1:k+p/u26/lVeNEpdxSeUrm7rTvoFckBKaf7gTzgmHyDA=
|
||||
google.golang.org/genproto v0.0.0-20200710124503-20a17af7bd0e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc=
|
||||
google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c h1:6DWnZZ6EY/59QRRQttZKiktVL23UuQYs7uy75MhhLRM=
|
||||
google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200722002428-88e341933a54 h1:ASrBgpl9XvkNTP0m39/j18mid7aoF21npu2ioIBxYnY=
|
||||
google.golang.org/genproto v0.0.0-20200722002428-88e341933a54/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200814021100-8c09557e8a18 h1:hjnc4GP1IeBnHi+u3sp8be4ELyIHmFSO5p8DUdJUVt4=
|
||||
google.golang.org/genproto v0.0.0-20200814021100-8c09557e8a18/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8=
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
|
||||
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.58.0 h1:VdDvTzv/005R8vEFyQ56bpEnOKTNPbpJhL0VCohxlQw=
|
||||
gopkg.in/ini.v1 v1.58.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g=
|
||||
gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -18,23 +18,25 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types"
|
||||
"github.com/spf13/viper"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// FetchChainConfig fetches the chain configuration from the beacon node.
|
||||
// It tweaks the output to make it easier to work with by setting appropriate
|
||||
// types.
|
||||
func FetchChainConfig(conn *grpc.ClientConn) (map[string]interface{}, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
config, err := beaconClient.GetBeaconConfig(ctx, &empty.Empty{})
|
||||
config, err := beaconClient.GetBeaconConfig(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -72,16 +74,245 @@ func FetchChainConfig(conn *grpc.ClientConn) (map[string]interface{}, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// FetchValidator fetches validator information from the beacon node.
|
||||
func FetchValidator(conn *grpc.ClientConn, account wtypes.Account) (*ethpb.Validator, error) {
|
||||
func FetchLatestFilledSlot(conn *grpc.ClientConn) (uint64, error) {
|
||||
if conn == nil {
|
||||
return 0, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
chainHead, err := beaconClient.GetChainHead(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to obtain latest")
|
||||
}
|
||||
|
||||
return chainHead.HeadSlot, nil
|
||||
}
|
||||
|
||||
// FetchValidatorCommittees fetches the validator committees for a given epoch.
|
||||
func FetchValidatorCommittees(conn *grpc.ClientConn, epoch uint64) (map[uint64][][]uint64, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
req := ðpb.ListCommitteesRequest{
|
||||
QueryFilter: ðpb.ListCommitteesRequest_Epoch{
|
||||
Epoch: epoch,
|
||||
},
|
||||
}
|
||||
resp, err := beaconClient.ListBeaconCommittees(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain committees")
|
||||
}
|
||||
|
||||
res := make(map[uint64][][]uint64)
|
||||
for slot, committees := range resp.Committees {
|
||||
res[slot] = make([][]uint64, len(resp.Committees))
|
||||
for i, committee := range committees.Committees {
|
||||
res[slot][uint64(i)] = make([]uint64, len(committee.ValidatorIndices))
|
||||
indices := make([]uint64, len(committee.ValidatorIndices))
|
||||
copy(indices, committee.ValidatorIndices)
|
||||
res[slot][uint64(i)] = indices
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FetchValidator fetches the validator definition from the beacon node.
|
||||
func FetchValidator(conn *grpc.ClientConn, account e2wtypes.Account) (*ethpb.Validator, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return nil, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
req := ðpb.GetValidatorRequest{
|
||||
QueryFilter: ðpb.GetValidatorRequest_PublicKey{
|
||||
PublicKey: pubKey,
|
||||
},
|
||||
}
|
||||
return beaconClient.GetValidator(ctx, req)
|
||||
}
|
||||
|
||||
// FetchValidatorByIndex fetches the validator definition from the beacon node.
|
||||
func FetchValidatorByIndex(conn *grpc.ClientConn, index uint64) (*ethpb.Validator, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
req := ðpb.GetValidatorRequest{
|
||||
QueryFilter: ðpb.GetValidatorRequest_PublicKey{
|
||||
PublicKey: account.PublicKey().Marshal(),
|
||||
QueryFilter: ðpb.GetValidatorRequest_Index{
|
||||
Index: index,
|
||||
},
|
||||
}
|
||||
return beaconClient.GetValidator(ctx, req)
|
||||
}
|
||||
|
||||
// FetchValidatorBalance fetches the validator balance from the beacon node.
|
||||
func FetchValidatorBalance(conn *grpc.ClientConn, account e2wtypes.Account) (uint64, error) {
|
||||
if conn == nil {
|
||||
return 0, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return 0, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
res, err := beaconClient.ListValidatorBalances(ctx, ðpb.ListValidatorBalancesRequest{
|
||||
PublicKeys: [][]byte{pubKey},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(res.Balances) == 0 {
|
||||
return 0, errors.New("unknown validator")
|
||||
}
|
||||
return res.Balances[0].Balance, nil
|
||||
}
|
||||
|
||||
// FetchValidatorPerformance fetches the validator performance from the beacon node.
|
||||
func FetchValidatorPerformance(conn *grpc.ClientConn, account e2wtypes.Account) (bool, bool, bool, uint64, int64, error) {
|
||||
if conn == nil {
|
||||
return false, false, false, 0, 0, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return false, false, false, 0, 0, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
req := ðpb.ValidatorPerformanceRequest{
|
||||
PublicKeys: [][]byte{pubKey},
|
||||
}
|
||||
res, err := beaconClient.GetValidatorPerformance(ctx, req)
|
||||
if err != nil {
|
||||
return false, false, false, 0, 0, err
|
||||
}
|
||||
if len(res.InclusionDistances) == 0 {
|
||||
return false, false, false, 0, 0, errors.New("unknown validator")
|
||||
}
|
||||
return res.CorrectlyVotedHead[0],
|
||||
res.CorrectlyVotedSource[0],
|
||||
res.CorrectlyVotedTarget[0],
|
||||
res.InclusionDistances[0],
|
||||
int64(res.BalancesAfterEpochTransition[0]) - int64(res.BalancesBeforeEpochTransition[0]),
|
||||
err
|
||||
}
|
||||
|
||||
// FetchValidatorInfo fetches current validator info from the beacon node.
|
||||
func FetchValidatorInfo(conn *grpc.ClientConn, account e2wtypes.Account) (*ethpb.ValidatorInfo, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
stream, err := beaconClient.StreamValidatorsInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to contact beacon node")
|
||||
}
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return nil, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
changeSet := ðpb.ValidatorChangeSet{
|
||||
Action: ethpb.SetAction_SET_VALIDATOR_KEYS,
|
||||
PublicKeys: [][]byte{pubKey},
|
||||
}
|
||||
err = stream.Send(changeSet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to send validator public key")
|
||||
}
|
||||
return stream.Recv()
|
||||
}
|
||||
|
||||
// FetchChainInfo fetches current chain info from the beacon node.
|
||||
func FetchChainInfo(conn *grpc.ClientConn) (*ethpb.ChainHead, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
return beaconClient.GetChainHead(ctx, &types.Empty{})
|
||||
}
|
||||
|
||||
// FetchBlock fetches a block at a given slot from the beacon node.
|
||||
func FetchBlock(conn *grpc.ClientConn, slot uint64) (*ethpb.SignedBeaconBlock, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
req := ðpb.ListBlocksRequest{}
|
||||
if slot == 0 {
|
||||
req.QueryFilter = ðpb.ListBlocksRequest_Genesis{Genesis: true}
|
||||
} else {
|
||||
req.QueryFilter = ðpb.ListBlocksRequest_Slot{Slot: slot}
|
||||
}
|
||||
resp, err := beaconClient.ListBlocks(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.BlockContainers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return resp.BlockContainers[0].Block, nil
|
||||
}
|
||||
|
||||
func StreamBlocks(conn *grpc.ClientConn) (ethpb.BeaconChain_StreamBlocksClient, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
|
||||
beaconClient := ethpb.NewBeaconChainClient(conn)
|
||||
stream, err := beaconClient.StreamBlocks(context.Background(), &types.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
@@ -16,22 +16,35 @@ package grpc
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types"
|
||||
wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// FetchValidatorIndex fetches the index of a validator.
|
||||
func FetchValidatorIndex(conn *grpc.ClientConn, account wtypes.Account) (uint64, error) {
|
||||
if conn == nil {
|
||||
return 0, errors.New("no connection to beacon node")
|
||||
}
|
||||
validatorClient := ethpb.NewBeaconNodeValidatorClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return 0, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
// Fetch the account.
|
||||
req := ðpb.ValidatorIndexRequest{
|
||||
PublicKey: account.PublicKey().Marshal(),
|
||||
PublicKey: pubKey,
|
||||
}
|
||||
resp, err := validatorClient.ValidatorIndex(ctx, req)
|
||||
if err != nil {
|
||||
@@ -43,17 +56,29 @@ func FetchValidatorIndex(conn *grpc.ClientConn, account wtypes.Account) (uint64,
|
||||
|
||||
// FetchValidatorState fetches the state of a validator.
|
||||
func FetchValidatorState(conn *grpc.ClientConn, account wtypes.Account) (ethpb.ValidatorStatus, error) {
|
||||
if conn == nil {
|
||||
return ethpb.ValidatorStatus_UNKNOWN_STATUS, errors.New("no connection to beacon node")
|
||||
}
|
||||
validatorClient := ethpb.NewBeaconNodeValidatorClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
var pubKey []byte
|
||||
if pubKeyProvider, ok := account.(wtypes.AccountCompositePublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.CompositePublicKey().Marshal()
|
||||
} else if pubKeyProvider, ok := account.(wtypes.AccountPublicKeyProvider); ok {
|
||||
pubKey = pubKeyProvider.PublicKey().Marshal()
|
||||
} else {
|
||||
return ethpb.ValidatorStatus_UNKNOWN_STATUS, errors.New("Unable to obtain public key")
|
||||
}
|
||||
|
||||
// Fetch the account.
|
||||
req := ðpb.ValidatorStatusRequest{
|
||||
PublicKey: account.PublicKey().Marshal(),
|
||||
PublicKey: pubKey,
|
||||
}
|
||||
resp, err := validatorClient.ValidatorStatus(ctx, req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return ethpb.ValidatorStatus_UNKNOWN_STATUS, err
|
||||
}
|
||||
|
||||
return resp.Status, nil
|
||||
|
||||
72
grpc/node.go
72
grpc/node.go
@@ -17,21 +17,85 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
||||
)
|
||||
|
||||
// FetchGenesis fetches the genesis time.
|
||||
func FetchGenesis(conn *grpc.ClientConn) (time.Time, error) {
|
||||
// FetchGenesisTime fetches the genesis time.
|
||||
func FetchGenesisTime(conn *grpc.ClientConn) (time.Time, error) {
|
||||
if conn == nil {
|
||||
return time.Now(), errors.New("no connection to beacon node")
|
||||
}
|
||||
client := ethpb.NewNodeClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
res, err := client.GetGenesis(ctx, &empty.Empty{})
|
||||
res, err := client.GetGenesis(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
return time.Unix(res.GetGenesisTime().Seconds, 0), nil
|
||||
}
|
||||
|
||||
// FetchGenesisValidatorsRoot fetches the genesis validators root.
|
||||
func FetchGenesisValidatorsRoot(conn *grpc.ClientConn) ([]byte, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
client := ethpb.NewNodeClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
res, err := client.GetGenesis(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.GetGenesisValidatorsRoot(), nil
|
||||
}
|
||||
|
||||
// FetchDepositContractAddress fetches the address of the deposit contract.
|
||||
func FetchDepositContractAddress(conn *grpc.ClientConn) ([]byte, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("no connection to beacon node")
|
||||
}
|
||||
client := ethpb.NewNodeClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
res, err := client.GetGenesis(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.DepositContractAddress, nil
|
||||
}
|
||||
|
||||
// FetchVersion fetches the version and metadata from the server.
|
||||
func FetchVersion(conn *grpc.ClientConn) (string, string, error) {
|
||||
if conn == nil {
|
||||
return "", "", errors.New("no connection to beacon node")
|
||||
}
|
||||
client := ethpb.NewNodeClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
version, err := client.GetVersion(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return version.Version, version.Metadata, nil
|
||||
}
|
||||
|
||||
// FetchSyncing returns true if the node is syncing, otherwise false.
|
||||
func FetchSyncing(conn *grpc.ClientConn) (bool, error) {
|
||||
if conn == nil {
|
||||
return false, errors.New("no connection to beacon node")
|
||||
}
|
||||
client := ethpb.NewNodeClient(conn)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
syncStatus, err := client.GetSyncStatus(ctx, &types.Empty{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return syncStatus.Syncing, nil
|
||||
}
|
||||
|
||||
31
util/bls.go
Normal file
31
util/bls.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/herumi/bls-eth-go-binary/bls"
|
||||
)
|
||||
|
||||
// BLSID turns a uint64 in to a BLS identifier.
|
||||
func BLSID(id uint64) *bls.ID {
|
||||
var res bls.ID
|
||||
buf := [8]byte{}
|
||||
binary.LittleEndian.PutUint64(buf[:], id)
|
||||
if err := res.SetLittleEndian(buf[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
282
util/depositinfo.go
Normal file
282
util/depositinfo.go
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DepositInfo is a generic deposit structure.
|
||||
type DepositInfo struct {
|
||||
Name string
|
||||
Account string
|
||||
PublicKey []byte
|
||||
WithdrawalCredentials []byte
|
||||
Signature []byte
|
||||
DepositDataRoot []byte
|
||||
DepositMessageRoot []byte
|
||||
ForkVersion []byte
|
||||
Amount uint64
|
||||
Version uint64
|
||||
}
|
||||
|
||||
// depositInfoV1 is an ethdo V1 deposit structure.
|
||||
type depositInfoV1 struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Account string `json:"account,omitempty"`
|
||||
PublicKey string `json:"pubkey"`
|
||||
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
||||
Signature string `json:"signature"`
|
||||
DepositDataRoot string `json:"deposit_data_root"`
|
||||
Value uint64 `json:"value"`
|
||||
Version uint64 `json:"version"`
|
||||
}
|
||||
|
||||
// depositInfoV3 is an ethdo V3 deposit structure.
|
||||
type depositInfoV3 struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Account string `json:"account,omitempty"`
|
||||
PublicKey string `json:"pubkey"`
|
||||
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
||||
Signature string `json:"signature"`
|
||||
DepositDataRoot string `json:"deposit_data_root"`
|
||||
DepositMessageRoot string `json:"deposit_message_root"`
|
||||
ForkVersion string `json:"fork_version"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Version uint64 `json:"version"`
|
||||
}
|
||||
|
||||
// depositInfoCLI is a deposit structure from the eth2 deposit CLI.
|
||||
type depositInfoCLI struct {
|
||||
PublicKey string `json:"pubkey"`
|
||||
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
||||
Signature string `json:"signature"`
|
||||
DepositDataRoot string `json:"deposit_data_root"`
|
||||
DepositMessageRoot string `json:"deposit_message_root"`
|
||||
ForkVersion string `json:"fork_version"`
|
||||
Amount uint64 `json:"amount"`
|
||||
}
|
||||
|
||||
func DepositInfoFromJSON(input []byte) ([]*DepositInfo, error) {
|
||||
// Work out the type of data that we're dealing with, and decode it appropriately.
|
||||
depositInfo, err := tryRawTxData(input)
|
||||
if err != nil {
|
||||
depositInfo, err = tryV3DepositInfoFromJSON(input)
|
||||
if err != nil {
|
||||
depositInfo, err = tryV1DepositInfoFromJSON(input)
|
||||
if err != nil {
|
||||
depositInfo, err = tryCLIDepositInfoFromJSON(input)
|
||||
if err != nil {
|
||||
// Give up
|
||||
return nil, errors.New("unknown deposit data format")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(depositInfo) == 0 {
|
||||
return nil, errors.New("no deposits supplied")
|
||||
}
|
||||
|
||||
for i := range depositInfo {
|
||||
if len(depositInfo[i].PublicKey) == 0 {
|
||||
return nil, fmt.Errorf("no public key for deposit %d", i)
|
||||
}
|
||||
if len(depositInfo[i].DepositDataRoot) == 0 {
|
||||
return nil, fmt.Errorf("no data root for deposit %d", i)
|
||||
}
|
||||
if len(depositInfo[i].Signature) == 0 {
|
||||
return nil, fmt.Errorf("no signature for deposit %d", i)
|
||||
}
|
||||
if len(depositInfo[i].WithdrawalCredentials) == 0 {
|
||||
return nil, fmt.Errorf("no withdrawal credentials for deposit %d", i)
|
||||
}
|
||||
}
|
||||
return depositInfo, nil
|
||||
}
|
||||
|
||||
func tryV3DepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
|
||||
var depositData []*depositInfoV3
|
||||
err := json.Unmarshal(data, &depositData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
depositInfos := make([]*DepositInfo, len(depositData))
|
||||
for i, deposit := range depositData {
|
||||
if deposit.Version != 3 {
|
||||
return nil, errors.New("incorrect V3 deposit version")
|
||||
}
|
||||
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("public key invalid")
|
||||
}
|
||||
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("withdrawal credentials invalid")
|
||||
}
|
||||
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("signature invalid")
|
||||
}
|
||||
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("deposit data root invalid")
|
||||
}
|
||||
depositMessageRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositMessageRoot, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("deposit message root invalid")
|
||||
}
|
||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(deposit.ForkVersion, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("fork version invalid")
|
||||
}
|
||||
depositInfos[i] = &DepositInfo{
|
||||
Name: deposit.Name,
|
||||
Account: deposit.Account,
|
||||
PublicKey: publicKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Signature: signature,
|
||||
DepositDataRoot: depositDataRoot,
|
||||
DepositMessageRoot: depositMessageRoot,
|
||||
ForkVersion: forkVersion,
|
||||
Amount: deposit.Amount,
|
||||
Version: 3,
|
||||
}
|
||||
}
|
||||
|
||||
return depositInfos, nil
|
||||
}
|
||||
|
||||
func tryCLIDepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
|
||||
var depositData []*depositInfoCLI
|
||||
err := json.Unmarshal(data, &depositData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
depositInfos := make([]*DepositInfo, len(depositData))
|
||||
for i, deposit := range depositData {
|
||||
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("public key invalid")
|
||||
}
|
||||
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("withdrawal credentials invalid")
|
||||
}
|
||||
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("signature invalid")
|
||||
}
|
||||
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("deposit data root invalid")
|
||||
}
|
||||
depositMessageRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositMessageRoot, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("deposit message root invalid")
|
||||
}
|
||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(deposit.ForkVersion, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("fork version invalid")
|
||||
}
|
||||
depositInfos[i] = &DepositInfo{
|
||||
PublicKey: publicKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Signature: signature,
|
||||
DepositDataRoot: depositDataRoot,
|
||||
DepositMessageRoot: depositMessageRoot,
|
||||
ForkVersion: forkVersion,
|
||||
Amount: deposit.Amount,
|
||||
Version: 3,
|
||||
}
|
||||
}
|
||||
|
||||
return depositInfos, nil
|
||||
}
|
||||
|
||||
func tryV1DepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
|
||||
var depositData []*depositInfoV1
|
||||
err := json.Unmarshal(data, &depositData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
depositInfos := make([]*DepositInfo, len(depositData))
|
||||
for i, deposit := range depositData {
|
||||
if deposit.Version < 1 || deposit.Version > 2 {
|
||||
return nil, errors.New("incorrect deposit version")
|
||||
}
|
||||
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("public key invalid")
|
||||
}
|
||||
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("withdrawal credentials invalid")
|
||||
}
|
||||
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("signature invalid")
|
||||
}
|
||||
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("deposit data root invalid")
|
||||
}
|
||||
depositInfos[i] = &DepositInfo{
|
||||
Name: deposit.Name,
|
||||
Account: deposit.Account,
|
||||
PublicKey: publicKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Signature: signature,
|
||||
DepositDataRoot: depositDataRoot,
|
||||
Amount: deposit.Value,
|
||||
Version: 3,
|
||||
}
|
||||
}
|
||||
|
||||
return depositInfos, nil
|
||||
}
|
||||
|
||||
func tryRawTxData(data []byte) ([]*DepositInfo, error) {
|
||||
txData, err := hex.DecodeString(strings.TrimPrefix(string(data), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.New("public key invalid")
|
||||
}
|
||||
|
||||
depositInfos := make([]*DepositInfo, 1)
|
||||
|
||||
if len(txData) != 420 {
|
||||
return nil, errors.New("invalid transaction length")
|
||||
}
|
||||
if !bytes.Equal(txData[0:4], []byte{0x22, 0x89, 0x51, 0x18}) {
|
||||
return nil, errors.New("invalid function signature")
|
||||
}
|
||||
|
||||
depositInfos[0] = &DepositInfo{
|
||||
PublicKey: txData[164:212],
|
||||
WithdrawalCredentials: txData[260:292],
|
||||
Signature: txData[324:420],
|
||||
DepositDataRoot: txData[100:132],
|
||||
}
|
||||
|
||||
return depositInfos, nil
|
||||
}
|
||||
100
util/scratchaccount.go
Normal file
100
util/scratchaccount.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// 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 util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
types "github.com/wealdtech/go-eth2-types/v2"
|
||||
)
|
||||
|
||||
// ScratchAccount is an account that exists temporarily.
|
||||
type ScratchAccount struct {
|
||||
id uuid.UUID
|
||||
privKey types.PrivateKey
|
||||
pubKey types.PublicKey
|
||||
unlocked bool
|
||||
}
|
||||
|
||||
// NewScratchAccount creates a new local account.
|
||||
func NewScratchAccount(privKey []byte, pubKey []byte) (*ScratchAccount, error) {
|
||||
if len(privKey) > 0 {
|
||||
return newScratchAccountFromPrivKey(privKey)
|
||||
} else {
|
||||
return newScratchAccountFromPubKey(pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
func newScratchAccountFromPrivKey(privKey []byte) (*ScratchAccount, error) {
|
||||
key, err := types.BLSPrivateKeyFromBytes(privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ScratchAccount{
|
||||
id: uuid.New(),
|
||||
privKey: key,
|
||||
pubKey: key.PublicKey(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newScratchAccountFromPubKey(pubKey []byte) (*ScratchAccount, error) {
|
||||
key, err := types.BLSPublicKeyFromBytes(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ScratchAccount{
|
||||
id: uuid.New(),
|
||||
pubKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) ID() uuid.UUID {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) Name() string {
|
||||
return "scratch"
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) PublicKey() types.PublicKey {
|
||||
return a.pubKey
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) Lock() {
|
||||
a.unlocked = false
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) Unlock([]byte) error {
|
||||
a.unlocked = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) IsUnlocked() bool {
|
||||
return a.unlocked
|
||||
}
|
||||
|
||||
func (a *ScratchAccount) Sign(data []byte) (types.Signature, error) {
|
||||
if !a.IsUnlocked() {
|
||||
return nil, errors.New("locked")
|
||||
}
|
||||
if a.privKey == nil {
|
||||
return nil, errors.New("no private key")
|
||||
}
|
||||
return a.privKey.Sign(data), nil
|
||||
}
|
||||
Reference in New Issue
Block a user