mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f14233ccea | ||
|
|
d5fc8fed37 | ||
|
|
2bd8fdee5e | ||
|
|
ed830d7901 | ||
|
|
6b127796f9 | ||
|
|
720edf8475 | ||
|
|
e27fae11e2 | ||
|
|
f5d31cf827 | ||
|
|
317c22366e | ||
|
|
98717b9da0 | ||
|
|
c95ba66a0b | ||
|
|
40a7c57abd | ||
|
|
7743481b8c | ||
|
|
6a5251c738 | ||
|
|
ceb5195b69 | ||
|
|
53f0de3fb9 | ||
|
|
16a94d726a | ||
|
|
9d00f6bafc | ||
|
|
d274ab3db0 | ||
|
|
ad3d8606fd | ||
|
|
30455e7c43 | ||
|
|
7eb2c68a19 | ||
|
|
2d96f7cb13 | ||
|
|
fd1e4a97bb | ||
|
|
be2270c543 | ||
|
|
0c36239b8b | ||
|
|
f78b2922ec | ||
|
|
bcf6ffdaf0 | ||
|
|
9fc184f6a1 | ||
|
|
c9a30a6e4b | ||
|
|
1ec6ddc914 | ||
|
|
2c96ef958e | ||
|
|
3c10131c45 | ||
|
|
fe0bfd4f87 | ||
|
|
290413f115 |
7
.github/workflows/golangci-lint.yml
vendored
7
.github/workflows/golangci-lint.yml
vendored
@@ -4,18 +4,21 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=60m
|
||||
|
||||
300
.github/workflows/release.yml
vendored
300
.github/workflows/release.yml
vendored
@@ -7,150 +7,196 @@ on:
|
||||
- 't*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
# Set variables that will be available to all builds.
|
||||
env_vars:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
release_version: ${{ steps.release_version.outputs.release_version }}
|
||||
binary: ${{ steps.binary.outputs.binary }}
|
||||
steps:
|
||||
- id: release_version
|
||||
run: |
|
||||
RELEASE_VERSION=$(echo ${{ github.ref_name }} | sed -e 's/^[vt]//')
|
||||
echo "release_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
|
||||
- id: binary
|
||||
run: |
|
||||
BINARY=$(basename ${{ github.repository }})
|
||||
echo "binary=${BINARY}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.17
|
||||
# Create a github release to hold the assets once built.
|
||||
create_release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: env_vars
|
||||
outputs:
|
||||
upload_url: ${{ steps.release_action.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create Release
|
||||
id: release_action
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Release ${{ needs.env_vars.outputs.release_version }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
# Build and pack the binaries for linux.
|
||||
build_linux:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
|
||||
- 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: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set env
|
||||
run: |
|
||||
echo "GO111MODULE=on" >> $GITHUB_ENV
|
||||
# Release tag comes from the github reference.
|
||||
RELEASE_TAG=$(echo ${GITHUB_REF} | sed -e 's!.*/!!')
|
||||
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
|
||||
echo "::set-output name=RELEASE_TAG::${RELEASE_TAG}"
|
||||
# Ensure the release tag has expected format.
|
||||
echo ${RELEASE_TAG} | grep -q '^[vt]' || exit 1
|
||||
# Release version is same as release tag without leading 'v'.
|
||||
RELEASE_VERSION=$(echo ${GITHUB_REF} | sed -e 's!.*/[vt]!!')
|
||||
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV
|
||||
echo "::set-output name=RELEASE_VERSION::${RELEASE_VERSION}"
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
|
||||
- name: Build
|
||||
run: go build -v -ldflags="-X github.com/wealdtech/ethdo/cmd.ReleaseVersion=${RELEASE_VERSION}" .
|
||||
- name: Fetch repository dependencies
|
||||
run: go get -v -t ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v .
|
||||
- name: Compile
|
||||
run: |
|
||||
go build -tags osusergo,netgo -ldflags="-extldflags=-static" -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }}"
|
||||
tar zcf ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz ${{ needs.env_vars.outputs.binary }}
|
||||
sha256sum ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz | sed -e 's/ .*//' >${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz.sha256
|
||||
- name: Upload binary
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Fetch xgo
|
||||
run: |
|
||||
go install github.com/wealdtech/xgo@latest
|
||||
- name: Upload hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz.sha256
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz.sha256
|
||||
asset_content_type: text/plain
|
||||
|
||||
- name: Cross-compile linux
|
||||
run: |
|
||||
xgo -v -x -ldflags="-X github.com/wealdtech/ethdo/cmd.ReleaseVersion=${RELEASE_VERSION}" --targets="linux/amd64,linux/arm64" github.com/wealdtech/ethdo
|
||||
- name: Cross compile (ARM64)
|
||||
run: |
|
||||
apt install -y gcc-aarch64-linux-gnu libstdc++-12-pic-arm64-cross
|
||||
CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 go build -tags osusergo,netgo -ldflags="-extldflags=-static" -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }}"
|
||||
tar zcf ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz ${{ needs.env_vars.outputs.binary }}
|
||||
sha256sum ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz | sed -e 's/ .*//' >${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz.sha256
|
||||
|
||||
- name: Cross-compile windows
|
||||
run: |
|
||||
xgo -v -x -ldflags="-X github.com/wealdtech/ethdo/cmd.ReleaseVersion=${RELEASE_VERSION} -s -w -extldflags -static" --targets="windows/amd64" github.com/wealdtech/ethdo
|
||||
- name: Upload binary (ARM64)
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Create windows release files
|
||||
run: |
|
||||
mv ethdo-windows-4.0-amd64.exe ethdo.exe
|
||||
zip --junk-paths ethdo-${RELEASE_VERSION}-windows-exe.zip ethdo.exe
|
||||
sha256sum ethdo-${RELEASE_VERSION}-windows-exe.zip >ethdo-${RELEASE_VERSION}-windows.sha256
|
||||
- name: Upload hash (ARM64)
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz.sha256
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz.sha256
|
||||
asset_content_type: text/plain
|
||||
|
||||
- name: Create linux AMD64 tgz file
|
||||
run: |
|
||||
mv ethdo-linux-amd64 ethdo
|
||||
tar zcf ethdo-${RELEASE_VERSION}-linux-amd64.tar.gz ethdo
|
||||
sha256sum ethdo-${RELEASE_VERSION}-linux-amd64.tar.gz >ethdo-${RELEASE_VERSION}-linux-amd64.sha256
|
||||
# Build and pack the binaries for OSX.
|
||||
build_macos:
|
||||
runs-on: macos-latest
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
|
||||
- name: Create linux ARM64 tgz file
|
||||
run: |
|
||||
mv ethdo-linux-arm64 ethdo
|
||||
tar zcf ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz ethdo
|
||||
sha256sum ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz >ethdo-${RELEASE_VERSION}-linux-arm64.sha256
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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: true
|
||||
prerelease: false
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
|
||||
- name: Upload windows checksum file
|
||||
id: upload-release-asset-windows-checksum
|
||||
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.sha256
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-windows.sha256
|
||||
asset_content_type: text/plain
|
||||
- name: Fetch repository dependencies
|
||||
run: go get -v -t ./...
|
||||
|
||||
- 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: Compile
|
||||
run: |
|
||||
go build -tags osusergo,netgo -ldflags="-extldflags=-static" -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }}"
|
||||
tar zcf ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz ${{ needs.env_vars.outputs.binary }}
|
||||
brew install coreutils
|
||||
sha256sum ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz | sed -e 's/ .*//' >${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz.sha256
|
||||
- name: Upload binary
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload linux AMD64 checksum file
|
||||
id: upload-release-asset-linux-amd64-checksum
|
||||
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.sha256
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-linux-amd64.sha256
|
||||
asset_content_type: text/plain
|
||||
- name: Upload hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz.sha256
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-darwin-amd64.tar.gz.sha256
|
||||
asset_content_type: text/plain
|
||||
|
||||
- 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
|
||||
# Build and pack the binaries for Windows.
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
|
||||
- name: Upload linux ARM64 checksum file
|
||||
id: upload-release-asset-linux-arm64-checksum
|
||||
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.sha256
|
||||
asset_name: ethdo-${{ env.RELEASE_VERSION }}-linux-arm64.sha256
|
||||
asset_content_type: text/plain
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
|
||||
- name: Fetch repository dependencies
|
||||
run: go get -v -t ./...
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
go build -tags osusergo,netgo -ldflags="-extldflags=-static" -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }}"
|
||||
choco install zip
|
||||
zip --junk-paths ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe.zip ${{ needs.env_vars.outputs.binary }}.exe
|
||||
$FileHash=(certutil -hashfile ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe.zip SHA256 | findstr /v hash | findstr /v SHA).replace(" ", "")
|
||||
echo "$FileHash" > ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe-zip.sha256
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe.zip
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe.zip
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe-zip.sha256
|
||||
asset_name: ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-windows-exe-zip.sha256
|
||||
asset_content_type: text/plain
|
||||
|
||||
15
.github/workflows/test.yml
vendored
Normal file
15
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- uses: n8maninger/action-golang-test@v1
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,5 +18,8 @@ coverage.html
|
||||
# Vim
|
||||
*.sw?
|
||||
|
||||
# Local JSON files
|
||||
*.json
|
||||
|
||||
# Local TODO
|
||||
TODO.md
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,3 +1,27 @@
|
||||
1.26.5:
|
||||
- provide validator information in "chain status" verbose output
|
||||
|
||||
1.26.4:
|
||||
- provide details of BLS to execution change operations with verbose block output
|
||||
|
||||
1.26.3:
|
||||
- provide support for additional S3 store options
|
||||
- show error when attempting to delete non-filesystem wallets
|
||||
- provide additional support for Capella
|
||||
|
||||
1.26.2
|
||||
- remove check that requires capella prior to generating validator credentials change operations
|
||||
|
||||
1.26.1
|
||||
- add ability to generate validator credentials change operations prior to the fork in which they become usable
|
||||
|
||||
1.26.0
|
||||
- add commands and documentation to set user validator credentials (not usable until capella)
|
||||
|
||||
1.25.3
|
||||
- add more information to "epoch summary"
|
||||
- add "validator summary"
|
||||
|
||||
1.25.2:
|
||||
- no longer require connection parameter
|
||||
- support "block analyze" on bellatrix (thanks @tcrossland)
|
||||
|
||||
30
README.md
30
README.md
@@ -35,7 +35,7 @@ docker pull wealdtech/ethdo
|
||||
`ethdo` is a standard Go program which can be installed with:
|
||||
|
||||
```sh
|
||||
GO111MODULE=on go get github.com/wealdtech/ethdo
|
||||
go install github.com/wealdtech/ethdo@latest
|
||||
```
|
||||
|
||||
Note that `ethdo` requires at least version 1.13 of go to operate. The version of go can be found with `go version`.
|
||||
@@ -64,7 +64,7 @@ Alternatively, if the beacon node is running in a separate docker container a sh
|
||||
|
||||
## Setting up
|
||||
|
||||
`ethdo` needs a connection to a beacon node for many of its features. `ethdo` can connect to any beacon node that fully supports the [standard REST API](https://ethereum.github.io/beacon-APIs/). The following changes are required to beacon nodes to make this available.
|
||||
`ethdo` needs a connection to a beacon node for many of its features. `ethdo` can connect to any beacon node that fully supports the [standard REST API](https://ethereum.github.io/beacon-APIs/) using the `--connection <beacon-node:port>` argument. The following changes are required to beacon nodes to make this available.
|
||||
|
||||
### Lighthouse
|
||||
Lighthouse disables the REST API by default. To enable it, the beacon node must be started with the `--http` parameter. If you want to access the REST API from a remote server then you should also look to change the `--http-address` and `--http-allow-origin` options as per the Lighthouse documentation.
|
||||
@@ -88,7 +88,7 @@ The default port for the REST API is 5051, which can be changed with the `--rest
|
||||
|
||||
## Usage
|
||||
|
||||
`ethdo` contains a large number of features that are useful for day-to-day interactions with the Ethereum 2 blockchain.
|
||||
`ethdo` contains a large number of features that are useful for day-to-day interactions with the different consensus clients.
|
||||
|
||||
### Wallets and accounts
|
||||
|
||||
@@ -98,12 +98,12 @@ The default port for the REST API is 5051, which can be changed with the `--rest
|
||||
- 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 the filesystem store, the additional parameter `base-dir` 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
|
||||
> docker run -v $HOME/.config/ethereum2/wallets:/data ethdo --base-dir=/data wallet list
|
||||
> ```
|
||||
>
|
||||
> This will allow you to use `ethdo` with or without docker, with the same location for wallets and accounts.
|
||||
@@ -138,6 +138,24 @@ ethdo also supports environment variables. Environment variables are prefixed w
|
||||
export ETHDO_PASSPHRASE="my account passphrase"
|
||||
```
|
||||
|
||||
### S3 store options
|
||||
|
||||
Amazon S3-compatible stores have additional options available, which can be configured under the "stores.s3" key. An example configuration is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"stores": {
|
||||
"s3": {
|
||||
"bucket":"mybucketname",
|
||||
"path":"path/in/bucket",
|
||||
"passphrase":"secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Information on these and other options can be found in the S3 store repository.
|
||||
|
||||
### Output and exit status
|
||||
|
||||
If set, the `--quiet` argument will suppress all output.
|
||||
@@ -178,7 +196,7 @@ There is a [HOWTO](https://github.com/wealdtech/ethdo/blob/master/docs/howto.md)
|
||||
|
||||
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.
|
||||
Special thanks to [@SuburbanDad](https://github.com/SuburbanDad) for updating xgo to allow for cross-compilation of `ethdo` releases.
|
||||
|
||||
## Contribute
|
||||
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
// 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 accountcreate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/attestantio/dirk/testing/daemon"
|
||||
"github.com/attestantio/dirk/testing/resources"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
dirk "github.com/wealdtech/go-eth2-wallet-dirk"
|
||||
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"
|
||||
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
testNDWallet, err := nd.CreateWallet(context.Background(),
|
||||
"Test",
|
||||
scratch.New(),
|
||||
keystorev4.New(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
testHDWallet, err := hd.CreateWallet(context.Background(),
|
||||
"Test",
|
||||
[]byte("pass"),
|
||||
scratch.New(),
|
||||
keystorev4.New(),
|
||||
[]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, 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, 0x00, 0x00,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// #nosec G404
|
||||
port1 := uint32(12000 + rand.Intn(4000))
|
||||
// #nosec G404
|
||||
port2 := uint32(12000 + rand.Intn(4000))
|
||||
// #nosec G404
|
||||
port3 := uint32(12000 + rand.Intn(4000))
|
||||
peers := map[uint64]string{
|
||||
1: fmt.Sprintf("signer-test01:%d", port1),
|
||||
2: fmt.Sprintf("signer-test02:%d", port2),
|
||||
3: fmt.Sprintf("signer-test03:%d", port3),
|
||||
}
|
||||
_, path, err := daemon.New(context.Background(), "", 1, port1, peers)
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(path)
|
||||
_, path, err = daemon.New(context.Background(), "", 2, port2, peers)
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(path)
|
||||
_, path, err = daemon.New(context.Background(), "", 3, port3, peers)
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(path)
|
||||
endpoints := []*dirk.Endpoint{
|
||||
dirk.NewEndpoint("signer-test01", port1),
|
||||
dirk.NewEndpoint("signer-test02", port2),
|
||||
dirk.NewEndpoint("signer-test03", port3),
|
||||
}
|
||||
credentials, err := credentialsFromCerts(context.Background(), resources.ClientTest01Crt, resources.ClientTest01Key, resources.CACrt)
|
||||
require.NoError(t, err)
|
||||
testDistributedWallet, err := dirk.OpenWallet(context.Background(), "Wallet 3", credentials, endpoints)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "WalletPassphraseIncorrect",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Good",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "bad",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
},
|
||||
err: "failed to unlock wallet: incorrect passphrase",
|
||||
},
|
||||
{
|
||||
name: "PassphraseMissing",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Good",
|
||||
passphrase: "",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
},
|
||||
err: "passphrase is required",
|
||||
},
|
||||
{
|
||||
name: "PassphraseWeak",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Good",
|
||||
passphrase: "poor",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
},
|
||||
err: "supplied passphrase is weak; use a stronger one or run with the --allow-weak-passphrases flag",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Good",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PathMalformed",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Pathed",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
path: "n/12381/3600/1/2/3",
|
||||
},
|
||||
err: "path does not match expected format m/…",
|
||||
},
|
||||
{
|
||||
name: "PathPassphraseMissing",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Pathed",
|
||||
passphrase: "",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
path: "m/12381/3600/1/2/3",
|
||||
},
|
||||
err: "passphrase is required",
|
||||
},
|
||||
{
|
||||
name: "PathNotSupported",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testNDWallet,
|
||||
accountName: "Pathed",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
path: "m/12381/3600/1/2/3",
|
||||
},
|
||||
err: "wallet does not support account creation with an explicit path",
|
||||
},
|
||||
{
|
||||
name: "GoodWithPath",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testHDWallet,
|
||||
accountName: "Pathed",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 1,
|
||||
signingThreshold: 1,
|
||||
path: "m/12381/3600/1/2/3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DistributedSigningThresholdZero",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testDistributedWallet,
|
||||
accountName: "Remote",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 3,
|
||||
signingThreshold: 0,
|
||||
},
|
||||
err: "signing threshold required",
|
||||
},
|
||||
{
|
||||
name: "DistributedSigningThresholdNotHalf",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testDistributedWallet,
|
||||
accountName: "Remote",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 3,
|
||||
signingThreshold: 1,
|
||||
},
|
||||
err: "signing threshold must be more than half the number of participants",
|
||||
},
|
||||
{
|
||||
name: "DistributedSigningThresholdTooHigh",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testDistributedWallet,
|
||||
accountName: "Remote",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 3,
|
||||
signingThreshold: 4,
|
||||
},
|
||||
err: "signing threshold cannot be higher than the number of participants",
|
||||
},
|
||||
{
|
||||
name: "DistributedNotSupported",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testNDWallet,
|
||||
accountName: "Remote",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 3,
|
||||
signingThreshold: 2,
|
||||
},
|
||||
err: "wallet does not support distributed account creation",
|
||||
},
|
||||
{
|
||||
name: "DistributedGood",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
wallet: testDistributedWallet,
|
||||
accountName: "Remote",
|
||||
passphrase: "ce%NohGhah4ye5ra",
|
||||
walletPassphrase: "pass",
|
||||
participants: 3,
|
||||
signingThreshold: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := process(context.Background(), test.dataIn)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.dataIn.accountName, res.account.Name())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilData(t *testing.T) {
|
||||
_, err := processStandard(context.Background(), nil)
|
||||
require.EqualError(t, err, "no data")
|
||||
_, err = processPathed(context.Background(), nil)
|
||||
require.EqualError(t, err, "no data")
|
||||
_, err = processDistributed(context.Background(), nil)
|
||||
require.EqualError(t, err, "no data")
|
||||
}
|
||||
|
||||
func credentialsFromCerts(ctx context.Context, clientCert []byte, clientKey []byte, caCert []byte) (credentials.TransportCredentials, error) {
|
||||
clientPair, err := tls.X509KeyPair(clientCert, clientKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load client keypair")
|
||||
}
|
||||
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{clientPair},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(caCert) {
|
||||
return nil, errors.New("failed to add CA certificate")
|
||||
}
|
||||
tlsCfg.RootCAs = cp
|
||||
}
|
||||
|
||||
return credentials.NewTLS(tlsCfg), nil
|
||||
}
|
||||
@@ -42,11 +42,13 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
if data.showPrivateKey {
|
||||
builder.WriteString(fmt.Sprintf("Private key: %#x\n", data.key.Marshal()))
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("Public key: %#x", data.key.PublicKey().Marshal()))
|
||||
if data.showWithdrawalCredentials {
|
||||
withdrawalCredentials := util.SHA256(data.key.PublicKey().Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
builder.WriteString(fmt.Sprintf("\nWithdrawal credentials: %#x", withdrawalCredentials))
|
||||
builder.WriteString(fmt.Sprintf("Withdrawal credentials: %#x\n", withdrawalCredentials))
|
||||
}
|
||||
if !(data.showPrivateKey || data.showWithdrawalCredentials) {
|
||||
builder.WriteString(fmt.Sprintf("Public key: %#x\n", data.key.PublicKey().Marshal()))
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestOutput(t *testing.T) {
|
||||
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
|
||||
showPrivateKey: true,
|
||||
},
|
||||
needs: []string{"Public key", "Private key"},
|
||||
needs: []string{"Private key"},
|
||||
},
|
||||
{
|
||||
name: "WithdrawalCredentials",
|
||||
@@ -72,7 +72,7 @@ func TestOutput(t *testing.T) {
|
||||
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
|
||||
showWithdrawalCredentials: true,
|
||||
},
|
||||
needs: []string{"Public key", "Withdrawal credentials"},
|
||||
needs: []string{"Withdrawal credentials"},
|
||||
},
|
||||
{
|
||||
name: "All",
|
||||
@@ -81,7 +81,7 @@ func TestOutput(t *testing.T) {
|
||||
showPrivateKey: true,
|
||||
showWithdrawalCredentials: true,
|
||||
},
|
||||
needs: []string{"Public key", "Private key", "Withdrawal credentials"},
|
||||
needs: []string{"Private key", "Withdrawal credentials"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,57 +15,32 @@ package accountderive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// pathRegex is the regular expression that matches an HD path.
|
||||
var pathRegex = regexp.MustCompile("^m/[0-9]+/[0-9]+(/[0-9+])+")
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
// If there are more than 24 words we treat the additional characters as the passphrase.
|
||||
mnemonicParts := strings.Split(data.mnemonic, " ")
|
||||
mnemonicPassphrase := ""
|
||||
if len(mnemonicParts) > 24 {
|
||||
data.mnemonic = strings.Join(mnemonicParts[:24], " ")
|
||||
mnemonicPassphrase = strings.Join(mnemonicParts[24:], " ")
|
||||
}
|
||||
// Normalise the input.
|
||||
data.mnemonic = string(norm.NFKD.Bytes([]byte(data.mnemonic)))
|
||||
mnemonicPassphrase = string(norm.NFKD.Bytes([]byte(mnemonicPassphrase)))
|
||||
|
||||
if !bip39.IsMnemonicValid(data.mnemonic) {
|
||||
return nil, errors.New("mnemonic is invalid")
|
||||
}
|
||||
|
||||
// Create seed from mnemonic and passphrase.
|
||||
seed := bip39.NewSeed(data.mnemonic, mnemonicPassphrase)
|
||||
|
||||
// Ensure the path is valid.
|
||||
match := pathRegex.Match([]byte(data.path))
|
||||
if !match {
|
||||
return nil, errors.New("path does not match expected format m/…")
|
||||
}
|
||||
|
||||
// Derive private key from seed and path.
|
||||
key, err := util.PrivateKeyFromSeedAndPath(seed, data.path)
|
||||
account, err := util.ParseAccount(ctx, data.mnemonic, []string{data.path}, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate key")
|
||||
return nil, errors.Wrap(err, "failed to derive account")
|
||||
}
|
||||
|
||||
key, err := account.(e2wtypes.AccountPrivateKeyProvider).PrivateKey(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account private key")
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
showPrivateKey: data.showPrivateKey,
|
||||
showPrivateKey: data.showPrivateKey,
|
||||
showWithdrawalCredentials: data.showWithdrawalCredentials,
|
||||
key: key,
|
||||
key: key.(*e2types.BLSPrivateKey),
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestProcess(t *testing.T) {
|
||||
dataIn: &dataIn{
|
||||
path: "m/12381/3600/0/0",
|
||||
},
|
||||
err: "mnemonic is invalid",
|
||||
err: "failed to derive account: no account specified",
|
||||
},
|
||||
{
|
||||
name: "MnemonicInvalid",
|
||||
@@ -48,14 +48,14 @@ func TestProcess(t *testing.T) {
|
||||
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
path: "m/12381/3600/0/0",
|
||||
},
|
||||
err: "mnemonic is invalid",
|
||||
err: "failed to derive account: mnemonic is invalid",
|
||||
},
|
||||
{
|
||||
name: "PathMissing",
|
||||
dataIn: &dataIn{
|
||||
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
},
|
||||
err: "path does not match expected format m/…",
|
||||
err: "failed to derive account: path does not match expected format m/…",
|
||||
},
|
||||
{
|
||||
name: "PathInvalid",
|
||||
@@ -63,7 +63,7 @@ func TestProcess(t *testing.T) {
|
||||
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
path: "n/12381/3600/0/0",
|
||||
},
|
||||
err: "path does not match expected format m/…",
|
||||
err: "failed to derive account: path does not match expected format m/…",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
|
||||
@@ -15,6 +15,7 @@ package accountderive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -45,5 +46,5 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
return strings.TrimSuffix(results, "\n"), nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package accountimport
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -115,7 +115,7 @@ func obtainKeystore(input string) ([]byte, error) {
|
||||
data = []byte(input)
|
||||
} else {
|
||||
// Assume it's a path to JSON
|
||||
data, err = ioutil.ReadFile(input)
|
||||
data, err = os.ReadFile(input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ func init() {
|
||||
accountFlags(accountCreateCmd)
|
||||
accountCreateCmd.Flags().Uint32("participants", 1, "Number of participants (1 for non-distributed accounts, >1 for distributed accounts)")
|
||||
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
|
||||
accountCreateCmd.Flags().String("path", "", "path of account (for hierarchical deterministic accounts)")
|
||||
}
|
||||
|
||||
func accountCreateBindings() {
|
||||
@@ -59,7 +58,4 @@ func accountCreateBindings() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,19 +47,11 @@ In quiet mode this will return 0 if the inputs can derive an account account, ot
|
||||
func init() {
|
||||
accountCmd.AddCommand(accountDeriveCmd)
|
||||
accountFlags(accountDeriveCmd)
|
||||
accountDeriveCmd.Flags().String("mnemonic", "", "mnemonic from which to derive the HD seed")
|
||||
accountDeriveCmd.Flags().String("path", "", "path from which to derive the account")
|
||||
accountDeriveCmd.Flags().Bool("show-private-key", false, "show private key for derived account")
|
||||
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
|
||||
}
|
||||
|
||||
func accountDeriveBindings() {
|
||||
if err := viper.BindPFlag("mnemonic", accountDeriveCmd.Flags().Lookup("mnemonic")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("path", accountDeriveCmd.Flags().Lookup("path")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("show-private-key", accountDeriveCmd.Flags().Lookup("show-private-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -432,6 +432,13 @@ func (c *command) analyzeSyncCommittees(ctx context.Context, block *spec.Version
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
case spec.DataVersionCapella:
|
||||
c.analysis.SyncCommitee.Contributions = int(block.Capella.Message.Body.SyncAggregate.SyncCommitteeBits.Count())
|
||||
c.analysis.SyncCommitee.PossibleContributions = int(block.Capella.Message.Body.SyncAggregate.SyncCommitteeBits.Len())
|
||||
c.analysis.SyncCommitee.Score = float64(c.syncRewardWeight) / float64(c.weightDenominator)
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported block version %d", block.Version)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
@@ -223,7 +224,7 @@ func outputBlockVoluntaryExits(ctx context.Context, eth2Client eth2client.Servic
|
||||
if err != nil {
|
||||
res.WriteString(fmt.Sprintf(" Error: failed to obtain validators: %v\n", err))
|
||||
} else {
|
||||
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[0].Validator.PublicKey, voluntaryExit.Message.ValidatorIndex))
|
||||
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[voluntaryExit.Message.ValidatorIndex].Validator.PublicKey, voluntaryExit.Message.ValidatorIndex))
|
||||
res.WriteString(fmt.Sprintf(" Epoch: %d\n", voluntaryExit.Message.Epoch))
|
||||
}
|
||||
}
|
||||
@@ -232,6 +233,27 @@ func outputBlockVoluntaryExits(ctx context.Context, eth2Client eth2client.Servic
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockBLSToExecutionChanges(ctx context.Context, eth2Client eth2client.Service, verbose bool, ops []*capella.SignedBLSToExecutionChange) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString(fmt.Sprintf("BLS to execution changes: %d\n", len(ops)))
|
||||
if verbose {
|
||||
for i, op := range ops {
|
||||
res.WriteString(fmt.Sprintf(" %d:\n", i))
|
||||
validators, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, "head", []phase0.ValidatorIndex{op.Message.ValidatorIndex})
|
||||
if err != nil {
|
||||
res.WriteString(fmt.Sprintf(" Error: failed to obtain validators: %v\n", err))
|
||||
} else {
|
||||
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[op.Message.ValidatorIndex].Validator.PublicKey, op.Message.ValidatorIndex))
|
||||
res.WriteString(fmt.Sprintf(" BLS public key: %#x\n", op.Message.FromBLSPubkey))
|
||||
res.WriteString(fmt.Sprintf(" Execution address: %#x\n", op.Message.ToExecutionAddress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service, verbose bool, syncAggregate *altair.SyncAggregate, epoch phase0.Epoch) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
@@ -270,7 +292,7 @@ func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *bellatrix.SignedBeaconBlock) (string, error) {
|
||||
func outputCapellaBlockText(ctx context.Context, data *dataOut, signedBlock *capella.SignedBeaconBlock) (string, error) {
|
||||
if signedBlock == nil {
|
||||
return "", errors.New("no block supplied")
|
||||
}
|
||||
@@ -296,7 +318,7 @@ func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *b
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
signedBlock.Message.Body.Graffiti,
|
||||
signedBlock.Message.Body.Graffiti[:],
|
||||
data.genesisTime,
|
||||
data.slotDuration,
|
||||
data.slotsPerEpoch)
|
||||
@@ -351,7 +373,103 @@ func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *b
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
|
||||
tmp, err = outputBlockBLSToExecutionChanges(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.BLSToExecutionChanges)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputCapellaBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *bellatrix.SignedBeaconBlock) (string, error) {
|
||||
if signedBlock == nil {
|
||||
return "", errors.New("no block supplied")
|
||||
}
|
||||
|
||||
body := signedBlock.Message.Body
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
// General info.
|
||||
blockRoot, err := signedBlock.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain block root")
|
||||
}
|
||||
bodyRoot, err := signedBlock.Message.Body.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to generate body root")
|
||||
}
|
||||
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
signedBlock.Message.Body.Graffiti[:],
|
||||
data.genesisTime,
|
||||
data.slotDuration,
|
||||
data.slotsPerEpoch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Eth1 data.
|
||||
if data.verbose {
|
||||
tmp, err := outputBlockETH1Data(ctx, body.ETH1Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
}
|
||||
|
||||
// Sync aggregate.
|
||||
tmp, err = outputBlockSyncAggregate(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.SyncAggregate, phase0.Epoch(uint64(signedBlock.Message.Slot)/data.slotsPerEpoch))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Attestations.
|
||||
tmp, err = outputBlockAttestations(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.Attestations)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Attester slashings.
|
||||
tmp, err = outputBlockAttesterSlashings(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.AttesterSlashings)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
res.WriteString(fmt.Sprintf("Proposer slashings: %d\n", len(body.ProposerSlashings)))
|
||||
// Add verbose proposer slashings.
|
||||
|
||||
tmp, err = outputBlockDeposits(ctx, data.verbose, signedBlock.Message.Body.Deposits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Voluntary exits.
|
||||
tmp, err = outputBlockVoluntaryExits(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.VoluntaryExits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputBellatrixBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -386,7 +504,7 @@ func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *alta
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
signedBlock.Message.Body.Graffiti,
|
||||
signedBlock.Message.Body.Graffiti[:],
|
||||
data.genesisTime,
|
||||
data.slotDuration,
|
||||
data.slotsPerEpoch)
|
||||
@@ -469,7 +587,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
signedBlock.Message.Body.Graffiti,
|
||||
signedBlock.Message.Body.Graffiti[:],
|
||||
data.genesisTime,
|
||||
data.slotDuration,
|
||||
data.slotsPerEpoch)
|
||||
@@ -520,7 +638,71 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockExecutionPayload(ctx context.Context,
|
||||
func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
verbose bool,
|
||||
payload *capella.ExecutionPayload,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if payload == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// If the block number is 0 then we're before the merge.
|
||||
if payload.BlockNumber == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
if !verbose {
|
||||
res.WriteString("Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
res.WriteString("Transactions: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
|
||||
} else {
|
||||
res.WriteString("Execution payload:\n")
|
||||
res.WriteString(" Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
baseFeePerGasBEBytes := make([]byte, len(payload.BaseFeePerGas))
|
||||
for i := 0; i < 32; i++ {
|
||||
baseFeePerGasBEBytes[i] = payload.BaseFeePerGas[32-1-i]
|
||||
}
|
||||
baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBEBytes)
|
||||
res.WriteString(" Base fee per gas: ")
|
||||
res.WriteString(string2eth.WeiToString(baseFeePerGas, true))
|
||||
res.WriteString("\n Block hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.BlockHash))
|
||||
res.WriteString(" Parent hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash))
|
||||
res.WriteString(" Fee recipient: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.FeeRecipient))
|
||||
res.WriteString(" Gas limit: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit))
|
||||
res.WriteString(" Gas used: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasUsed))
|
||||
res.WriteString(" Timestamp: ")
|
||||
res.WriteString(fmt.Sprintf("%s (%d)\n", time.Unix(int64(payload.Timestamp), 0).String(), payload.Timestamp))
|
||||
res.WriteString(" Prev RANDAO: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.PrevRandao))
|
||||
res.WriteString(" Receipts root: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ReceiptsRoot))
|
||||
res.WriteString(" State root: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.StateRoot))
|
||||
res.WriteString(" Extra data: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ExtraData))
|
||||
res.WriteString(" Logs bloom: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.LogsBloom))
|
||||
res.WriteString(" Transactions: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
|
||||
res.WriteString(" Withdrawals: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Withdrawals)))
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockExecutionPayload(ctx context.Context,
|
||||
verbose bool,
|
||||
payload *bellatrix.ExecutionPayload,
|
||||
) (
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -75,6 +76,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Bellatrix); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
case spec.DataVersionCapella:
|
||||
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Capella); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown block version")
|
||||
}
|
||||
@@ -137,6 +142,13 @@ func headEventHandler(event *api.Event) {
|
||||
}
|
||||
return
|
||||
}
|
||||
case spec.DataVersionCapella:
|
||||
if err := outputCapellaBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Capella); err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Unknown block version: %v\n", signedBlock.Version)
|
||||
@@ -213,3 +225,27 @@ func outputBellatrixBlock(ctx context.Context, jsonOutput bool, sszOutput bool,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputCapellaBlock(ctx context.Context, jsonOutput bool, sszOutput bool, signedBlock *capella.SignedBeaconBlock) error {
|
||||
switch {
|
||||
case jsonOutput:
|
||||
data, err := json.Marshal(signedBlock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate JSON")
|
||||
}
|
||||
fmt.Printf("%s\n", string(data))
|
||||
case sszOutput:
|
||||
data, err := signedBlock.MarshalSSZ()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate SSZ")
|
||||
}
|
||||
fmt.Printf("%x\n", data)
|
||||
default:
|
||||
data, err := outputCapellaBlockText(ctx, results, signedBlock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate text")
|
||||
}
|
||||
fmt.Print(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -79,13 +79,17 @@ func (c *command) process(ctx context.Context) error {
|
||||
c.incumbent = state.Phase0.ETH1Data
|
||||
c.eth1DataVotes = state.Phase0.ETH1DataVotes
|
||||
case spec.DataVersionAltair:
|
||||
c.slot = phase0.Slot(state.Altair.Slot)
|
||||
c.slot = state.Altair.Slot
|
||||
c.incumbent = state.Altair.ETH1Data
|
||||
c.eth1DataVotes = state.Altair.ETH1DataVotes
|
||||
case spec.DataVersionBellatrix:
|
||||
c.slot = phase0.Slot(state.Bellatrix.Slot)
|
||||
c.slot = state.Bellatrix.Slot
|
||||
c.incumbent = state.Bellatrix.ETH1Data
|
||||
c.eth1DataVotes = state.Bellatrix.ETH1DataVotes
|
||||
case spec.DataVersionCapella:
|
||||
c.slot = state.Capella.Slot
|
||||
c.incumbent = state.Capella.ETH1Data
|
||||
c.eth1DataVotes = state.Capella.ETH1DataVotes
|
||||
default:
|
||||
return fmt.Errorf("unhandled beacon state version %v", state.Version)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2022 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
|
||||
@@ -53,6 +53,11 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if viper.GetBool("prepare-offline") {
|
||||
fmt.Printf("Add the following to your command to run it offline:\n --offline --genesis-validators=root=%#x --fork-version=%#x\n", genesis.GenesisValidatorsRoot, fork.CurrentVersion)
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if genesis.GenesisTime.Unix() == 0 {
|
||||
fmt.Println("Genesis time: undefined")
|
||||
} else {
|
||||
@@ -84,4 +89,11 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainInfoCmd)
|
||||
chainFlags(chainInfoCmd)
|
||||
chainInfoCmd.Flags().Bool("prepare-offline", false, "Provide information useful for offline commands")
|
||||
}
|
||||
|
||||
func chainInfoBindings() {
|
||||
if err := viper.BindPFlag("prepare-offline", chainInfoCmd.Flags().Lookup("prepare-offline")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,13 @@ import (
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
var chainStatusCmd = &cobra.Command{
|
||||
@@ -116,6 +119,34 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
if verbose {
|
||||
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
|
||||
if isProvider {
|
||||
validators, err := validatorsProvider.Validators(ctx, "head", nil)
|
||||
errCheck(err, "Failed to obtain validators information")
|
||||
// Stats of inteest.
|
||||
totalBalance := phase0.Gwei(0)
|
||||
activeEffectiveBalance := phase0.Gwei(0)
|
||||
validatorCount := make(map[apiv1.ValidatorState]int)
|
||||
for _, validator := range validators {
|
||||
validatorCount[validator.Status]++
|
||||
totalBalance += validator.Balance
|
||||
if validator.Status.IsActive() {
|
||||
activeEffectiveBalance += validator.Validator.EffectiveBalance
|
||||
}
|
||||
}
|
||||
res.WriteString(fmt.Sprintf("Total balance: %s\n", string2eth.GWeiToString(uint64(totalBalance), true)))
|
||||
res.WriteString(fmt.Sprintf("Active effective balance: %s\n", string2eth.GWeiToString(uint64(activeEffectiveBalance), true)))
|
||||
res.WriteString("Validator states:\n")
|
||||
res.WriteString(fmt.Sprintf(" Pending: %d\n", validatorCount[apiv1.ValidatorStatePendingInitialized]))
|
||||
res.WriteString(fmt.Sprintf(" Activating: %d\n", validatorCount[apiv1.ValidatorStatePendingQueued]))
|
||||
res.WriteString(fmt.Sprintf(" Active: %d\n", validatorCount[apiv1.ValidatorStateActiveOngoing]+validatorCount[apiv1.ValidatorStateActiveSlashed]))
|
||||
res.WriteString(fmt.Sprintf(" Exiting: %d\n", validatorCount[apiv1.ValidatorStateActiveExiting]))
|
||||
res.WriteString(fmt.Sprintf(" Exited: %d\n", validatorCount[apiv1.ValidatorStateExitedUnslashed]+validatorCount[apiv1.ValidatorStateExitedSlashed]+validatorCount[apiv1.ValidatorStateWithdrawalPossible]+validatorCount[apiv1.ValidatorStateWithdrawalDone]))
|
||||
res.WriteString(fmt.Sprintf(" Unknown: %d\n", validatorCount[apiv1.ValidatorStateUnknown]))
|
||||
}
|
||||
}
|
||||
|
||||
if epoch >= chainTime.AltairInitialEpoch() {
|
||||
period := chainTime.SlotToSyncCommitteePeriod(slot)
|
||||
periodStartEpoch := chainTime.FirstEpochOfSyncPeriod(period)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019-2021 Weald Technology Limited.
|
||||
// Copyright © 2019-2022 Weald Technology Limited.
|
||||
// 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
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -64,7 +63,7 @@ In quiet mode this will return 0 if the the data is verified correctly, otherwis
|
||||
data = []byte(depositVerifyData)
|
||||
default:
|
||||
// Assume it's a path to JSON.
|
||||
data, err = ioutil.ReadFile(depositVerifyData)
|
||||
data, err = os.ReadFile(depositVerifyData)
|
||||
errCheck(err, "Failed to read deposit data file")
|
||||
if data[0] == '{' {
|
||||
data = []byte("[" + string(data) + "]")
|
||||
@@ -155,7 +154,7 @@ func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) {
|
||||
pubKeys[key] = true
|
||||
} else {
|
||||
// Assume it's a path to a file of public keys.
|
||||
data, err = ioutil.ReadFile(input)
|
||||
data, err = os.ReadFile(input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find public key file")
|
||||
}
|
||||
|
||||
@@ -40,13 +40,14 @@ type command struct {
|
||||
jsonOutput bool
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
proposerDutiesProvider eth2client.ProposerDutiesProvider
|
||||
blocksProvider eth2client.SignedBeaconBlockProvider
|
||||
syncCommitteesProvider eth2client.SyncCommitteesProvider
|
||||
validatorsProvider eth2client.ValidatorsProvider
|
||||
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
proposerDutiesProvider eth2client.ProposerDutiesProvider
|
||||
blocksProvider eth2client.SignedBeaconBlockProvider
|
||||
syncCommitteesProvider eth2client.SyncCommitteesProvider
|
||||
validatorsProvider eth2client.ValidatorsProvider
|
||||
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
||||
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||
|
||||
// Results.
|
||||
summary *epochSummary
|
||||
@@ -60,6 +61,11 @@ type epochSummary struct {
|
||||
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
||||
ActiveValidators int `json:"active_validators"`
|
||||
ParticipatingValidators int `json:"participating_validators"`
|
||||
HeadCorrectValidators int `json:"head_correct_validators"`
|
||||
HeadTimelyValidators int `json:"head_timely_validators"`
|
||||
SourceTimelyValidators int `json:"source_timely_validators"`
|
||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
}
|
||||
|
||||
builder.WriteString(fmt.Sprintf("\n Attestations: %d/%d (%0.2f%%)", c.summary.ParticipatingValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.ParticipatingValidators)/float64(c.summary.ActiveValidators)))
|
||||
builder.WriteString(fmt.Sprintf("\n Source timely: %d/%d (%0.2f%%)", c.summary.SourceTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.SourceTimelyValidators)/float64(c.summary.ActiveValidators)))
|
||||
builder.WriteString(fmt.Sprintf("\n Target correct: %d/%d (%0.2f%%)", c.summary.TargetCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetCorrectValidators)/float64(c.summary.ActiveValidators)))
|
||||
builder.WriteString(fmt.Sprintf("\n Target timely: %d/%d (%0.2f%%)", c.summary.TargetTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetTimelyValidators)/float64(c.summary.ActiveValidators)))
|
||||
builder.WriteString(fmt.Sprintf("\n Head correct: %d/%d (%0.2f%%)", c.summary.HeadCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadCorrectValidators)/float64(c.summary.ActiveValidators)))
|
||||
builder.WriteString(fmt.Sprintf("\n Head timely: %d/%d (%0.2f%%)", c.summary.HeadTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadTimelyValidators)/float64(c.summary.ActiveValidators)))
|
||||
if c.verbose {
|
||||
// Sort list by validator index.
|
||||
for _, validator := range c.summary.NonParticipatingValidators {
|
||||
|
||||
@@ -79,11 +79,10 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
// Obtain all active validators for the given epoch.
|
||||
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
|
||||
validators, err := c.validatorsProvider.Validators(ctx, fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)), nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validators for epoch")
|
||||
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
|
||||
}
|
||||
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
||||
for _, validator := range validators {
|
||||
@@ -92,6 +91,15 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
return activeValidators, nil
|
||||
}
|
||||
func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
activeValidators, err := c.activeValidators(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.summary.ActiveValidators = len(activeValidators)
|
||||
|
||||
// Obtain number of validators that voted for blocks in the epoch.
|
||||
// These votes can be included anywhere from the second slot of
|
||||
// the epoch to the first slot of the next-but-one epoch.
|
||||
@@ -101,61 +109,13 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
lastSlot = c.chainTime.CurrentSlot()
|
||||
}
|
||||
|
||||
votes := make(map[phase0.ValidatorIndex]struct{})
|
||||
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
participations := make(map[phase0.ValidatorIndex]*nonParticipatingValidator)
|
||||
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
if block == nil {
|
||||
// No block at this slot; that's fine.
|
||||
continue
|
||||
}
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
||||
// Outside of this epoch's range.
|
||||
continue
|
||||
}
|
||||
slotCommittees, exists := allCommittees[attestation.Data.Slot]
|
||||
if !exists {
|
||||
beaconCommittees, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, fmt.Sprintf("%d", attestation.Data.Slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
|
||||
}
|
||||
for _, beaconCommittee := range beaconCommittees {
|
||||
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
||||
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
}
|
||||
allCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
||||
for _, index := range beaconCommittee.Validators {
|
||||
participations[index] = &nonParticipatingValidator{
|
||||
Validator: index,
|
||||
Slot: beaconCommittee.Slot,
|
||||
Committee: beaconCommittee.Index,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
slotCommittees = allCommittees[attestation.Data.Slot]
|
||||
}
|
||||
committee := slotCommittees[attestation.Data.Index]
|
||||
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
|
||||
if attestation.AggregationBits.BitAt(i) {
|
||||
votes[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var votes map[phase0.ValidatorIndex]struct{}
|
||||
var participations map[phase0.ValidatorIndex]*nonParticipatingValidator
|
||||
c.summary.ParticipatingValidators, c.summary.HeadCorrectValidators, c.summary.HeadTimelyValidators, c.summary.SourceTimelyValidators, c.summary.TargetCorrectValidators, c.summary.TargetTimelyValidators, votes, participations, err = c.processSlots(ctx, firstSlot, lastSlot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.summary.ActiveValidators = len(activeValidators)
|
||||
c.summary.ParticipatingValidators = len(votes)
|
||||
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0, len(activeValidators)-len(votes))
|
||||
for activeValidatorIndex := range activeValidators {
|
||||
if _, exists := votes[activeValidatorIndex]; !exists {
|
||||
@@ -177,6 +137,121 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processSlots(ctx context.Context,
|
||||
firstSlot phase0.Slot,
|
||||
lastSlot phase0.Slot,
|
||||
) (
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]*nonParticipatingValidator,
|
||||
error,
|
||||
) {
|
||||
votes := make(map[phase0.ValidatorIndex]struct{})
|
||||
headCorrects := make(map[phase0.ValidatorIndex]struct{})
|
||||
headTimelys := make(map[phase0.ValidatorIndex]struct{})
|
||||
sourceTimelys := make(map[phase0.ValidatorIndex]struct{})
|
||||
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
|
||||
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
|
||||
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
participations := make(map[phase0.ValidatorIndex]*nonParticipatingValidator)
|
||||
|
||||
// Need a cache of beacon block headers to reduce lookup times.
|
||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
if block == nil {
|
||||
// No block at this slot; that's fine.
|
||||
continue
|
||||
}
|
||||
slot, err := block.Slot()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
}
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
||||
// Outside of this epoch's range.
|
||||
continue
|
||||
}
|
||||
slotCommittees, exists := allCommittees[attestation.Data.Slot]
|
||||
if !exists {
|
||||
beaconCommittees, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, fmt.Sprintf("%d", attestation.Data.Slot))
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
|
||||
}
|
||||
for _, beaconCommittee := range beaconCommittees {
|
||||
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
||||
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
}
|
||||
allCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
||||
for _, index := range beaconCommittee.Validators {
|
||||
participations[index] = &nonParticipatingValidator{
|
||||
Validator: index,
|
||||
Slot: beaconCommittee.Slot,
|
||||
Committee: beaconCommittee.Index,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
slotCommittees = allCommittees[attestation.Data.Slot]
|
||||
}
|
||||
committee := slotCommittees[attestation.Data.Index]
|
||||
|
||||
inclusionDistance := slot - attestation.Data.Slot
|
||||
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
}
|
||||
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
}
|
||||
|
||||
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
|
||||
if attestation.AggregationBits.BitAt(i) {
|
||||
votes[committee[int(i)]] = struct{}{}
|
||||
if _, exists := headCorrects[committee[int(i)]]; !exists && headCorrect {
|
||||
headCorrects[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
if _, exists := headTimelys[committee[int(i)]]; !exists && headCorrect && inclusionDistance == 1 {
|
||||
headTimelys[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
if _, exists := sourceTimelys[committee[int(i)]]; !exists && inclusionDistance <= 5 {
|
||||
sourceTimelys[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
if _, exists := targetCorrects[committee[int(i)]]; !exists && targetCorrect {
|
||||
targetCorrects[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
if _, exists := targetTimelys[committee[int(i)]]; !exists && targetCorrect && inclusionDistance <= 32 {
|
||||
targetTimelys[committee[int(i)]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(votes),
|
||||
len(headCorrects),
|
||||
len(headTimelys),
|
||||
len(sourceTimelys),
|
||||
len(targetCorrects),
|
||||
len(targetTimelys),
|
||||
votes,
|
||||
participations,
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
if c.summary.Epoch < c.chainTime.AltairInitialEpoch() {
|
||||
// The epoch is pre-Altair. No info but no error.
|
||||
@@ -214,6 +289,8 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
aggregate = block.Altair.Message.Body.SyncAggregate
|
||||
case spec.DataVersionBellatrix:
|
||||
aggregate = block.Bellatrix.Message.Body.SyncAggregate
|
||||
case spec.DataVersionCapella:
|
||||
aggregate = block.Capella.Message.Body.SyncAggregate
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
@@ -286,6 +363,10 @@ func (c *command) setup(ctx context.Context) error {
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide beacon committees")
|
||||
}
|
||||
c.beaconBlockHeadersProvider, isProvider = c.eth2Client.(eth2client.BeaconBlockHeadersProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide beacon block headers")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2022 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
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -97,7 +96,7 @@ func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
||||
data = []byte(input)
|
||||
} else {
|
||||
// Assume it's a path to JSON
|
||||
data, err = ioutil.ReadFile(input)
|
||||
data, err = os.ReadFile(input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||
}
|
||||
|
||||
42
cmd/root.go
42
cmd/root.go
@@ -96,6 +96,8 @@ func includeCommandBindings(cmd *cobra.Command) {
|
||||
blockInfoBindings()
|
||||
case "chain/eth1votes":
|
||||
chainEth1VotesBindings()
|
||||
case "chain/info":
|
||||
chainInfoBindings()
|
||||
case "chain/queues":
|
||||
chainQueuesBindings()
|
||||
case "chain/time":
|
||||
@@ -118,6 +120,8 @@ func includeCommandBindings(cmd *cobra.Command) {
|
||||
synccommitteeMembersBindings()
|
||||
case "validator/credentials/get":
|
||||
validatorCredentialsGetBindings()
|
||||
case "validator/credentials/set":
|
||||
validatorCredentialsSetBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "validator/duties":
|
||||
@@ -128,6 +132,8 @@ func includeCommandBindings(cmd *cobra.Command) {
|
||||
validatorInfoBindings()
|
||||
case "validator/keycheck":
|
||||
validatorKeycheckBindings()
|
||||
case "validator/summary":
|
||||
validatorSummaryBindings()
|
||||
case "validator/yield":
|
||||
validatorYieldBindings()
|
||||
case "validator/expectation":
|
||||
@@ -169,10 +175,26 @@ func init() {
|
||||
if err := viper.BindPFlag("store", RootCmd.PersistentFlags().Lookup("store")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("account", "", "Account name (in format \"wallet/account\")")
|
||||
RootCmd.PersistentFlags().String("account", "", `Account name (in format "<wallet>/<account>")`)
|
||||
if err := viper.BindPFlag("account", RootCmd.PersistentFlags().Lookup("account")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("mnemonic", "", "Mnemonic to provide access to an account")
|
||||
if err := viper.BindPFlag("mnemonic", RootCmd.PersistentFlags().Lookup("mnemonic")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("path", "", "Hierarchical derivation path used with mnemonic to provide access to an account")
|
||||
if err := viper.BindPFlag("path", RootCmd.PersistentFlags().Lookup("path")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("private-key", "", "Private key to provide access to an account")
|
||||
if err := viper.BindPFlag("private-key", RootCmd.PersistentFlags().Lookup("private-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("public-key", "", "public key to provide access to an account")
|
||||
if err := viper.BindPFlag("public-key", RootCmd.PersistentFlags().Lookup("public-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("basedir", "", "Base directory for filesystem wallets")
|
||||
if err := viper.BindPFlag("basedir", RootCmd.PersistentFlags().Lookup("basedir")); err != nil {
|
||||
panic(err)
|
||||
@@ -375,24 +397,6 @@ func walletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet
|
||||
return wallet, account, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestOutput(t *testing.T) {
|
||||
dataOut: &dataOut{
|
||||
startTime: time.Unix(1606824023, 0),
|
||||
},
|
||||
res: "2020-12-01 12:00:23 +0000 GMT",
|
||||
res: "2020-12-01 12:00:23 +0000 UTC",
|
||||
},
|
||||
{
|
||||
name: "Verbose",
|
||||
@@ -46,7 +46,7 @@ func TestOutput(t *testing.T) {
|
||||
endTime: time.Unix(1606824035, 0),
|
||||
verbose: true,
|
||||
},
|
||||
res: "2020-12-01 12:00:23 +0000 GMT - 2020-12-01 12:00:35 +0000 GMT",
|
||||
res: "2020-12-01 12:00:23 +0000 UTC - 2020-12-01 12:00:35 +0000 UTC",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,13 @@ func (c *command) process(ctx context.Context) error {
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
case spec.DataVersionCapella:
|
||||
aggregate = block.Capella.Message.Body.SyncAggregate
|
||||
if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) {
|
||||
c.inclusions = append(c.inclusions, 1)
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
|
||||
@@ -30,3 +30,6 @@ func init() {
|
||||
|
||||
func validatorFlags(cmd *cobra.Command) {
|
||||
}
|
||||
|
||||
func validatorBindings() {
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ type command struct {
|
||||
debug bool
|
||||
|
||||
// Input.
|
||||
account string
|
||||
index string
|
||||
pubKey string
|
||||
validator string
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
@@ -43,7 +41,7 @@ type command struct {
|
||||
validatorsProvider eth2client.ValidatorsProvider
|
||||
|
||||
// Output.
|
||||
validator *apiv1.Validator
|
||||
validatorInfo *apiv1.Validator
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
@@ -62,25 +60,10 @@ func newCommand(ctx context.Context) (*command, error) {
|
||||
c.connection = viper.GetString("connection")
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
c.account = viper.GetString("account")
|
||||
c.index = viper.GetString("index")
|
||||
c.pubKey = viper.GetString("pubkey")
|
||||
nonNil := 0
|
||||
if c.account != "" {
|
||||
nonNil++
|
||||
}
|
||||
if c.index != "" {
|
||||
nonNil++
|
||||
}
|
||||
if c.pubKey != "" {
|
||||
nonNil++
|
||||
}
|
||||
if nonNil == 0 {
|
||||
return nil, errors.New("one of account, index or pubkey required")
|
||||
}
|
||||
if nonNil > 1 {
|
||||
return nil, errors.New("only one of account, index and pubkey allowed")
|
||||
if viper.GetString("validator") == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
c.validator = viper.GetString("validator")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ethutil "github.com/wealdtech/go-eth2-util"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
@@ -26,19 +28,38 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
switch c.validator.Validator.WithdrawalCredentials[0] {
|
||||
switch c.validatorInfo.Validator.WithdrawalCredentials[0] {
|
||||
case 0:
|
||||
builder.WriteString("BLS credentials: ")
|
||||
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials))
|
||||
builder.WriteString(fmt.Sprintf("%#x", c.validatorInfo.Validator.WithdrawalCredentials))
|
||||
case 1:
|
||||
builder.WriteString("Ethereum execution address: ")
|
||||
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials[12:]))
|
||||
builder.WriteString(addressBytesToEIP55(c.validatorInfo.Validator.WithdrawalCredentials[12:]))
|
||||
if c.verbose {
|
||||
builder.WriteString("\n")
|
||||
builder.WriteString("Withdrawal credentials: ")
|
||||
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials))
|
||||
builder.WriteString(fmt.Sprintf("%#x", c.validatorInfo.Validator.WithdrawalCredentials))
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
|
||||
func addressBytesToEIP55(address []byte) string {
|
||||
bytes := []byte(fmt.Sprintf("%x", address))
|
||||
hash := ethutil.Keccak256(bytes)
|
||||
for i := 0; i < len(bytes); i++ {
|
||||
hashByte := hash[i/2]
|
||||
if i%2 == 0 {
|
||||
hashByte >>= 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if bytes[i] > '9' && hashByte > 7 {
|
||||
bytes[i] -= 32
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("0x%s", string(bytes))
|
||||
}
|
||||
|
||||
@@ -15,14 +15,10 @@ package validatorcredentialsget
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
@@ -68,71 +64,10 @@ func (c *command) setup(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *command) fetchValidator(ctx context.Context) error {
|
||||
if c.account != "" {
|
||||
_, account, err := util.WalletAndAccountFromInput(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to obtain account")
|
||||
}
|
||||
|
||||
accPubKey, err := util.BestPublicKey(account)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to obtain public key for account")
|
||||
}
|
||||
pubKey := phase0.BLSPubKey{}
|
||||
copy(pubKey[:], accPubKey.Marshal())
|
||||
validators, err := c.validatorsProvider.ValidatorsByPubKey(ctx,
|
||||
"head",
|
||||
[]phase0.BLSPubKey{pubKey},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return errors.New("unknown validator")
|
||||
}
|
||||
for _, validator := range validators {
|
||||
c.validator = validator
|
||||
}
|
||||
}
|
||||
if c.index != "" {
|
||||
tmp, err := strconv.ParseUint(c.index, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid validator index")
|
||||
}
|
||||
index := phase0.ValidatorIndex(tmp)
|
||||
validators, err := c.validatorsProvider.Validators(ctx,
|
||||
"head",
|
||||
[]phase0.ValidatorIndex{index},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
if _, exists := validators[index]; !exists {
|
||||
return errors.New("unknown validator")
|
||||
}
|
||||
c.validator = validators[index]
|
||||
}
|
||||
if c.pubKey != "" {
|
||||
bytes, err := hex.DecodeString(strings.TrimPrefix(c.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid validator public key")
|
||||
}
|
||||
pubKey := phase0.BLSPubKey{}
|
||||
copy(pubKey[:], bytes)
|
||||
|
||||
validators, err := c.validatorsProvider.ValidatorsByPubKey(ctx,
|
||||
"head",
|
||||
[]phase0.BLSPubKey{pubKey},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return errors.New("unknown validator")
|
||||
}
|
||||
for _, validator := range validators {
|
||||
c.validator = validator
|
||||
}
|
||||
var err error
|
||||
c.validatorInfo, err = util.ParseValidator(ctx, c.validatorsProvider, c.validator, "head")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
127
cmd/validator/credentials/set/chaininfo.go
Normal file
127
cmd/validator/credentials/set/chaininfo.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type chainInfo struct {
|
||||
Version uint64
|
||||
Validators []*validatorInfo
|
||||
GenesisValidatorsRoot phase0.Root
|
||||
Epoch phase0.Epoch
|
||||
ForkVersion phase0.Version
|
||||
Domain phase0.Domain
|
||||
}
|
||||
|
||||
type chainInfoJSON struct {
|
||||
Version string `json:"version"`
|
||||
Validators []*validatorInfo `json:"validators"`
|
||||
GenesisValidatorsRoot string `json:"genesis_validators_root"`
|
||||
Epoch string `json:"epoch"`
|
||||
ForkVersion string `json:"fork_version"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v *chainInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&chainInfoJSON{
|
||||
Version: fmt.Sprintf("%d", v.Version),
|
||||
Validators: v.Validators,
|
||||
GenesisValidatorsRoot: fmt.Sprintf("%#x", v.GenesisValidatorsRoot),
|
||||
Epoch: fmt.Sprintf("%d", v.Epoch),
|
||||
ForkVersion: fmt.Sprintf("%#x", v.ForkVersion),
|
||||
Domain: fmt.Sprintf("%#x", v.Domain),
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (v *chainInfo) UnmarshalJSON(input []byte) error {
|
||||
var data chainInfoJSON
|
||||
if err := json.Unmarshal(input, &data); err != nil {
|
||||
return errors.Wrap(err, "invalid JSON")
|
||||
}
|
||||
|
||||
if data.Version == "" {
|
||||
// Default to 1.
|
||||
v.Version = 1
|
||||
} else {
|
||||
version, err := strconv.ParseUint(data.Version, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "version invalid")
|
||||
}
|
||||
v.Version = version
|
||||
}
|
||||
|
||||
if len(data.Validators) == 0 {
|
||||
return errors.New("validators missing")
|
||||
}
|
||||
v.Validators = data.Validators
|
||||
|
||||
if data.GenesisValidatorsRoot == "" {
|
||||
return errors.New("genesis validators root missing")
|
||||
}
|
||||
|
||||
genesisValidatorsRootBytes, err := hex.DecodeString(strings.TrimPrefix(data.GenesisValidatorsRoot, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "genesis validators root invalid")
|
||||
}
|
||||
if len(genesisValidatorsRootBytes) != phase0.RootLength {
|
||||
return errors.New("genesis validators root incorrect length")
|
||||
}
|
||||
copy(v.GenesisValidatorsRoot[:], genesisValidatorsRootBytes)
|
||||
|
||||
if data.Epoch == "" {
|
||||
return errors.New("epoch missing")
|
||||
}
|
||||
epoch, err := strconv.ParseUint(data.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "epoch invalid")
|
||||
}
|
||||
v.Epoch = phase0.Epoch(epoch)
|
||||
|
||||
if data.ForkVersion == "" {
|
||||
return errors.New("fork version missing")
|
||||
}
|
||||
forkVersionBytes, err := hex.DecodeString(strings.TrimPrefix(data.ForkVersion, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fork version invalid")
|
||||
}
|
||||
if len(forkVersionBytes) != phase0.ForkVersionLength {
|
||||
return errors.New("fork version incorrect length")
|
||||
}
|
||||
copy(v.ForkVersion[:], forkVersionBytes)
|
||||
|
||||
if data.Domain == "" {
|
||||
return errors.New("domain missing")
|
||||
}
|
||||
domainBytes, err := hex.DecodeString(strings.TrimPrefix(data.Domain, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "domain invalid")
|
||||
}
|
||||
if len(domainBytes) != phase0.DomainLength {
|
||||
return errors.New("domain incorrect length")
|
||||
}
|
||||
copy(v.Domain[:], domainBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
104
cmd/validator/credentials/set/command.go
Normal file
104
cmd/validator/credentials/set/command.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
consensusclient "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
capella "github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
type command struct {
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
offline bool
|
||||
json bool
|
||||
|
||||
// Input.
|
||||
account string
|
||||
passphrases []string
|
||||
mnemonic string
|
||||
path string
|
||||
privateKey string
|
||||
validator string
|
||||
withdrawalAddressStr string
|
||||
forkVersion string
|
||||
genesisValidatorsRoot string
|
||||
prepareOffline bool
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
connection string
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Information required to generate the operations.
|
||||
withdrawalAddress bellatrix.ExecutionAddress
|
||||
chainInfo *chainInfo
|
||||
|
||||
// Processing.
|
||||
consensusClient consensusclient.Service
|
||||
chainTime chaintime.Service
|
||||
|
||||
// Output.
|
||||
signedOperations []*capella.SignedBLSToExecutionChange
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
offline: viper.GetBool("offline"),
|
||||
json: viper.GetBool("json"),
|
||||
timeout: viper.GetDuration("timeout"),
|
||||
connection: viper.GetString("connection"),
|
||||
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
|
||||
prepareOffline: viper.GetBool("prepare-offline"),
|
||||
account: viper.GetString("account"),
|
||||
passphrases: util.GetPassphrases(),
|
||||
mnemonic: viper.GetString("mnemonic"),
|
||||
path: viper.GetString("path"),
|
||||
privateKey: viper.GetString("private-key"),
|
||||
|
||||
validator: viper.GetString("validator"),
|
||||
withdrawalAddressStr: viper.GetString("withdrawal-address"),
|
||||
forkVersion: viper.GetString("fork-version"),
|
||||
genesisValidatorsRoot: viper.GetString("genesis-validators-root"),
|
||||
}
|
||||
|
||||
// Timeout is required.
|
||||
if c.timeout == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
|
||||
// We are generating information for offline use, we don't need any information
|
||||
// related to the accounts or signing.
|
||||
if c.prepareOffline {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
if c.account != "" && len(c.passphrases) == 0 {
|
||||
return nil, errors.New("passphrase required with account")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
83
cmd/validator/credentials/set/command_internal_test.go
Normal file
83
cmd/validator/credentials/set/command_internal_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "TimeoutMissing",
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "NoValidatorInfo",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
err: "one of account, index or pubkey required",
|
||||
},
|
||||
{
|
||||
name: "MultipleValidatorInfo",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"index": "1",
|
||||
"pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
err: "only one of account, index and pubkey allowed",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"index": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
_, err := newCommand(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
42
cmd/validator/credentials/set/output.go
Normal file
42
cmd/validator/credentials/set/output.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if c.json || c.offline {
|
||||
data, err := json.Marshal(c.signedOperations)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to marshal signed operations")
|
||||
}
|
||||
if err := os.WriteFile(changeOperationsFilename, data, 0600); err != nil {
|
||||
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", changeOperationsFilename))
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
696
cmd/validator/credentials/set/process.go
Normal file
696
cmd/validator/credentials/set/process.go
Normal file
@@ -0,0 +1,696 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
consensusclient "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
capella "github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/signing"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
ethutil "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// validatorPath is the regular expression that matches a validator path.
|
||||
var validatorPath = regexp.MustCompile("^m/12381/3600/[0-9]+/0/0$")
|
||||
|
||||
var offlinePreparationFilename = "offline-preparation.json"
|
||||
var changeOperationsFilename = "change-operations.json"
|
||||
|
||||
func (c *command) process(ctx context.Context) error {
|
||||
// We should have exactly 1 specifier to know what we're working with.
|
||||
validatorSpecifiers := 0
|
||||
if c.validator != "" {
|
||||
validatorSpecifiers++
|
||||
}
|
||||
if c.mnemonic != "" {
|
||||
validatorSpecifiers++
|
||||
}
|
||||
if c.privateKey != "" {
|
||||
validatorSpecifiers++
|
||||
}
|
||||
if validatorSpecifiers == 0 {
|
||||
return errors.New("one of validator, mmenomic or private key should be specified")
|
||||
}
|
||||
if validatorSpecifiers > 1 {
|
||||
return errors.New("only one of validator, mmenomic or private key should be specified")
|
||||
}
|
||||
|
||||
if err := c.setup(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.obtainRequiredInformation(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.prepareOffline {
|
||||
return c.dumpRequiredInformation(ctx)
|
||||
}
|
||||
|
||||
if err := c.generateOperations(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if validated, reason := c.validateOperations(ctx); !validated {
|
||||
return fmt.Errorf("operation failed validation: %s", reason)
|
||||
}
|
||||
|
||||
if c.json || c.offline {
|
||||
// Want JSON output, or cannot broadcast.
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.broadcastOperations(ctx)
|
||||
}
|
||||
|
||||
// obtainRequiredInformation obtains the information required to create a
|
||||
// withdrawal credentials change operation.
|
||||
func (c *command) obtainRequiredInformation(ctx context.Context) error {
|
||||
c.chainInfo = &chainInfo{
|
||||
Validators: make([]*validatorInfo, 0),
|
||||
}
|
||||
|
||||
// Use the offline preparation file if present (and we haven't been asked to recreate it).
|
||||
if !c.prepareOffline {
|
||||
err := c.loadChainInfo(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if c.offline {
|
||||
return fmt.Errorf("could not find the %s file; this is required to have been previously generated using --offline-preparation on an online machine and be readable in the directory in which this command is being run", offlinePreparationFilename)
|
||||
}
|
||||
|
||||
if err := c.populateChainInfo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateChainInfo populates chain info structure from a beacon node.
|
||||
func (c *command) populateChainInfo(ctx context.Context) error {
|
||||
if c.debug {
|
||||
fmt.Printf("Populating chain info from beacon node\n")
|
||||
}
|
||||
|
||||
// Obtain validators.
|
||||
validators, err := c.consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, "head", nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validators")
|
||||
}
|
||||
|
||||
for _, validator := range validators {
|
||||
c.chainInfo.Validators = append(c.chainInfo.Validators, &validatorInfo{
|
||||
Index: validator.Index,
|
||||
Pubkey: validator.Validator.PublicKey,
|
||||
WithdrawalCredentials: validator.Validator.WithdrawalCredentials,
|
||||
})
|
||||
}
|
||||
|
||||
// Obtain genesis validators root.
|
||||
if c.genesisValidatorsRoot != "" {
|
||||
// Genesis validators root supplied manually.
|
||||
genesisValidatorsRoot, err := hex.DecodeString(strings.TrimPrefix(c.genesisValidatorsRoot, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid genesis validators root supplied")
|
||||
}
|
||||
if len(genesisValidatorsRoot) != phase0.RootLength {
|
||||
return errors.New("invalid length for genesis validators root")
|
||||
}
|
||||
copy(c.chainInfo.GenesisValidatorsRoot[:], genesisValidatorsRoot)
|
||||
} else {
|
||||
// Genesis validators root obtained from beacon node.
|
||||
genesis, err := c.consensusClient.(consensusclient.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain genesis information")
|
||||
}
|
||||
c.chainInfo.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Printf("Genesis validators root is %#x\n", c.chainInfo.GenesisValidatorsRoot)
|
||||
}
|
||||
|
||||
// Obtain epoch.
|
||||
c.chainInfo.Epoch = c.chainTime.CurrentEpoch()
|
||||
|
||||
// Obtain fork version.
|
||||
if c.forkVersion != "" {
|
||||
// Fork version supplied manually.
|
||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(c.forkVersion, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid fork version supplied")
|
||||
}
|
||||
if len(forkVersion) != phase0.ForkVersionLength {
|
||||
return errors.New("invalid length for fork version")
|
||||
}
|
||||
copy(c.chainInfo.ForkVersion[:], forkVersion)
|
||||
} else {
|
||||
// Fork version obtained from beacon node.
|
||||
forkSchedule, err := c.consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain fork schedule")
|
||||
}
|
||||
if len(forkSchedule) == 0 {
|
||||
return errors.New("beacon node did not provide any fork schedule; provide manually with --fork-version")
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Printf("Genesis fork version is %#x\n", forkSchedule[0].CurrentVersion)
|
||||
}
|
||||
if len(forkSchedule) < 4 {
|
||||
return errors.New("beacon node not providing capella fork schedule; provide manually with --fork-version")
|
||||
}
|
||||
for i := range forkSchedule {
|
||||
// Need to be at least fork 3 (i.e. capella)
|
||||
if i < 3 {
|
||||
continue
|
||||
}
|
||||
if i == 3 {
|
||||
// Force use of capella even if we aren't there yet, to allow credential
|
||||
// change operations to be signed in advance with a signature that will be
|
||||
// valid once capella goes live.
|
||||
c.chainInfo.ForkVersion = forkSchedule[i].CurrentVersion
|
||||
continue
|
||||
}
|
||||
if forkSchedule[i].Epoch <= c.chainInfo.Epoch {
|
||||
c.chainInfo.ForkVersion = forkSchedule[i].CurrentVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Printf("Fork version is %#x\n", c.chainInfo.ForkVersion)
|
||||
}
|
||||
|
||||
// Calculate domain.
|
||||
spec, err := c.consensusClient.(consensusclient.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain spec")
|
||||
}
|
||||
domainType, exists := spec["DOMAIN_BLS_TO_EXECUTION_CHANGE"].(phase0.DomainType)
|
||||
if !exists {
|
||||
return errors.New("failed to obtain DOMAIN_BLS_TO_EXECUTION_CHANGE")
|
||||
}
|
||||
domainProvider, isProvider := c.consensusClient.(consensusclient.DomainProvider)
|
||||
if !isProvider {
|
||||
return errors.New("consensus node does not provide domain information")
|
||||
}
|
||||
c.chainInfo.Domain, err = domainProvider.Domain(ctx, domainType, c.chainInfo.Epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain domain")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dumpRequiredInformation prepares for an offline run of this command by dumping
|
||||
// the chain information to a file.
|
||||
func (c *command) dumpRequiredInformation(ctx context.Context) error {
|
||||
data, err := json.Marshal(c.chainInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(offlinePreparationFilename, data, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) generateOperations(ctx context.Context) error {
|
||||
if c.account == "" && c.mnemonic == "" && c.privateKey == "" {
|
||||
// No input information; fetch the operations from a file.
|
||||
if err := c.loadOperations(ctx); err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no account, mnemonic or private key specified and no %s file found; cannot proceed", changeOperationsFilename)
|
||||
}
|
||||
|
||||
if c.mnemonic != "" && c.path == "" {
|
||||
// Have a mnemonic and no path; scan mnemonic.
|
||||
return c.generateOperationsFromMnemonic(ctx)
|
||||
}
|
||||
|
||||
if c.mnemonic != "" && c.path != "" {
|
||||
// Have a mnemonic and path.
|
||||
return c.generateOperationsFromMnemonicAndPath(ctx)
|
||||
}
|
||||
|
||||
// Have a validator index or public key ; fetch the validator info.
|
||||
validatorInfo, err := c.fetchValidatorInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the individual account.
|
||||
withdrawalAccount, err := c.fetchAccount(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the operation.
|
||||
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) loadChainInfo(ctx context.Context) error {
|
||||
_, err := os.Stat(offlinePreparationFilename)
|
||||
if err != nil {
|
||||
if c.debug {
|
||||
fmt.Printf("Failed to read offline preparation file: %v\n", err)
|
||||
}
|
||||
return errors.Wrap(err, fmt.Sprintf("cannot find %s", offlinePreparationFilename))
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
fmt.Printf("%s found; loading chain state\n", offlinePreparationFilename)
|
||||
}
|
||||
data, err := os.ReadFile(offlinePreparationFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read offline preparation file")
|
||||
}
|
||||
if err := json.Unmarshal(data, c.chainInfo); err != nil {
|
||||
return errors.Wrap(err, "failed to parse offline preparation file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) loadOperations(ctx context.Context) error {
|
||||
_, err := os.Stat(changeOperationsFilename)
|
||||
if err != nil {
|
||||
if c.debug {
|
||||
fmt.Printf("Failed to read change operations file: %v\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
fmt.Printf("%s found; loading operations\n", changeOperationsFilename)
|
||||
}
|
||||
data, err := os.ReadFile(changeOperationsFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read change operations file")
|
||||
}
|
||||
if err := json.Unmarshal(data, &c.signedOperations); err != nil {
|
||||
return errors.Wrap(err, "failed to parse change operations file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
|
||||
seed, err := util.SeedFromMnemonic(c.mnemonic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Turn the validators in to a map for easy lookup.
|
||||
validators := make(map[string]*validatorInfo, 0)
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
|
||||
}
|
||||
|
||||
maxDistance := 1024
|
||||
// Start scanning the validator keys.
|
||||
lastFoundIndex := 0
|
||||
for i := 0; ; i++ {
|
||||
if i-lastFoundIndex > maxDistance {
|
||||
if c.debug {
|
||||
fmt.Printf("Gone %d indices without finding a validator, not scanning any further\n", maxDistance)
|
||||
}
|
||||
break
|
||||
}
|
||||
validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", i)
|
||||
|
||||
found, err := c.generateOperationFromSeedAndPath(ctx, validators, seed, validatorKeyPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate operation from seed and path")
|
||||
}
|
||||
if found {
|
||||
lastFoundIndex = i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) generateOperationFromSeedAndPath(ctx context.Context,
|
||||
validators map[string]*validatorInfo,
|
||||
seed []byte,
|
||||
path string,
|
||||
) (
|
||||
bool,
|
||||
error,
|
||||
) {
|
||||
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, path)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to generate validator private key")
|
||||
}
|
||||
validatorPubkey := fmt.Sprintf("%#x", validatorPrivkey.PublicKey().Marshal())
|
||||
validator, exists := validators[validatorPubkey]
|
||||
if !exists {
|
||||
if c.debug {
|
||||
fmt.Printf("No validator found with public key %s at path %s\n", validatorPubkey, path)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if c.verbose {
|
||||
fmt.Printf("Validator %d found with public key %s at path %s\n", validator.Index, validatorPubkey, path)
|
||||
}
|
||||
|
||||
if validator.WithdrawalCredentials[0] != byte(0) {
|
||||
if c.debug {
|
||||
fmt.Printf("Validator %s has non-BLS withdrawal credentials %#x\n", validatorPubkey, validator.WithdrawalCredentials)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Recreate the withdrawal credentials to ensure a match.
|
||||
withdrawalKeyPath := strings.TrimSuffix(path, "/0")
|
||||
withdrawalPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, withdrawalKeyPath)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to generate withdrawal private key")
|
||||
}
|
||||
withdrawalPubkey := withdrawalPrivkey.PublicKey()
|
||||
withdrawalCredentials := ethutil.SHA256(withdrawalPubkey.Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
if !bytes.Equal(withdrawalCredentials, validator.WithdrawalCredentials) {
|
||||
if c.verbose {
|
||||
fmt.Printf("Validator %s withdrawal credentials %#x do not match expected credentials, cannot update\n", validatorPubkey, validator.WithdrawalCredentials)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
fmt.Printf("Validator %s eligible for setting credentials\n", validatorPubkey)
|
||||
}
|
||||
|
||||
withdrawalAccount, err := util.ParseAccount(ctx, c.mnemonic, []string{withdrawalKeyPath}, true)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to create withdrawal account")
|
||||
}
|
||||
|
||||
err = c.generateOperationFromAccount(ctx, validator, withdrawalAccount)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *command) generateOperationFromAccount(ctx context.Context,
|
||||
validator *validatorInfo,
|
||||
withdrawalAccount e2wtypes.Account,
|
||||
) error {
|
||||
signedOperation, err := c.createSignedOperation(ctx, validator, withdrawalAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.signedOperations = append(c.signedOperations, signedOperation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) createSignedOperation(ctx context.Context,
|
||||
validator *validatorInfo,
|
||||
withdrawalAccount e2wtypes.Account,
|
||||
) (
|
||||
*capella.SignedBLSToExecutionChange,
|
||||
error,
|
||||
) {
|
||||
pubkey, err := util.BestPublicKey(withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blsPubkey := phase0.BLSPubKey{}
|
||||
copy(blsPubkey[:], pubkey.Marshal())
|
||||
|
||||
if err := c.parseWithdrawalAddress(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid withdrawal address")
|
||||
}
|
||||
|
||||
operation := &capella.BLSToExecutionChange{
|
||||
ValidatorIndex: validator.Index,
|
||||
FromBLSPubkey: blsPubkey,
|
||||
ToExecutionAddress: c.withdrawalAddress,
|
||||
}
|
||||
root, err := operation.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate root for credentials change operation")
|
||||
}
|
||||
|
||||
// Sign the operation.
|
||||
signature, err := signing.SignRoot(ctx, withdrawalAccount, nil, root, c.chainInfo.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign credentials change operation")
|
||||
}
|
||||
|
||||
return &capella.SignedBLSToExecutionChange{
|
||||
Message: operation,
|
||||
Signature: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *command) parseWithdrawalAddress(ctx context.Context) error {
|
||||
withdrawalAddressBytes, err := hex.DecodeString(strings.TrimPrefix(c.withdrawalAddressStr, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain execution address")
|
||||
}
|
||||
if len(withdrawalAddressBytes) != bellatrix.ExecutionAddressLength {
|
||||
return errors.New("withdrawal address must be exactly 20 bytes in length")
|
||||
}
|
||||
// Ensure the address is properly checksummed.
|
||||
checksummedAddress := addressBytesToEIP55(withdrawalAddressBytes)
|
||||
if checksummedAddress != c.withdrawalAddressStr {
|
||||
return fmt.Errorf("withdrawal address checksum does not match (expected %s)", checksummedAddress)
|
||||
}
|
||||
copy(c.withdrawalAddress[:], withdrawalAddressBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) validateOperations(ctx context.Context) (bool, string) {
|
||||
// Turn the validators in to a map for easy lookup.
|
||||
validators := make(map[phase0.ValidatorIndex]*validatorInfo, 0)
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
validators[validator.Index] = validator
|
||||
}
|
||||
|
||||
for _, signedOperation := range c.signedOperations {
|
||||
if validated, reason := c.validateOperation(ctx, validators, signedOperation); !validated {
|
||||
return validated, reason
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (c *command) validateOperation(ctx context.Context,
|
||||
validators map[phase0.ValidatorIndex]*validatorInfo,
|
||||
signedOperation *capella.SignedBLSToExecutionChange,
|
||||
) (
|
||||
bool,
|
||||
string,
|
||||
) {
|
||||
validator, exists := validators[signedOperation.Message.ValidatorIndex]
|
||||
if !exists {
|
||||
return false, "validator not known on chain"
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Printf("Credentials change operation: %v", signedOperation)
|
||||
fmt.Printf("On-chain validator info: %v\n", validator)
|
||||
}
|
||||
|
||||
if validator.WithdrawalCredentials[0] != byte(0) {
|
||||
return false, "validator is not using BLS withdrawal credentials"
|
||||
}
|
||||
|
||||
withdrawalCredentials := ethutil.SHA256(signedOperation.Message.FromBLSPubkey[:])
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
if !bytes.Equal(withdrawalCredentials, validator.WithdrawalCredentials) {
|
||||
if c.debug {
|
||||
fmt.Printf("validator withdrawal credentials %#x do not match calculated operation withdrawal credentials %#x\n", validator.WithdrawalCredentials, withdrawalCredentials)
|
||||
}
|
||||
return false, "validator withdrawal credentials do not match those in the operation"
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (c *command) broadcastOperations(ctx context.Context) error {
|
||||
// Broadcast the operations.
|
||||
for _, signedOperation := range c.signedOperations {
|
||||
if err := c.consensusClient.(consensusclient.BLSToExecutionChangeSubmitter).SubmitBLSToExecutionChange(ctx, signedOperation); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) setup(ctx context.Context) error {
|
||||
if c.offline {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect to the consensus node.
|
||||
var err error
|
||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to consensus node")
|
||||
}
|
||||
|
||||
// Set up chaintime.
|
||||
c.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithGenesisTimeProvider(c.consensusClient.(consensusclient.GenesisTimeProvider)),
|
||||
standardchaintime.WithForkScheduleProvider(c.consensusClient.(consensusclient.ForkScheduleProvider)),
|
||||
standardchaintime.WithSpecProvider(c.consensusClient.(consensusclient.SpecProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create chaintime service")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) fetchValidatorInfo(ctx context.Context) (*validatorInfo, error) {
|
||||
var validatorInfo *validatorInfo
|
||||
switch {
|
||||
case c.validator == "":
|
||||
return nil, errors.New("no validator specified")
|
||||
case strings.HasPrefix(c.validator, "0x"):
|
||||
// A public key
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
if strings.EqualFold(c.validator, fmt.Sprintf("%#x", validator.Pubkey)) {
|
||||
validatorInfo = validator
|
||||
break
|
||||
}
|
||||
}
|
||||
case strings.Contains(c.validator, "/"):
|
||||
// An account.
|
||||
_, account, err := util.WalletAndAccountFromPath(ctx, c.validator)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to obtain account")
|
||||
}
|
||||
accPubKey, err := util.BestPublicKey(account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to obtain public key for account")
|
||||
}
|
||||
pubkey := fmt.Sprintf("%#x", accPubKey.Marshal())
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
if strings.EqualFold(pubkey, fmt.Sprintf("%#x", validator.Pubkey)) {
|
||||
validatorInfo = validator
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
// An index.
|
||||
index, err := strconv.ParseUint(c.validator, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse validator index")
|
||||
}
|
||||
validatorIndex := phase0.ValidatorIndex(index)
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
if validator.Index == validatorIndex {
|
||||
validatorInfo = validator
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if validatorInfo == nil {
|
||||
return nil, errors.New("unknown validator")
|
||||
}
|
||||
|
||||
return validatorInfo, nil
|
||||
}
|
||||
|
||||
func (c *command) fetchAccount(ctx context.Context) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case c.account != "":
|
||||
account, err = util.ParseAccount(ctx, c.account, c.passphrases, true)
|
||||
case c.mnemonic != "":
|
||||
account, err = util.ParseAccount(ctx, c.mnemonic, []string{c.path}, true)
|
||||
case c.privateKey != "":
|
||||
account, err = util.ParseAccount(ctx, c.privateKey, nil, true)
|
||||
default:
|
||||
err = errors.New("account, mnemonic or private key must be supplied")
|
||||
}
|
||||
|
||||
return account, err
|
||||
}
|
||||
|
||||
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
|
||||
func addressBytesToEIP55(address []byte) string {
|
||||
bytes := []byte(fmt.Sprintf("%x", address))
|
||||
hash := ethutil.Keccak256(bytes)
|
||||
for i := 0; i < len(bytes); i++ {
|
||||
hashByte := hash[i/2]
|
||||
if i%2 == 0 {
|
||||
hashByte >>= 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if bytes[i] > '9' && hashByte > 7 {
|
||||
bytes[i] -= 32
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("0x%s", string(bytes))
|
||||
}
|
||||
|
||||
func (c *command) generateOperationsFromMnemonicAndPath(ctx context.Context) error {
|
||||
seed, err := util.SeedFromMnemonic(c.mnemonic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Turn the validators in to a map for easy lookup.
|
||||
validators := make(map[string]*validatorInfo, 0)
|
||||
for _, validator := range c.chainInfo.Validators {
|
||||
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
|
||||
}
|
||||
|
||||
validatorKeyPath := c.path
|
||||
match := validatorPath.Match([]byte(c.path))
|
||||
if !match {
|
||||
return fmt.Errorf("path %s does not match EIP-2334 format", c.path)
|
||||
}
|
||||
|
||||
if _, err := c.generateOperationFromSeedAndPath(ctx, validators, seed, validatorKeyPath); err != nil {
|
||||
return errors.Wrap(err, "failed to generate operation from seed and path")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
cmd/validator/credentials/set/run.go
Normal file
50
cmd/validator/credentials/set/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
97
cmd/validator/credentials/set/validatorinfo.go
Normal file
97
cmd/validator/credentials/set/validatorinfo.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright © 2022 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 validatorcredentialsset
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type validatorInfo struct {
|
||||
Index phase0.ValidatorIndex
|
||||
Pubkey phase0.BLSPubKey
|
||||
WithdrawalCredentials []byte
|
||||
}
|
||||
|
||||
type validatorInfoJSON struct {
|
||||
Index string `json:"index"`
|
||||
Pubkey string `json:"pubkey"`
|
||||
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v *validatorInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&validatorInfoJSON{
|
||||
Index: fmt.Sprintf("%d", v.Index),
|
||||
Pubkey: fmt.Sprintf("%#x", v.Pubkey),
|
||||
WithdrawalCredentials: fmt.Sprintf("%#x", v.WithdrawalCredentials),
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (v *validatorInfo) UnmarshalJSON(input []byte) error {
|
||||
var data validatorInfoJSON
|
||||
if err := json.Unmarshal(input, &data); err != nil {
|
||||
return errors.Wrap(err, "invalid JSON")
|
||||
}
|
||||
|
||||
if data.Index == "" {
|
||||
return errors.New("index missing")
|
||||
}
|
||||
index, err := strconv.ParseUint(data.Index, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid value for index")
|
||||
}
|
||||
v.Index = phase0.ValidatorIndex(index)
|
||||
|
||||
if data.Pubkey == "" {
|
||||
return errors.New("public key missing")
|
||||
}
|
||||
pubkey, err := hex.DecodeString(strings.TrimPrefix(data.Pubkey, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid value for public key")
|
||||
}
|
||||
if len(pubkey) != phase0.PublicKeyLength {
|
||||
return fmt.Errorf("incorrect length %d for public key", len(pubkey))
|
||||
}
|
||||
copy(v.Pubkey[:], pubkey)
|
||||
|
||||
if data.WithdrawalCredentials == "" {
|
||||
return errors.New("withdrawal credentials missing")
|
||||
}
|
||||
v.WithdrawalCredentials, err = hex.DecodeString(strings.TrimPrefix(data.WithdrawalCredentials, "0x"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid value for withdrawal credentials")
|
||||
}
|
||||
if len(v.WithdrawalCredentials) != phase0.HashLength {
|
||||
return fmt.Errorf("incorrect length %d for withdrawal credentials", len(v.WithdrawalCredentials))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (v *validatorInfo) String() string {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Err: %v\n", err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
@@ -101,6 +101,9 @@ func inputKey(ctx context.Context, data *dataIn) (*dataIn, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create acount from key")
|
||||
}
|
||||
if err := data.account.(e2wtypes.AccountLocker).Unlock(ctx, nil); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unlock account")
|
||||
}
|
||||
return inputChainData(ctx, data)
|
||||
}
|
||||
|
||||
|
||||
140
cmd/validator/summary/command.go
Normal file
140
cmd/validator/summary/command.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
)
|
||||
|
||||
type command struct {
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
connection string
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Operation.
|
||||
epoch string
|
||||
validators []string
|
||||
jsonOutput bool
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
proposerDutiesProvider eth2client.ProposerDutiesProvider
|
||||
attesterDutiesProvider eth2client.AttesterDutiesProvider
|
||||
blocksProvider eth2client.SignedBeaconBlockProvider
|
||||
syncCommitteesProvider eth2client.SyncCommitteesProvider
|
||||
validatorsProvider eth2client.ValidatorsProvider
|
||||
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
||||
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||
|
||||
// Processing.
|
||||
validatorsByIndex map[phase0.ValidatorIndex]*apiv1.Validator
|
||||
|
||||
// Results.
|
||||
summary *validatorSummary
|
||||
}
|
||||
|
||||
type validatorSummary struct {
|
||||
Epoch phase0.Epoch `json:"epoch"`
|
||||
Validators []*apiv1.Validator `json:"validators"`
|
||||
FirstSlot phase0.Slot `json:"first_slot"`
|
||||
LastSlot phase0.Slot `json:"last_slot"`
|
||||
ActiveValidators int `json:"active_validators"`
|
||||
ParticipatingValidators int `json:"participating_validators"`
|
||||
NonParticipatingValidators []*nonParticipatingValidator `json:"non_participating_validators"`
|
||||
IncorrectHeadValidators []*validatorFault `json:"incorrect_head_validators"`
|
||||
UntimelyHeadValidators []*validatorFault `json:"untimely_head_validators"`
|
||||
UntimelySourceValidators []*validatorFault `json:"untimely_source_validators"`
|
||||
IncorrectTargetValidators []*validatorFault `json:"incorrect_target_validators"`
|
||||
UntimelyTargetValidators []*validatorFault `json:"untimely_target_validators"`
|
||||
Slots []*slot `json:"slots"`
|
||||
Proposals []*epochProposal `json:"-"`
|
||||
SyncCommittee []*epochSyncCommittee `json:"-"`
|
||||
}
|
||||
|
||||
type slot struct {
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Attestations *slotAttestations `json:"attestations"`
|
||||
}
|
||||
|
||||
type slotAttestations struct {
|
||||
Expected int `json:"expected"`
|
||||
Included int `json:"included"`
|
||||
CorrectHead int `json:"correct_head"`
|
||||
TimelyHead int `json:"timely_head"`
|
||||
CorrectTarget int `json:"correct_target"`
|
||||
TimelyTarget int `json:"timely_target"`
|
||||
TimelySource int `json:"timely_source"`
|
||||
}
|
||||
|
||||
type epochProposal struct {
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Proposer phase0.ValidatorIndex `json:"proposer"`
|
||||
Block bool `json:"block"`
|
||||
}
|
||||
|
||||
type epochSyncCommittee struct {
|
||||
Index phase0.ValidatorIndex `json:"index"`
|
||||
Missed int `json:"missed"`
|
||||
}
|
||||
|
||||
type validatorFault struct {
|
||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||
AttestationData *phase0.AttestationData `json:"attestation_data,omitempty"`
|
||||
InclusionDistance int `json:"inclusion_delay"`
|
||||
}
|
||||
|
||||
type nonParticipatingValidator struct {
|
||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
validatorsByIndex: make(map[phase0.ValidatorIndex]*apiv1.Validator),
|
||||
summary: &validatorSummary{},
|
||||
}
|
||||
|
||||
// Timeout.
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
c.timeout = viper.GetDuration("timeout")
|
||||
|
||||
c.connection = viper.GetString("connection")
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
c.epoch = viper.GetString("epoch")
|
||||
c.validators = viper.GetStringSlice("validators")
|
||||
c.jsonOutput = viper.GetBool("json")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
64
cmd/validator/summary/command_internal_test.go
Normal file
64
cmd/validator/summary/command_internal_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "TimeoutMissing",
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
_, err := newCommand(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
86
cmd/validator/summary/output.go
Normal file
86
cmd/validator/summary/output.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if c.jsonOutput {
|
||||
return c.outputJSON(ctx)
|
||||
}
|
||||
|
||||
return c.outputTxt(ctx)
|
||||
}
|
||||
|
||||
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
data, err := json.Marshal(c.summary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
builder.WriteString("Epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d:\n", c.summary.Epoch))
|
||||
if len(c.summary.NonParticipatingValidators) > 0 {
|
||||
builder.WriteString(" Non-participating validators:\n")
|
||||
for _, validator := range c.summary.NonParticipatingValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.Slot, validator.Committee))
|
||||
}
|
||||
}
|
||||
if len(c.summary.IncorrectHeadValidators) > 0 {
|
||||
builder.WriteString(" Incorrect head validators:\n")
|
||||
for _, validator := range c.summary.IncorrectHeadValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index))
|
||||
}
|
||||
}
|
||||
if len(c.summary.UntimelyHeadValidators) > 0 {
|
||||
builder.WriteString(" Untimely head validators:\n")
|
||||
for _, validator := range c.summary.UntimelyHeadValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
|
||||
}
|
||||
}
|
||||
if len(c.summary.UntimelySourceValidators) > 0 {
|
||||
builder.WriteString(" Untimely source validators:\n")
|
||||
for _, validator := range c.summary.UntimelySourceValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
|
||||
}
|
||||
}
|
||||
if len(c.summary.IncorrectTargetValidators) > 0 {
|
||||
builder.WriteString(" Incorrect target validators:\n")
|
||||
for _, validator := range c.summary.IncorrectTargetValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index))
|
||||
}
|
||||
}
|
||||
if len(c.summary.UntimelyTargetValidators) > 0 {
|
||||
builder.WriteString(" Untimely target validators:\n")
|
||||
for _, validator := range c.summary.UntimelyTargetValidators {
|
||||
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
425
cmd/validator/summary/process.go
Normal file
425
cmd/validator/summary/process.go
Normal file
@@ -0,0 +1,425 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
func (c *command) process(ctx context.Context) error {
|
||||
// Obtain information we need to process.
|
||||
err := c.setup(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.summary.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse epoch")
|
||||
}
|
||||
c.summary.FirstSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)
|
||||
c.summary.LastSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) - 1
|
||||
c.summary.Slots = make([]*slot, 1+int(c.summary.LastSlot)-int(c.summary.FirstSlot))
|
||||
for i := range c.summary.Slots {
|
||||
c.summary.Slots[i] = &slot{
|
||||
Slot: c.summary.FirstSlot + phase0.Slot(i),
|
||||
}
|
||||
}
|
||||
|
||||
c.summary.Validators, err = util.ParseValidators(ctx, c.validatorsProvider, c.validators, fmt.Sprintf("%d", c.summary.FirstSlot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse validators")
|
||||
}
|
||||
// Reorder validators by index.
|
||||
sort.Slice(c.summary.Validators, func(i int, j int) bool {
|
||||
return c.summary.Validators[i].Index < c.summary.Validators[j].Index
|
||||
})
|
||||
|
||||
// Create a map for validator indices for easy lookup.
|
||||
c.validatorsByIndex = make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
||||
for _, validator := range c.summary.Validators {
|
||||
c.validatorsByIndex[validator.Index] = validator
|
||||
}
|
||||
|
||||
if err := c.processProposerDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.processAttesterDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
duties, err := c.proposerDutiesProvider.ProposerDuties(ctx, c.summary.Epoch, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain proposer duties")
|
||||
}
|
||||
if duties == nil {
|
||||
return errors.New("empty proposer duties")
|
||||
}
|
||||
for _, duty := range duties {
|
||||
if _, exists := c.validatorsByIndex[duty.ValidatorIndex]; !exists {
|
||||
continue
|
||||
}
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", duty.Slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
||||
}
|
||||
present := block != nil
|
||||
c.summary.Proposals = append(c.summary.Proposals, &epochProposal{
|
||||
Slot: duty.Slot,
|
||||
Proposer: duty.ValidatorIndex,
|
||||
Block: present,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) activeValidators() (map[phase0.ValidatorIndex]*apiv1.Validator, []phase0.ValidatorIndex) {
|
||||
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
||||
activeValidatorIndices := make([]phase0.ValidatorIndex, 0, len(c.validatorsByIndex))
|
||||
for _, validator := range c.summary.Validators {
|
||||
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
|
||||
activeValidators[validator.Index] = validator
|
||||
activeValidatorIndices = append(activeValidatorIndices, validator.Index)
|
||||
}
|
||||
}
|
||||
|
||||
return activeValidators, activeValidatorIndices
|
||||
}
|
||||
|
||||
func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
activeValidators, activeValidatorIndices := c.activeValidators()
|
||||
|
||||
// Obtain number of validators that voted for blocks in the epoch.
|
||||
// These votes can be included anywhere from the second slot of
|
||||
// the epoch to the first slot of the next-but-one epoch.
|
||||
firstSlot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) + 1
|
||||
lastSlot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch + 2)
|
||||
if lastSlot > c.chainTime.CurrentSlot() {
|
||||
lastSlot = c.chainTime.CurrentSlot()
|
||||
}
|
||||
|
||||
// Obtain the duties for the validators to know where they should be attesting.
|
||||
duties, err := c.attesterDutiesProvider.AttesterDuties(ctx, c.summary.Epoch, activeValidatorIndices)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attester duties")
|
||||
}
|
||||
for slot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch); slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1); slot++ {
|
||||
index := int(slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
|
||||
c.summary.Slots[index].Attestations = &slotAttestations{}
|
||||
}
|
||||
|
||||
// Need a cache of beacon block headers to reduce lookup times.
|
||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||
|
||||
// Need a map of duties to easily find the attestations we care about.
|
||||
dutiesBySlot := make(map[phase0.Slot]map[phase0.CommitteeIndex][]*apiv1.AttesterDuty)
|
||||
dutiesByValidatorIndex := make(map[phase0.ValidatorIndex]*apiv1.AttesterDuty)
|
||||
for _, duty := range duties {
|
||||
index := int(duty.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
|
||||
dutiesByValidatorIndex[duty.ValidatorIndex] = duty
|
||||
c.summary.Slots[index].Attestations.Expected++
|
||||
if _, exists := dutiesBySlot[duty.Slot]; !exists {
|
||||
dutiesBySlot[duty.Slot] = make(map[phase0.CommitteeIndex][]*apiv1.AttesterDuty)
|
||||
}
|
||||
if _, exists := dutiesBySlot[duty.Slot][duty.CommitteeIndex]; !exists {
|
||||
dutiesBySlot[duty.Slot][duty.CommitteeIndex] = make([]*apiv1.AttesterDuty, 0)
|
||||
}
|
||||
dutiesBySlot[duty.Slot][duty.CommitteeIndex] = append(dutiesBySlot[duty.Slot][duty.CommitteeIndex], duty)
|
||||
}
|
||||
|
||||
c.summary.IncorrectHeadValidators = make([]*validatorFault, 0)
|
||||
c.summary.UntimelyHeadValidators = make([]*validatorFault, 0)
|
||||
c.summary.UntimelySourceValidators = make([]*validatorFault, 0)
|
||||
c.summary.IncorrectTargetValidators = make([]*validatorFault, 0)
|
||||
c.summary.UntimelyTargetValidators = make([]*validatorFault, 0)
|
||||
|
||||
// Hunt through the blocks looking for attestations from the validators.
|
||||
votes := make(map[phase0.ValidatorIndex]struct{})
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
if err := c.processAttesterDutiesSlot(ctx, slot, dutiesBySlot, votes, headersCache, activeValidatorIndices); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Use dutiesMap and votes to work out which validators didn't participate.
|
||||
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0)
|
||||
for _, index := range activeValidatorIndices {
|
||||
if _, exists := votes[index]; !exists {
|
||||
// Didn't vote.
|
||||
duty := dutiesByValidatorIndex[index]
|
||||
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, &nonParticipatingValidator{
|
||||
Validator: index,
|
||||
Slot: duty.Slot,
|
||||
Committee: duty.CommitteeIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the non-participating validators list.
|
||||
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
|
||||
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
|
||||
return c.summary.NonParticipatingValidators[i].Slot < c.summary.NonParticipatingValidators[j].Slot
|
||||
}
|
||||
if c.summary.NonParticipatingValidators[i].Committee != c.summary.NonParticipatingValidators[j].Committee {
|
||||
return c.summary.NonParticipatingValidators[i].Committee < c.summary.NonParticipatingValidators[j].Committee
|
||||
}
|
||||
return c.summary.NonParticipatingValidators[i].Validator < c.summary.NonParticipatingValidators[j].Validator
|
||||
})
|
||||
|
||||
c.summary.ActiveValidators = len(activeValidators)
|
||||
c.summary.ParticipatingValidators = len(votes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
||||
slot phase0.Slot,
|
||||
dutiesBySlot map[phase0.Slot]map[phase0.CommitteeIndex][]*apiv1.AttesterDuty,
|
||||
votes map[phase0.ValidatorIndex]struct{},
|
||||
headersCache *util.BeaconBlockHeaderCache,
|
||||
activeValidatorIndices []phase0.ValidatorIndex,
|
||||
) error {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
if block == nil {
|
||||
// No block at this slot; that's fine.
|
||||
return nil
|
||||
}
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
if _, exists := dutiesBySlot[attestation.Data.Slot]; !exists {
|
||||
// We do not have any attestations for this slot.
|
||||
continue
|
||||
}
|
||||
if _, exists := dutiesBySlot[attestation.Data.Slot][attestation.Data.Index]; !exists {
|
||||
// We do not have any attestations for this committee.
|
||||
continue
|
||||
}
|
||||
for _, duty := range dutiesBySlot[attestation.Data.Slot][attestation.Data.Index] {
|
||||
if attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||
// Found it.
|
||||
if _, exists := votes[duty.ValidatorIndex]; exists {
|
||||
// Duplicate; ignore.
|
||||
continue
|
||||
}
|
||||
votes[duty.ValidatorIndex] = struct{}{}
|
||||
|
||||
// Update the metrics for the attestation.
|
||||
index := int(attestation.Data.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
|
||||
c.summary.Slots[index].Attestations.Included++
|
||||
inclusionDelay := slot - duty.Slot
|
||||
|
||||
fault := &validatorFault{
|
||||
Validator: duty.ValidatorIndex,
|
||||
AttestationData: attestation.Data,
|
||||
InclusionDistance: int(inclusionDelay),
|
||||
}
|
||||
|
||||
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to calculate if attestation had correct head vote")
|
||||
}
|
||||
if headCorrect {
|
||||
c.summary.Slots[index].Attestations.CorrectHead++
|
||||
if inclusionDelay == 1 {
|
||||
c.summary.Slots[index].Attestations.TimelyHead++
|
||||
} else {
|
||||
c.summary.UntimelyHeadValidators = append(c.summary.UntimelyHeadValidators, fault)
|
||||
}
|
||||
} else {
|
||||
c.summary.IncorrectHeadValidators = append(c.summary.IncorrectHeadValidators, fault)
|
||||
if inclusionDelay > 1 {
|
||||
c.summary.UntimelyHeadValidators = append(c.summary.UntimelyHeadValidators, fault)
|
||||
}
|
||||
}
|
||||
|
||||
if inclusionDelay <= 5 {
|
||||
c.summary.Slots[index].Attestations.TimelySource++
|
||||
} else {
|
||||
c.summary.UntimelySourceValidators = append(c.summary.UntimelySourceValidators, fault)
|
||||
}
|
||||
|
||||
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to calculate if attestation had correct target vote")
|
||||
}
|
||||
if targetCorrect {
|
||||
c.summary.Slots[index].Attestations.CorrectTarget++
|
||||
if inclusionDelay <= 32 {
|
||||
c.summary.Slots[index].Attestations.TimelyTarget++
|
||||
} else {
|
||||
c.summary.UntimelyTargetValidators = append(c.summary.UntimelyTargetValidators, fault)
|
||||
}
|
||||
} else {
|
||||
c.summary.IncorrectTargetValidators = append(c.summary.IncorrectTargetValidators, fault)
|
||||
if inclusionDelay > 32 {
|
||||
c.summary.UntimelyTargetValidators = append(c.summary.UntimelyTargetValidators, fault)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(votes) == len(activeValidatorIndices) {
|
||||
// Found them all.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
// if c.summary.Epoch < c.chainTime.AltairInitialEpoch() {
|
||||
// // The epoch is pre-Altair. No info but no error.
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// committee, err := c.syncCommitteesProvider.SyncCommittee(ctx, fmt.Sprintf("%d", c.summary.FirstSlot))
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "failed to obtain sync committee")
|
||||
// }
|
||||
// if len(committee.Validators) == 0 {
|
||||
// return errors.Wrap(err, "empty sync committee")
|
||||
// }
|
||||
//
|
||||
// missed := make(map[phase0.ValidatorIndex]int)
|
||||
// for _, index := range committee.Validators {
|
||||
// missed[index] = 0
|
||||
// }
|
||||
//
|
||||
// for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
|
||||
// block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
// }
|
||||
// if block == nil {
|
||||
// // If the block is missed we don't count the sync aggregate miss.
|
||||
// continue
|
||||
// }
|
||||
// var aggregate *altair.SyncAggregate
|
||||
// switch block.Version {
|
||||
// case spec.DataVersionPhase0:
|
||||
// // No sync committees in this fork.
|
||||
// return nil
|
||||
// case spec.DataVersionAltair:
|
||||
// aggregate = block.Altair.Message.Body.SyncAggregate
|
||||
// case spec.DataVersionBellatrix:
|
||||
// aggregate = block.Bellatrix.Message.Body.SyncAggregate
|
||||
// default:
|
||||
// return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
// }
|
||||
// for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
|
||||
// if !aggregate.SyncCommitteeBits.BitAt(i) {
|
||||
// missed[committee.Validators[int(i)]]++
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// c.summary.SyncCommittee = make([]*epochSyncCommittee, 0, len(missed))
|
||||
// for index, count := range missed {
|
||||
// if count > 0 {
|
||||
// c.summary.SyncCommittee = append(c.summary.SyncCommittee, &epochSyncCommittee{
|
||||
// Index: index,
|
||||
// Missed: count,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// sort.Slice(c.summary.SyncCommittee, func(i int, j int) bool {
|
||||
// missedDiff := c.summary.SyncCommittee[i].Missed - c.summary.SyncCommittee[j].Missed
|
||||
// if missedDiff != 0 {
|
||||
// // Actually want to order by missed descending, so invert the expected condition.
|
||||
// return missedDiff > 0
|
||||
// }
|
||||
// // Then order by validator index.
|
||||
// return c.summary.SyncCommittee[i].Index < c.summary.SyncCommittee[j].Index
|
||||
// })
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
c.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithForkScheduleProvider(c.eth2Client.(eth2client.ForkScheduleProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
var isProvider bool
|
||||
c.proposerDutiesProvider, isProvider = c.eth2Client.(eth2client.ProposerDutiesProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide proposer duties")
|
||||
}
|
||||
c.attesterDutiesProvider, isProvider = c.eth2Client.(eth2client.AttesterDutiesProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide attester duties")
|
||||
}
|
||||
c.blocksProvider, isProvider = c.eth2Client.(eth2client.SignedBeaconBlockProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide signed beacon blocks")
|
||||
}
|
||||
c.syncCommitteesProvider, isProvider = c.eth2Client.(eth2client.SyncCommitteesProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide sync committee duties")
|
||||
}
|
||||
c.validatorsProvider, isProvider = c.eth2Client.(eth2client.ValidatorsProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide validators")
|
||||
}
|
||||
c.beaconCommitteesProvider, isProvider = c.eth2Client.(eth2client.BeaconCommitteesProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide beacon committees")
|
||||
}
|
||||
c.beaconBlockHeadersProvider, isProvider = c.eth2Client.(eth2client.BeaconBlockHeadersProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide beacon block headers")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
62
cmd/validator/summary/process_internal_test.go
Normal file
62
cmd/validator/summary/process_internal_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "InvalidData",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "60s",
|
||||
"data": "[[",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
cmd, err := newCommand(context.Background())
|
||||
require.NoError(t, err)
|
||||
err = cmd.process(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/validator/summary/run.go
Normal file
50
cmd/validator/summary/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2022 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -26,7 +26,7 @@ var validatorCredentialsGetCmd = &cobra.Command{
|
||||
Short: "Obtain withdrawal credentials for an Ethereum consensus validator",
|
||||
Long: `Obtain withdrawal credentials for an Ethereum consensus validator. For example:
|
||||
|
||||
ethdo validator credentials get --account=primary/validator
|
||||
ethdo validator credentials get --validator=primary/validator
|
||||
|
||||
In quiet mode this will return 0 if the validator exists, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,19 +47,11 @@ In quiet mode this will return 0 if the validator exists, otherwise 1.`,
|
||||
func init() {
|
||||
validatorCredentialsCmd.AddCommand(validatorCredentialsGetCmd)
|
||||
validatorCredentialsFlags(validatorCredentialsGetCmd)
|
||||
validatorCredentialsGetCmd.Flags().String("account", "", "Account for which to fetch validator credentials")
|
||||
validatorCredentialsGetCmd.Flags().String("index", "", "Validator index for which to fetch validator credentials")
|
||||
validatorCredentialsGetCmd.Flags().String("pubkey", "", "Validator public key for which to fetch validator credentials")
|
||||
validatorCredentialsGetCmd.Flags().String("validator", "", "Validator for which to get validator credentials")
|
||||
}
|
||||
|
||||
func validatorCredentialsGetBindings() {
|
||||
if err := viper.BindPFlag("account", validatorCredentialsGetCmd.Flags().Lookup("account")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("index", validatorCredentialsGetCmd.Flags().Lookup("index")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("pubkey", validatorCredentialsGetCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
if err := viper.BindPFlag("validator", validatorCredentialsGetCmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
92
cmd/validatorcredentialsset.go
Normal file
92
cmd/validatorcredentialsset.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright © 2022 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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
validatorcredentialsset "github.com/wealdtech/ethdo/cmd/validator/credentials/set"
|
||||
)
|
||||
|
||||
var validatorCredentialsSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "Set withdrawal credentials for an Ethereum consensus validator",
|
||||
Long: `Set withdrawal credentials for an Ethereum consensus validator via a "change credentials" operation. For example:
|
||||
|
||||
ethdo validator credentials set --validator=primary/validator --withdrawal-address=0x00...13 --private-key=0x00...1f
|
||||
|
||||
The existing account can be specified in one of a number of ways:
|
||||
|
||||
- mnemonic using --mnemonic; this will scan the mnemonic and generate all required operations
|
||||
- mnemonic and path to the validator key using --mnemonic and --path; this will generate a single operation
|
||||
- private key using --private-key; this will generate a single operation
|
||||
- account and passphrase using --account and --passphrase; this will generate a single operation
|
||||
|
||||
In quiet mode this will return 0 if the credentials operation has been generated (and successfully broadcast if online), otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := validatorcredentialsset.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCredentialsCmd.AddCommand(validatorCredentialsSetCmd)
|
||||
validatorCredentialsFlags(validatorCredentialsSetCmd)
|
||||
validatorCredentialsSetCmd.Flags().Bool("prepare-offline", false, "Create files for offline use")
|
||||
validatorCredentialsSetCmd.Flags().String("validator", "", "Validator for which to set validator credentials")
|
||||
validatorCredentialsSetCmd.Flags().String("withdrawal-address", "", "Execution address to which to direct withdrawals")
|
||||
validatorCredentialsSetCmd.Flags().String("signed-operation", "", "Use pre-defined JSON signed operation as created by --json to transmit the credentials change operation")
|
||||
validatorCredentialsSetCmd.Flags().Bool("json", false, "Generate JSON data containing a signed operation rather than broadcast it to the network (implied when offline)")
|
||||
validatorCredentialsSetCmd.Flags().Bool("offline", false, "Do not attempt to connect to a beacon node to obtain information for the operation")
|
||||
validatorCredentialsSetCmd.Flags().String("fork-version", "", "Fork version to use for signing (overrides fetching from beacon node)")
|
||||
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
||||
}
|
||||
|
||||
func validatorCredentialsSetBindings() {
|
||||
if err := viper.BindPFlag("prepare-offline", validatorCredentialsSetCmd.Flags().Lookup("prepare-offline")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("validator", validatorCredentialsSetCmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("signed-operation", validatorCredentialsSetCmd.Flags().Lookup("signed-operation")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("withdrawal-address", validatorCredentialsSetCmd.Flags().Lookup("withdrawal-address")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", validatorCredentialsSetCmd.Flags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("offline", validatorCredentialsSetCmd.Flags().Lookup("offline")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("fork-version", validatorCredentialsSetCmd.Flags().Lookup("fork-version")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("genesis-validators-root", validatorCredentialsSetCmd.Flags().Lookup("genesis-validators-root")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020, 2021 Weald Technology Trading
|
||||
// Copyright © 2020 - 2022 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
|
||||
@@ -16,10 +16,9 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -32,7 +31,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
@@ -41,7 +39,7 @@ var validatorInfoCmd = &cobra.Command{
|
||||
Short: "Obtain information about a validator",
|
||||
Long: `Obtain information about validator. For example:
|
||||
|
||||
ethdo validator info --account=primary/validator
|
||||
ethdo validator info --validator=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) {
|
||||
@@ -54,32 +52,21 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
)
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
account, err := validatorInfoAccount(ctx, eth2Client)
|
||||
errCheck(err, "Failed to obtain validator account")
|
||||
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
errCheck(err, "Failed to obtain validator public key")
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", pubKeys)
|
||||
errCheck(err, "Failed to obtain validator information")
|
||||
if len(validators) == 0 {
|
||||
fmt.Println("Validator not known by beacon node")
|
||||
os.Exit(_exitSuccess)
|
||||
if viper.GetString("validator") == "" {
|
||||
fmt.Println("validator is required")
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
}
|
||||
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), "head")
|
||||
errCheck(err, "Failed to obtain validator")
|
||||
|
||||
if verbose {
|
||||
network, err := util.Network(ctx, eth2Client)
|
||||
errCheck(err, "Failed to obtain network")
|
||||
outputIf(debug, fmt.Sprintf("Network is %s", network))
|
||||
pubKey, err := bestPublicKey(account)
|
||||
pubKey, err := validator.PubKey(ctx)
|
||||
if err == nil {
|
||||
deposits, totalDeposited, err := graphData(network, pubKey.Marshal())
|
||||
deposits, totalDeposited, err := graphData(network, pubKey[:])
|
||||
if err == nil && deposits > 0 {
|
||||
fmt.Printf("Number of deposits: %d\n", deposits)
|
||||
fmt.Printf("Total deposited: %s\n", string2eth.GWeiToString(uint64(totalDeposited), true))
|
||||
@@ -122,54 +109,6 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
},
|
||||
}
|
||||
|
||||
// validatorInfoAccount obtains the account for the validator info command.
|
||||
func validatorInfoAccount(ctx context.Context, eth2Client eth2client.Service) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
switch {
|
||||
case 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")
|
||||
}
|
||||
case viper.GetString("pubkey") != "":
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(viper.GetString("pubkey"), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", viper.GetString("pubkey")))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
|
||||
}
|
||||
case viper.GetInt64("index") != -1:
|
||||
validatorsProvider, isValidatorsProvider := eth2Client.(eth2client.ValidatorsProvider)
|
||||
if !isValidatorsProvider {
|
||||
return nil, errors.New("client does not provide validator information")
|
||||
}
|
||||
index := spec.ValidatorIndex(viper.GetInt64("index"))
|
||||
validators, err := validatorsProvider.Validators(ctx, "head", []spec.ValidatorIndex{
|
||||
index,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("unknown validator index")
|
||||
}
|
||||
pubKeyBytes := make([]byte, 48)
|
||||
copy(pubKeyBytes, validators[index].Validator.PublicKey[:])
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("neither account nor public key supplied")
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// graphData returns data from the graph about number and amount of deposits
|
||||
func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error) {
|
||||
subgraph := ""
|
||||
@@ -186,7 +125,7 @@ func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error
|
||||
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)
|
||||
body, err := io.ReadAll(graphResp.Body)
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrap(err, "bad information returned from existing deposit check")
|
||||
}
|
||||
@@ -224,16 +163,12 @@ func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorInfoCmd)
|
||||
validatorInfoCmd.Flags().String("pubkey", "", "Public key for which to obtain status")
|
||||
validatorInfoCmd.Flags().Int64("index", -1, "Index for which to obtain status")
|
||||
validatorInfoCmd.Flags().String("validator", "", "Public key for which to obtain status")
|
||||
validatorFlags(validatorInfoCmd)
|
||||
}
|
||||
|
||||
func validatorInfoBindings() {
|
||||
if err := viper.BindPFlag("pubkey", validatorInfoCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("index", validatorInfoCmd.Flags().Lookup("index")); err != nil {
|
||||
if err := viper.BindPFlag("validator", validatorInfoCmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ func init() {
|
||||
validatorCmd.AddCommand(validatorKeycheckCmd)
|
||||
validatorFlags(validatorKeycheckCmd)
|
||||
validatorKeycheckCmd.Flags().String("withdrawal-credentials", "", "Withdrawal credentials to check (can run offline)")
|
||||
validatorKeycheckCmd.Flags().String("mnemonic", "", "Mnemonic from which to generate withdrawal credentials")
|
||||
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
|
||||
}
|
||||
|
||||
@@ -58,9 +57,6 @@ func validatorKeycheckBindings() {
|
||||
if err := viper.BindPFlag("withdrawal-credentials", validatorKeycheckCmd.Flags().Lookup("withdrawal-credentials")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("mnemonic", validatorKeycheckCmd.Flags().Lookup("mnemonic")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("privkey", validatorKeycheckCmd.Flags().Lookup("privkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
66
cmd/validatorsummary.go
Normal file
66
cmd/validatorsummary.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright © 2022 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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
validatorsummary "github.com/wealdtech/ethdo/cmd/validator/summary"
|
||||
)
|
||||
|
||||
var validatorSummaryCmd = &cobra.Command{
|
||||
Use: "summary",
|
||||
Short: "Obtain summary information about validator(s) in an epoch",
|
||||
Long: `Obtain summary information about one or more validators in an epoch. For example:
|
||||
|
||||
ethdo validator summary --validators=1,2,3 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if information for the epoch is found, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := validatorsummary.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorSummaryCmd)
|
||||
validatorFlags(validatorSummaryCmd)
|
||||
validatorSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information ()")
|
||||
validatorSummaryCmd.Flags().StringSlice("validators", nil, "the list of validators for which to obtain information")
|
||||
validatorSummaryCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func validatorSummaryBindings() {
|
||||
validatorBindings()
|
||||
if err := viper.BindPFlag("epoch", validatorSummaryCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("validators", validatorSummaryCmd.Flags().Lookup("validators")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", validatorSummaryCmd.Flags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// ReleaseVersion is the release version of the codebase.
|
||||
// Usually overridden by tag names when building binaries.
|
||||
var ReleaseVersion = "local build (latest release 1.25.3)"
|
||||
var ReleaseVersion = "local build (latest release 1.26.5)"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
|
||||
@@ -15,6 +15,7 @@ package walletdelete
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -35,6 +36,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("cannot obtain store for the wallet")
|
||||
}
|
||||
store := storeProvider.Store()
|
||||
|
||||
if store.Name() != "filesystem" {
|
||||
return nil, fmt.Errorf("cannot delete %s wallet automatically, please remove manually", store.Name())
|
||||
}
|
||||
storeLocationProvider, isProvider := store.(e2wtypes.StoreLocationProvider)
|
||||
if !isProvider {
|
||||
return nil, errors.New("cannot obtain store location for the wallet")
|
||||
|
||||
@@ -15,7 +15,6 @@ package walletdelete
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -31,7 +30,7 @@ import (
|
||||
func TestProcess(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
base, err := ioutil.TempDir("", "")
|
||||
base, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(base)
|
||||
store := filesystem.New(filesystem.WithLocation(base))
|
||||
|
||||
@@ -15,7 +15,6 @@ package walletexport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -31,7 +30,7 @@ import (
|
||||
func TestProcess(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
base, err := ioutil.TempDir("", "")
|
||||
base, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(base)
|
||||
store := filesystem.New(filesystem.WithLocation(base))
|
||||
|
||||
@@ -16,7 +16,7 @@ package walletimport
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -58,7 +58,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
}
|
||||
if !strings.HasPrefix(viper.GetString("data"), "0x") {
|
||||
// Assume this is a path; read the file and replace the path with its contents.
|
||||
fileData, err := ioutil.ReadFile(viper.GetString("data"))
|
||||
fileData, err := os.ReadFile(viper.GetString("data"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read wallet import data")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2022 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
|
||||
@@ -15,7 +15,6 @@ package walletsharedexport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -31,7 +30,7 @@ import (
|
||||
func TestProcess(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
base, err := ioutil.TempDir("", "")
|
||||
base, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(base)
|
||||
store := filesystem.New(filesystem.WithLocation(base))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2022 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
|
||||
@@ -15,7 +15,7 @@ package walletsharedimport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -52,7 +52,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
if viper.GetString("file") == "" {
|
||||
return nil, errors.New("file is required")
|
||||
}
|
||||
data.file, err = ioutil.ReadFile(viper.GetString("file"))
|
||||
data.file, err = os.ReadFile(viper.GetString("file"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read wallet import file")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2022 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
|
||||
@@ -16,7 +16,6 @@ package walletsharedimport
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -38,7 +37,7 @@ func TestInput(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
datFile := filepath.Join(dir, "backup.dat")
|
||||
require.NoError(t, ioutil.WriteFile(datFile, []byte("dummy"), 0600))
|
||||
require.NoError(t, os.WriteFile(datFile, []byte("dummy"), 0600))
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
store := scratch.New()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2022 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
|
||||
@@ -15,7 +15,6 @@ package walletsharedimport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -40,7 +39,7 @@ func TestProcess(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
datFile := filepath.Join(dir, "backup.dat")
|
||||
require.NoError(t, ioutil.WriteFile(datFile, export, 0600))
|
||||
require.NoError(t, os.WriteFile(datFile, export, 0600))
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -45,14 +45,10 @@ func init() {
|
||||
walletCmd.AddCommand(walletCreateCmd)
|
||||
walletFlags(walletCreateCmd)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
218
docs/changingwithdrawalcredentials.md
Normal file
218
docs/changingwithdrawalcredentials.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Changing withdrawal credentials
|
||||
When creating a validator it is possible to set its withdrawal credentials to those based upon a BLS private key (known as BLS withdrawal credentials, or "type 0" withdrawal credentials) or based upon an Ethereum execution address (known as execution withdrawal credentials, or "type 1" withdrawal credentials). With the advent of the Capella hard fork, it is possible for rewards accrued on the consensus chain (also known as the beacon chain) to be sent to the execution chain. However, for this to occur the validator's withdrawal credentials must be type 1. Capella also brings a mechanism to change existing type 0 withdrawal credentials to type 1 withdrawal credentials, and this document outlines the process to change withdrawal credentials from type 0 to type 1 so that consensus rewards can be accessed.
|
||||
|
||||
**Once a validator has Ethereum execution credentials set they cannot be changed. Please be careful when following this or any similar process to ensure that you have access to the private key (either as a software file, a hardware key or a mnemonic) of the withdrawal address you use so that you have the ability to access your rewards.**
|
||||
|
||||
## Concepts
|
||||
The following concepts are useful when understanding the rest of this guide.
|
||||
|
||||
### Validator
|
||||
A validator is a logical entity that secures the Ethereum beacon chain (and hence the execution chain) by proposing blocks and attesting to blocks proposed by other validators.
|
||||
|
||||
### Withdrawal credentials
|
||||
Withdrawal credentials, held as part of a validator's on-chain definition, define where consensus rewards will be sent.
|
||||
|
||||
### Private key
|
||||
A private key is a hexadecimal string (_e.g._ 0x010203…a1a2a3) that can be used to generate a public key and (in the case of the execution chain) Ethereum address.
|
||||
|
||||
### Mnemonic
|
||||
A mnemonic is a 24-word phrase that can be used to generate multiple private keys with the use of _paths_.
|
||||
|
||||
### Path
|
||||
A path is a string starting with "m" and containing a number of components separated by "/", for example "m/12381/3600/0/0". The process to obtain a key from a mnemonic and path is known as "hierarchical derivation".
|
||||
|
||||
### Withdrawal address
|
||||
A withdrawal address is an Ethereum execution address that will receive consensus rewards periodically during the operation of the validator and, ultimately, to which the initial deposit will be returned when the validator is exited. It is important to understand that at time of writing this value cannot be changed, so it is critical that one of the following criteria are met:
|
||||
|
||||
- the private keys for the Ethereum address are known
|
||||
- the Ethereum address is secured by a hardware wallet
|
||||
- the Ethereum address is that of a smart contract with the ability to withdraw funds
|
||||
|
||||
The execution address must be supplied in [EIP-55](https://eips.ethereum.org/EIPS/eip-55) format, _i.e._ using mixed case for checksum. An example of a mixed-case Ethereum address is `0x8f0844Fd51E31ff6Bf5baBe21DCcf7328E19Fd9F`
|
||||
|
||||
### Online and Offline
|
||||
An _online_ computer is one that is is connected to the internet. It should be running a consensus node connected to the larger Ethereum network. An online computer is required to carry out the process, to obtain information from the consensus node and to broadcast your actions to the rest of the Ethereum network.
|
||||
|
||||
An _offline_ computer is one that is not connected to the internet. As such, it will not be running a consensus node. It can optionally be used in conjunction with an online computer to provide higher levels of security for your mnemonic or private key, but is less convenient because it requires manual transfer of files from the online computer to the offline computer, and back.
|
||||
|
||||
With only an online computer the flow of information is roughly as follows:
|
||||
|
||||

|
||||
|
||||
Here it can be seen that a copy of `ethdo` with access to private keys connects to a consensus node with access to the internet. Due to its connection to the internet it is possible that the computer on which `ethdo` and the consensus node runs has been compromised, and as such would expose the private keys to an attacker.
|
||||
|
||||
With both an offline and an online computer the flow of information is roughly as follows:
|
||||
|
||||

|
||||
|
||||
Here the copy of `ethdo` with access to private keys is on an offline computer, which protects it from being compromised via the internet. Data is physically moved from the offline to the online computer via a USB storage key or similar, and none of the information on the online computer is sensitive.
|
||||
|
||||
## Preparation
|
||||
Regardless of the method selected, preparation must take place on the online computer to ensure that `ethdo` can access your consensus node. `ethdo` will attempt to find a local consensus node automatically, but if not then an explicit connection value will be required. To find out if `ethdo` has access to the consensus node run:
|
||||
|
||||
```
|
||||
ethdo node info --verbose
|
||||
```
|
||||
|
||||
The result should be something similar to the following:
|
||||
|
||||
```
|
||||
Version: teku/v22.9.1/linux-x86_64/-privatebuild-openjdk64bitservervm-java-14
|
||||
Syncing: false
|
||||
```
|
||||
|
||||
It is important to confirm that the "Syncing" value is "false". If this is "true" it means that the node is currently syncing, and you will need to wait for the process to finish before proceeding.
|
||||
|
||||
If this command instead returns an error you will need to add an explicit connection string. For example, if your consensus node is serving its REST API on port 12345 then you should add `--connection=http://localhost:12345` to all `ethdo` commands in this process, for example:
|
||||
|
||||
```sh
|
||||
ethdo --connection=http://localhost:12345 node info --verbose
|
||||
```
|
||||
|
||||
Note that some consensus nodes may require configuration to serve their REST API. Please refer to the documentation of your specific consensus node to enable this.
|
||||
|
||||
Once the preparation is complete you should select either basic or advanced operation, depending on your requirements.
|
||||
|
||||
## Basic operation
|
||||
Given the above concepts, the purpose of this guide is to allow a change of validators' withdrawal credentials to be changed to a withdrawal address, allowing validator rewards to be accessed on the Ethereum execution chain.
|
||||
|
||||
Basic operation is suitable in the majority of cases. If you:
|
||||
|
||||
- generated your validators using a mnemonic (_e.g._ using the deposit CLI or launchpad)
|
||||
- want to change all of your validators to have the same withdrawal address
|
||||
- want to change all of your validators' withdrawal credentials at the same time
|
||||
|
||||
then this method is for you. If any of the above does not apply then please go to the "Advanced operation" section.
|
||||
|
||||
### Online process
|
||||
The online process generates and broadcasts the operations to change withdrawal credentials for all of your validators tied to a mnemonic in a single action.
|
||||
|
||||
Two pieces of information are required for carrying out this process online: the mnemonic and withdrawal address.
|
||||
|
||||
On your _online_ computer run the following:
|
||||
|
||||
```
|
||||
ethdo validator credentials set --mnemonic="abandon abandon abandon … art" --withdrawal-address=0x0123…cdef
|
||||
```
|
||||
|
||||
Replacing the `mnemonic` and `withdrawal-address` values with your own values. This command will:
|
||||
|
||||
1. obtain information from your consensus node about all currently-running validators and various additional information required to generate the operations
|
||||
2. scan your mnemonic to find any validators that were generated by it, and create the operations to change their credentials
|
||||
3. broadcast the credentials change operations to the Ethereum network
|
||||
|
||||
### Online and Offline process
|
||||
The online and offline process contains three steps. In the first, data is gathered on the online computer. In the second, the credentials change operations are generated on the offline computer. In the third, the operations are broadcast on the online computer.
|
||||
|
||||
Two pieces of information are required for carrying out this process online: the mnemonic and withdrawal address.
|
||||
|
||||
On your _online_ computer run the following:
|
||||
|
||||
```
|
||||
ethdo validator credentials set --prepare-offline
|
||||
```
|
||||
|
||||
This command will:
|
||||
|
||||
1. obtain information from your consensus node about all currently-running validators and various additional information required to generate the operations
|
||||
2. write this information to a file called `offline-preparation.json`
|
||||
|
||||
The `offline-preparation.json` file must be copied to your _offline_ computer. Once this has been done, on your _offline_ computer run the following:
|
||||
|
||||
```
|
||||
ethdo validator credentials set --offline --mnemonic="abandon abandon abandon … art" --withdrawal-address=0x0123…cdef
|
||||
```
|
||||
|
||||
Replacing the `mnemonic` and `withdrawal-address` values with your own values. This command will:
|
||||
|
||||
1. read the `offline-preparation.json` file to obtain information about all currently-running validators and various additional information required to generate the operations
|
||||
2. scan your mnemonic to find any validators that were generated by it, and create the operations to change their credentials
|
||||
3. write this information to a file called `change-operations.json`
|
||||
|
||||
The `change-operations.json` file must be copied to your _online_ computer. Once this has been done, on your _online_ computer run the following:
|
||||
|
||||
```
|
||||
ethdo validator credentials set
|
||||
```
|
||||
|
||||
This command will:
|
||||
|
||||
1. read the `change-operations.json` file to obtain the operations to change the validators' credentials
|
||||
2. broadcast the credentials change operations to the Ethereum network
|
||||
|
||||
## Advanced operation
|
||||
Advanced operation is required when any of the following conditions are met:
|
||||
|
||||
- your validators were created using something other than the deposit CLI or launchpad (_e.g._ `ethdo`)
|
||||
- you want to set your validators to have different withdrawal addresses
|
||||
- you want to change your validators' withdrawal credentials individually
|
||||
|
||||
### Validator reference
|
||||
There are three options to reference a validator:
|
||||
|
||||
- the `ethdo` account of the validator (in format wallet/account)
|
||||
- the validator's public key (in format 0x…)
|
||||
- the validator's on-chain index (in format 123…)
|
||||
|
||||
Any of these can be passed to the following commands with the `--validator` parameter. You need to ensure that you have this information before starting the process.
|
||||
|
||||
**In the following examples we will use the validator with index 123. Please replace this with the reference to your validator in all commands.**
|
||||
|
||||
### Withdrawal address
|
||||
The withdrawal address is defined above in the concepts section.
|
||||
|
||||
**In the following examples we will use a withdrawal address of 0x8f…9F. Please replace this with the your withdrawal address in all commands.**
|
||||
|
||||
### Generating credentials change operations
|
||||
Note that if you are carrying out this process offline then you still need to carry out the first and third steps outlined in the "Basic operation" section above. This is to ensure that the offline computer has the correct information to generate the operations, and that the operations are made available to the online computer for broadcasting to the network.
|
||||
|
||||
If using the online and offline process run the commands below on the offline computer, and add the `--offline` flag to the commands below. You will need to copy the resultant `change-operations.json` file to the online computer to broadcast to the network.
|
||||
|
||||
If using the online process run the commands below on the online computer. The operation will be broadcast to the network automatically.
|
||||
|
||||
#### Using a mnemonic and path.
|
||||
A mnemonic is a 24-word phrase from which withdrawal and validator keys are derived using a _path_. Commonly, keys will have been generated using two paths:
|
||||
|
||||
- m/12381/3600/_i_/0 is the path to a withdrawal key, where _i_ starts at 0 for the first validator, 1 for the second validator, _etc._
|
||||
- m/12381/3600/_i_/0/0 is the path to a validator key, where _i_ starts at 0 for the first validator, 1 for the second validator, _etc._
|
||||
|
||||
however this is only a standard and not a restriction, and it is possible for users to have created validators using paths of their own choice.
|
||||
|
||||
```
|
||||
ethdo validator credentials set --validator=123 --mnemonic="abandon abandon abandon … art" --path='m/12381/3600/0/0/0' --withdrawal-address=0x0123…cdef
|
||||
```
|
||||
|
||||
replacing the path with the path to your _withdrawal_ key, and all other parameters with your own values.
|
||||
|
||||
#### Using a private key
|
||||
If you have the private key from which the current withdrawal credentials were derived this can be used to generate and broadcast the credentials change operation with the following command:
|
||||
|
||||
```
|
||||
ethdo validator credentials set --validator=123 --withdrawal-address=0x8f…9F --private-key=0x3b…9c
|
||||
```
|
||||
|
||||
replacing the parameters with your own values.
|
||||
|
||||
#### Using an account
|
||||
If you used `ethdo` to generate your validator deposit data you will likely have used a separate account to generate the withdrawal credentials. You can specify the account to generate and broadcast the credentials change operation with the following command:
|
||||
|
||||
```
|
||||
ethdo validator credentials set --validator=123 --withdrawal-address=0x8f…9F --account=Wallet/Account --passphrase=secret
|
||||
```
|
||||
|
||||
replacing the parameters with your own values.
|
||||
|
||||
## Confirming the process has succeeded
|
||||
The final step is confirming the operation has taken place. To do so, run the following command on an online server:
|
||||
|
||||
```sh
|
||||
ethdo validator credentials get --validator=123
|
||||
```
|
||||
|
||||
The result should start with the phrase "Ethereum execution address" and display the execution address you chose at the beginning of the process, for example:
|
||||
|
||||
```
|
||||
Ethereum execution address: 0x8f0844Fd51E31ff6Bf5baBe21DCcf7328E19Fd9F
|
||||
```
|
||||
|
||||
If the result starts with the phrase "BLS credentials" then it may be that the operation has yet to be incorporated on the chain, please wait a few minutes and check again. If this continues to be the case please obtain help to understand why the change operation failed to work.
|
||||
@@ -10,7 +10,7 @@ A seed is a 24-word phrase that is used as the start point of a process called h
|
||||
|
||||
The first thing you need to do is to create a wallet. To do this run the command below with the following changes:
|
||||
|
||||
- put your mnemonic in place of the sample `abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art`
|
||||
- put your mnemonic in place of the sample `abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art`
|
||||
- pick a suitable passphrase for your wallet in place of `secret`. You will need to use this in all subsequent commands
|
||||
- rename the wallet to something other than `Wallet` if you so desire. If so, you will need to change it in all subsequent commands
|
||||
|
||||
|
||||
BIN
docs/images/credentials-change-offline.png
Normal file
BIN
docs/images/credentials-change-offline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
BIN
docs/images/credentials-change-online.png
Normal file
BIN
docs/images/credentials-change-online.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
923
docs/images/diagrams.svg
Normal file
923
docs/images/diagrams.svg
Normal file
@@ -0,0 +1,923 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
|
||||
<!-- Title: MerkleTree Pages: 1 -->
|
||||
|
||||
<svg
|
||||
width="1200"
|
||||
height="500"
|
||||
version="1.1"
|
||||
id="svg4363"
|
||||
sodipodi:docname="diagrams.svg"
|
||||
inkscape:export-filename="/home/jgm/src/go/wealdtech/ethdo/docs/images/credentials-change-online.png"
|
||||
inkscape:export-xdpi="180"
|
||||
inkscape:export-ydpi="180"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata4369">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4367">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker4440"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4438" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker4436"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4434" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker4412"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4410"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker4408"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4406"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker4318"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4316"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker4314"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4312"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker4120"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4118" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker4016"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4014" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker4012"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4010" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3717"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3715" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker3713"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3711" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3637"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3635" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker3633"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3631" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker3617"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path3615"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker3613"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path3611"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker3607"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path3605"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker3603"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path3601"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker3453"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3451" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3353"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3351" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker3349"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3347" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3035"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3033" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker3027"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3025" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3001"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path2999" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker2997"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lstart">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path2995" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker2565"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path2563"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker2555"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path2553"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker2532"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend">
|
||||
<path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
id="path2530" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker15035"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend">
|
||||
<path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
id="path15033" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker14901"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend">
|
||||
<path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
id="path14899" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker12697"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path12695"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5569"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend">
|
||||
<path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
id="path5567" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker3116"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Lend">
|
||||
<path
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path3114" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker3102"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path2842"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Lstart"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path1073"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1140"
|
||||
id="namedview4365"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.82"
|
||||
inkscape:cx="600"
|
||||
inkscape:cy="249.39024"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer9"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer7"
|
||||
inkscape:label="Base withdrawal credentials change"
|
||||
style="display:inline">
|
||||
<g
|
||||
id="g3352"
|
||||
transform="translate(330.851,-33.97924)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path3344"
|
||||
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g3350"
|
||||
transform="translate(117.34562,2.27034)">
|
||||
<path
|
||||
id="path3346"
|
||||
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
|
||||
id="path3348"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g10761"
|
||||
transform="matrix(0.19161542,0,0,0.19161542,-2.1459431,201.48294)"
|
||||
style="display:inline">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.95"
|
||||
d="m 224,2 c -67.165,0 -121.59961,54.434608 -121.59961,121.59961 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,67.165 54.43461,121.59961 121.59961,121.59961 67.165,0 121.59961,-54.43461 121.59961,-121.59961 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 C 345.59961,56.434608 291.165,2 224,2 Z M 132.99023,276.16992 C 65.255238,279.39992 11.199219,334.7843 11.199219,403.2793 v 18 21.52148 2 2 2 2 2 2 2 2 2 c 0,25.175 20.426562,45.59961 45.601562,45.59961 H 391.19922 c 25.175,0 45.60156,-20.42461 45.60156,-45.59961 v -2 -2 -2 -2 -2 -2 -2 -2 -2 -21.52148 -18 c 0,-68.495 -54.05601,-123.87938 -121.79101,-127.10938 L 269.59961,458 243.5,347.06641 h -39 L 178.40039,458 Z"
|
||||
id="path7306" />
|
||||
<path
|
||||
d="m 224,243.2 c 67.165,0 121.6,-54.435 121.6,-121.6 C 345.6,54.435 291.165,0 224,0 156.835,0 102.4,54.435 102.4,121.6 c 0,67.165 54.435,121.6 121.6,121.6 z M 315.01,274.17 269.6,456 239.2,326.8 269.6,273.6 H 178.4 L 208.8,326.8 178.4,456 132.99,274.17 C 65.255,277.4 11.2,332.785 11.2,401.28 v 39.52 c 0,25.175 20.425,45.6 45.6,45.6 h 334.4 c 25.175,0 45.6,-20.425 45.6,-45.6 v -39.52 c 0,-68.495 -54.055,-123.88 -121.79,-127.11 z"
|
||||
id="path1534"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke-width:0.95" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 224.11719,272.89258 c -30.84856,-1e-5 -61.38197,0.44231 -91.30664,1.25 v 0.0391 c 0.0601,-0.003 0.11956,-0.009 0.17968,-0.0117 L 178.40039,456 208.80078,326.80078 178.40039,273.59961 h 91.19922 L 239.19922,326.80078 269.59961,456 315.00977,274.16992 c 0.16788,0.008 0.33423,0.0206 0.50195,0.0293 v -0.0547 c -29.94323,-0.80978 -60.50517,-1.25196 -91.39453,-1.25195 z"
|
||||
id="path10733"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4284"
|
||||
transform="translate(608.49706,-233.4392)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path4276"
|
||||
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g4282"
|
||||
transform="translate(117.34562,2.27034)">
|
||||
<path
|
||||
id="path4278"
|
||||
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
|
||||
id="path4280"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4294"
|
||||
transform="translate(608.49706,-33.97924)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path4286"
|
||||
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g4292"
|
||||
transform="translate(117.34562,2.27034)">
|
||||
<path
|
||||
id="path4288"
|
||||
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
|
||||
id="path4290"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g4304"
|
||||
transform="translate(608.49706,165.48072)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path4296"
|
||||
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g4302"
|
||||
transform="translate(117.34562,2.27034)">
|
||||
<path
|
||||
id="path4298"
|
||||
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
|
||||
id="path4300"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 657.63833,252.24403 H 804.3783"
|
||||
id="path6146"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 935.28443,252.24403 H 1082.0244"
|
||||
id="path6247"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 936.44494,192.24403 1083.1849,97.693151"
|
||||
id="path6249"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 936.65042,312.24403 1083.3904,421.52638"
|
||||
id="path6251"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="873.41541"
|
||||
y="317.23297"
|
||||
id="text8140"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan8138"
|
||||
x="873.41541"
|
||||
y="317.23297"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">Local</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="873.41541"
|
||||
y="340.56635"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan11236">consensus node</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="1151.2808"
|
||||
y="145.29068"
|
||||
id="text11322"><tspan
|
||||
sodipodi:role="line"
|
||||
x="1151.2808"
|
||||
y="145.29068"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan11320">Consensus</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="1151.2808"
|
||||
y="168.62405"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan12530">network</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="1151.2808"
|
||||
y="344.75064"
|
||||
id="text12616"><tspan
|
||||
sodipodi:role="line"
|
||||
x="1151.2808"
|
||||
y="344.75064"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan12612">Consensus</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="1151.2808"
|
||||
y="368.08401"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan12614">network</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="40.075897"
|
||||
y="321.42487"
|
||||
id="text12622"><tspan
|
||||
sodipodi:role="line"
|
||||
x="40.075897"
|
||||
y="321.42487"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan14902">User</tspan></text>
|
||||
<g
|
||||
id="g27545"
|
||||
transform="translate(2.7996094e-5,147.10741)"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path27529"
|
||||
d="m 596.06796,54.89213 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="48.639999"
|
||||
cy="103.13213"
|
||||
cx="596.06799"
|
||||
id="circle27531"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g27537"
|
||||
transform="translate(-165.65975,5.478358)">
|
||||
<path
|
||||
d="m 780.23799,97.443192 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.056218 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.027298 a 2.8922269,2.8922269 0 0 0 -4.88787,2.082398 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.660838 z"
|
||||
fill="#000000"
|
||||
id="path27533"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
|
||||
<path
|
||||
d="m 780.23799,66.409593 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370005 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370005 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
|
||||
fill="#000000"
|
||||
id="path27535"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
|
||||
</g>
|
||||
<g
|
||||
id="g27543">
|
||||
<path
|
||||
d="m 614.57824,100.92155 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.05622 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.0824 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.66084 z"
|
||||
fill="#000000"
|
||||
id="path27539"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
|
||||
<path
|
||||
d="m 614.57824,69.887949 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370009 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370009 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
|
||||
fill="#000000"
|
||||
id="path27541"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="Online withdrawal credentials change"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 104.72769,252.24403 H 524.35081"
|
||||
id="path5578" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="596.06799"
|
||||
y="321.42487"
|
||||
id="text11108"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan11106"
|
||||
x="596.06799"
|
||||
y="321.42487"
|
||||
style="text-align:center;text-anchor:middle;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="596.06799"
|
||||
y="344.75824"
|
||||
style="text-align:center;text-anchor:middle;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan11132">(with keys)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="599.86401"
|
||||
y="26.809605"
|
||||
id="text27911"><tspan
|
||||
sodipodi:role="line"
|
||||
x="599.86401"
|
||||
y="26.809605"
|
||||
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan27909">Online</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer8"
|
||||
inkscape:label="Offline withdrawal credentials change"
|
||||
style="display:none">
|
||||
<g
|
||||
id="g27447"
|
||||
transform="translate(-277.64603,147.10741)">
|
||||
<path
|
||||
id="path27431"
|
||||
d="m 596.06796,54.89213 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="48.639999"
|
||||
cy="103.13213"
|
||||
cx="596.06799"
|
||||
id="circle27433"
|
||||
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
id="g27439"
|
||||
transform="translate(-165.65975,5.478358)">
|
||||
<path
|
||||
d="m 780.23799,97.443192 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.056218 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.027298 a 2.8922269,2.8922269 0 0 0 -4.88787,2.082398 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.660838 z"
|
||||
fill="#000000"
|
||||
id="path27435"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
|
||||
<path
|
||||
d="m 780.23799,66.409593 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370005 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370005 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
|
||||
fill="#000000"
|
||||
id="path27437"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
|
||||
</g>
|
||||
<g
|
||||
id="g27445">
|
||||
<path
|
||||
d="m 614.57824,100.92155 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.05622 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.0824 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.66084 z"
|
||||
fill="#000000"
|
||||
id="path27441"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
|
||||
<path
|
||||
d="m 614.57824,69.887949 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370009 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370009 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
|
||||
fill="#000000"
|
||||
id="path27443"
|
||||
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#b0b0b0;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:8, 16;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 452.22833,1.446294 V 498.3684"
|
||||
id="path4358" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 102.34622,252.24403 H 249.08619"
|
||||
id="path6402"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4440)"
|
||||
d="M 379.99228,252.24403 H 526.73225"
|
||||
id="path6404"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="596.06799"
|
||||
y="321.42487"
|
||||
id="text15698"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan15694"
|
||||
x="596.06799"
|
||||
y="321.42487"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="596.06799"
|
||||
y="344.75824"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan15696">(without keys)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="318.42194"
|
||||
y="321.42487"
|
||||
id="text15707"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan15703"
|
||||
x="318.42194"
|
||||
y="321.42487"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="318.42194"
|
||||
y="344.75824"
|
||||
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan15705">(with keys)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="246.36612"
|
||||
y="26.809605"
|
||||
id="text16335"><tspan
|
||||
sodipodi:role="line"
|
||||
x="246.36612"
|
||||
y="26.809605"
|
||||
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan16333">Offline</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="801.6582"
|
||||
y="26.809605"
|
||||
id="text18181"><tspan
|
||||
sodipodi:role="line"
|
||||
x="801.6582"
|
||||
y="26.809605"
|
||||
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="tspan18179">Online</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 66 KiB |
@@ -581,12 +581,18 @@ Validator commands focus on interaction with Ethereum 2 validators.
|
||||
#### `credentials get`
|
||||
|
||||
`ethdo validator credentials get` provides information about the withdrawal credentials for the provided validator. Options include:
|
||||
- `account` the account for which to obtain the withdrawal credentials (in format "wallet/account")
|
||||
- `pubkey` the public key of the validator for which to obtain the withdrawal credentials
|
||||
- `index` the index of the validator for which to obtain the withdrawal credentials
|
||||
- `validator` the account, public key or index for which to obtain the withdrawal credentials
|
||||
|
||||
```sh
|
||||
$ ethdo validator credentials get --account=Validators/1
|
||||
$ ethdo validator credentials get --validator=Validators/1
|
||||
```
|
||||
|
||||
#### `credentials set`
|
||||
|
||||
`ethdo validator credentials set` updates withdrawal credentials from BLS "type 0" credentials to execution "type 1" credentials. Full information about using this command can be found in the [specific documentation](./changingwithdrawalcredentials.md).
|
||||
|
||||
```sh
|
||||
$ ethdo validator credentials set --validator=Validators/1 --execution-address=0x8f…9F --private-key=0x3b…9c
|
||||
```
|
||||
|
||||
#### `depositdata`
|
||||
@@ -622,7 +628,7 @@ $ ethdo validator exit --key=0x01e748d098d3bcb477d636f19d510399ae18205fadf9814ee
|
||||
`ethdo validator info` provides information for a given validator.
|
||||
|
||||
```sh
|
||||
$ ethdo validator info --account=Validators/1
|
||||
$ ethdo validator info --validator=Validators/1
|
||||
Status: Active
|
||||
Balance: 3.203823585 Ether
|
||||
Effective balance: 3.1 Ether
|
||||
@@ -641,7 +647,7 @@ Effective balance: 3.1 Ether
|
||||
Withdrawal credentials: 0x0033ef3cb10b36d0771ffe8a02bc5bfc7e64ea2f398ce77e25bb78989edbee36
|
||||
```
|
||||
|
||||
If the validator is not an account it can be queried directly with `--pubkey`.
|
||||
If the validator is not an account then `--validator` option can be supplied with a validator index or public key.
|
||||
|
||||
```sh
|
||||
$ ethdo validator info --pubkey=0x842dd66cfeaeff4397fc7c94f7350d2131ca0c4ad14ff727963be9a1edb4526604970df6010c3da6474a9820fa81642b
|
||||
@@ -711,6 +717,12 @@ $ ethdo validator yield
|
||||
Yield: 4.64%
|
||||
```
|
||||
|
||||
#### `summary`
|
||||
`ethdo validator summary` provides a summary of the given epoch for the given validators. Options include:
|
||||
- `epoch`: the epoch for which to provide a summary; defaults to last complete epoch
|
||||
- `validators`: the list of validators for which to provide a summary
|
||||
- `json`: provide JSON output
|
||||
|
||||
### `proposer` commands
|
||||
|
||||
Proposer commands focus on Ethereum 2 validators' actions as proposers.
|
||||
|
||||
89
go.mod
89
go.mod
@@ -1,51 +1,90 @@
|
||||
module github.com/wealdtech/ethdo
|
||||
|
||||
go 1.16
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/attestantio/dirk v1.1.0
|
||||
github.com/attestantio/go-eth2-client v0.11.0
|
||||
github.com/aws/aws-sdk-go v1.42.44 // indirect
|
||||
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b
|
||||
github.com/attestantio/go-eth2-client v0.14.5
|
||||
github.com/ferranbt/fastssz v0.1.2
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7
|
||||
github.com/jackc/puddle v1.2.1 // indirect
|
||||
github.com/minio/highwayhash v1.0.2 // indirect
|
||||
github.com/herumi/bls-eth-go-binary v1.28.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/protolambda/zssz v0.1.5 // indirect
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/spf13/afero v1.8.0 // indirect
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/spf13/viper v1.13.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
github.com/wealdtech/go-bytesutil v1.1.1
|
||||
github.com/wealdtech/go-bytesutil v1.2.0
|
||||
github.com/wealdtech/go-ecodec v1.1.2
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.6.0
|
||||
github.com/wealdtech/go-eth2-util v1.7.0
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.8.0
|
||||
github.com/wealdtech/go-eth2-util v1.8.0
|
||||
github.com/wealdtech/go-eth2-wallet v1.15.0
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.2.0
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.6.0
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.4.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.17.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.10.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.11.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.0
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.9.0
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0
|
||||
github.com/wealdtech/go-string2eth v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed // indirect
|
||||
golang.org/x/text v0.3.7
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
|
||||
google.golang.org/grpc v1.44.0
|
||||
gopkg.in/ini.v1 v1.66.3 // indirect
|
||||
golang.org/x/text v0.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.44.152 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/goccy/go-yaml v1.9.4 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jackc/puddle v1.3.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/minio/highwayhash v1.0.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/protolambda/zssz v0.1.5 // indirect
|
||||
github.com/r3labs/sse/v2 v2.7.4 // indirect
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/wealdtech/eth2-signer-api v1.7.1 // indirect
|
||||
github.com/wealdtech/go-indexer v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/net v0.2.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
172
go.sum
172
go.sum
@@ -56,8 +56,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
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/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -75,16 +73,15 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
|
||||
github.com/attestantio/dirk v1.1.0 h1:hwMTYZkwj/Y0um3OD0LQxg2xSl4/5xqVWV2MRePE4ec=
|
||||
github.com/attestantio/dirk v1.1.0/go.mod h1:2jkOw/XHjvIDdhDcmj+Z3kuVPpxMcQ6zxzzjSSv71PY=
|
||||
github.com/attestantio/go-eth2-client v0.8.1/go.mod h1:kEK9iAAOBoADO5wEkd84FEOzjT1zXgVWveQsqn+uBGg=
|
||||
github.com/attestantio/go-eth2-client v0.11.0 h1:8/Jn5AAfd+4tOggLi+FvOv9/ORaObECv42ab7vK2FJc=
|
||||
github.com/attestantio/go-eth2-client v0.11.0/go.mod h1:zXL/BxC0cBBhxj+tP7QG7t9Ufoa8GwQLdlbvZRd9+dM=
|
||||
github.com/attestantio/go-eth2-client v0.14.5 h1:pKOTcbv9KOiVixVKM5Cr8nJrjD1VOIWmrlNFO0YtF64=
|
||||
github.com/attestantio/go-eth2-client v0.14.5/go.mod h1:5kLLzdlyPGboWr8tAwnG/4Kpi43BHd/HWp++WmmP6Ws=
|
||||
github.com/aws/aws-sdk-go v1.33.17/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.40.41/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.41.19/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.42.44 h1:vPlF4cUsdN5ETfvb7ewZFbFZyB6Rsfndt3kS2XqLXKo=
|
||||
github.com/aws/aws-sdk-go v1.42.44/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
|
||||
github.com/aws/aws-sdk-go v1.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI=
|
||||
github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -116,6 +113,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
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/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -154,12 +152,14 @@ github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iu
|
||||
github.com/ferranbt/fastssz v0.0.0-20210526181520-7df50c8568f8/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
|
||||
github.com/ferranbt/fastssz v0.0.0-20211031100431-9823ca9021f1/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
|
||||
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b h1:Jea4sHxe4sTegJgpfhWvxSjFF2nyq4/R/qWm6AziPiI=
|
||||
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
|
||||
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
|
||||
github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
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-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -180,15 +180,14 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
|
||||
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/goccy/go-yaml v1.8.9/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/goccy/go-yaml v1.9.2/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
|
||||
github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0=
|
||||
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -241,8 +240,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -282,7 +282,6 @@ github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsD
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
@@ -317,16 +316,16 @@ github.com/herumi/bls-eth-go-binary v0.0.0-20210520070601-31246bfa8ac4/go.mod h1
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20210902234237-7763804ee078/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20211117070716-2bdbdadbf8bb/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7 h1:gMN4oOdFLVR7ye4SCacHD4SIB64NPUNE+V9w8o7bR6A=
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/herumi/bls-eth-go-binary v1.28.1 h1:fcIZ48y5EE9973k05XjE8+P3YiQgjZz4JI/YabAm8KA=
|
||||
github.com/herumi/bls-eth-go-binary v1.28.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/puddle v1.1.4/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
|
||||
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
@@ -347,20 +346,22 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A=
|
||||
github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI=
|
||||
github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -369,24 +370,26 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
@@ -405,8 +408,9 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -424,8 +428,11 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -441,25 +448,21 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
|
||||
github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
|
||||
@@ -470,24 +473,28 @@ github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388 h1:4bD+ujqGfY4zoDUF3q9MhdmpPXzdp03DYUIlXeQ72kk=
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk=
|
||||
github.com/r3labs/sse/v2 v2.3.0/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
|
||||
github.com/r3labs/sse/v2 v2.7.4 h1:pvCMswPDlXd/ZUFx1dry0LbXJNHXwWPulLcUGYwClc0=
|
||||
github.com/r3labs/sse/v2 v2.7.4/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
|
||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
|
||||
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=
|
||||
@@ -495,19 +502,18 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
|
||||
github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
@@ -520,21 +526,25 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
|
||||
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
||||
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
|
||||
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
|
||||
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/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
|
||||
@@ -544,17 +554,20 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
||||
github.com/wealdtech/eth2-signer-api v1.7.1 h1:XdwFuv3VWCwcPPPrfa77sUXL1GSvxDtsUZxlByz//b0=
|
||||
github.com/wealdtech/eth2-signer-api v1.7.1/go.mod h1:fX8XtN9Svyjs+e7TgoOfOcwRTHeblR5SXftAVV3T1ZA=
|
||||
github.com/wealdtech/go-bytesutil v1.0.1/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-bytesutil v1.2.0 h1:GEIzvAZEIgqOoRfnEAaMRNL73gl8e+YlQzqxhFyR30Y=
|
||||
github.com/wealdtech/go-bytesutil v1.2.0/go.mod h1:FHQSlwhzfSZGffu1osaUGdnNtl5C8tBKwmqvPdB66pM=
|
||||
github.com/wealdtech/go-ecodec v1.1.1/go.mod h1:PSdBFEB6cltdT7V4E1jbboufMZTZXcQOKG/2PeEjKK4=
|
||||
github.com/wealdtech/go-ecodec v1.1.2 h1:CcNBnFV6b8IoDtu8wj6cfrmuBRBw00VYqsvpT4+I44w=
|
||||
github.com/wealdtech/go-ecodec v1.1.2/go.mod h1:0Rgn8mEr0GX4fDyuU3aUL+xp4n17Wviscasp6rWCh10=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.5/go.mod h1:QY9t7nwxc26C9/iZHh1wzc+hZbGGn7ipgjMYPhKnN/0=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.6/go.mod h1:SX7+QxnhXGxeW8tESzJ/BSIXvoflBHUi21chqRsZ9wE=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.6.0 h1:djgMv1A40bstgkg6L1ZA7eowR/Gbmj1ZWnBdrK39lhY=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.6.0/go.mod h1:psOez/ZRBzZSDl5hiNDwRf5ZqQujNE6h5FxAz09Koxg=
|
||||
github.com/wealdtech/go-eth2-util v1.7.0 h1:bgCnZ4H5K5VE+CiAwFTKhrICItq5rI+5VYaODFWC6is=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.8.0 h1:Cts9J78ryXVp8jwotdSSVU75S+QWJrgVCArXreD2X8A=
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.8.0/go.mod h1:tJazo9o28kdQs3V/U4VafQ4neG+/sL3OBozQ8J3CWmo=
|
||||
github.com/wealdtech/go-eth2-util v1.7.0/go.mod h1:L0q3G7S1Dm1WJPye5m2wjuNweXC98o1CmLTKPmBTlpg=
|
||||
github.com/wealdtech/go-eth2-util v1.8.0 h1:hhs3h2y3Ldty18lppFdpe46nZpdDAMbY7QqiHO5BvE0=
|
||||
github.com/wealdtech/go-eth2-util v1.8.0/go.mod h1:rSuE0v5zX+uyZrqW/iUmXOxeDpB7lTvhcZvAVh0KlMU=
|
||||
github.com/wealdtech/go-eth2-wallet v1.15.0 h1:yff2rWCvHyr5bOU41wwilsNemFRYOFinZoIPjhzG10M=
|
||||
github.com/wealdtech/go-eth2-wallet v1.15.0/go.mod h1:BqXnen6XCa99yaUx/XIcyImaZLJMMj4wIMcUBywlMJs=
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.2.0 h1:GNrxDo1ajDbUU8XvCHku1ImFhgtV79oNVHEZlRfu9f4=
|
||||
@@ -562,8 +575,9 @@ github.com/wealdtech/go-eth2-wallet-dirk v1.2.0/go.mod h1:1lnHDSdcRxj9CFJh+jPtdg
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4 h1:EBk/FofiQWtbRYmwdCp0qM9TPDvlh1LN2yjlLr4W3ao=
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4/go.mod h1:Y31pDxcdyADwfQl65t8V6KhcmCes31EXkXZPDcT2SIk=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.6/go.mod h1:hIeHsLJWVxFUUqQqaBm9nmUW3Y/eHneQclYyNayUeQg=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0 h1:HgIK30YsXxgR6Ra6pvxW1KFUPbx7BpIIK1VGhOlmCm4=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0/go.mod h1:qqIU42c9sXcNYsiEjUQoOOWYZfZDL1zmyLtz3t+wN2s=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0 h1:3Kx2QvKU/4PP0d7+e3+q9G+QiXQprNQPeAXsWOcfKRI=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0/go.mod h1:qqIU42c9sXcNYsiEjUQoOOWYZfZDL1zmyLtz3t+wN2s=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.1 h1:x6bq8cVgRgfhwtSQSYo/9AqJ8qEeaS6af28cW0cVj5U=
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.1/go.mod h1:49K88T/4LNQpB8ghVcjTKeRRi/bZHeYjN8Ef5S23yps=
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.6.0 h1:Oy5TsD6HqZzb3MwGOe+5WVCj4pCKKUYYc2OKyN42QCM=
|
||||
@@ -573,15 +587,17 @@ github.com/wealdtech/go-eth2-wallet-nd/v2 v2.4.0/go.mod h1:i95ceNREFwNOQn/17ezSo
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.15/go.mod h1:v/JATYJQsInMeu8TXLBIuFnlEmU5KP6Uuoojl1C1OJ0=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.17.0 h1:cq7k9osiIkaYrdpetPQgk3ozl/dFvmxW364OC/uNuww=
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.17.0/go.mod h1:Fiw5If3/mgH+qYRKIH+kTpZZ3r6z2KgHUiE5Vf/QrfE=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.10.0 h1:WallseqmiLdB6eSLMj7NFERhM10cp7g9Dhyk28xcK3k=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.10.0/go.mod h1:DhAm7si8N/5qU1sZ/RLavm87LsOthnWuRyQaGWNFiyI=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.11.0 h1:qta5XIXe7o9djqB6L90dmf2CvnLmVunzmKeIAnrUyPs=
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.11.0/go.mod h1:azzsylTwr1hnLisDWZJbUz3HToRsrG7ADpLG8TXJgOU=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.3/go.mod h1:V4NUofSBIyzoqc5cNZaGciaDm2WFAgSQikRslOyh5Tg=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.0 h1:1dMKx9jtw1v9JrwOPFf2JaOQKmvpMp1GEeuMRiNfq5o=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.0/go.mod h1:O7BitrDeQVtBFNnvYmOYLzJCZAiCf5ur/4IRucIz+S0=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.4/go.mod h1:+5Hy4MMmX1x/5gSU2C+ucHYv6W3WVYELGzaeGipIcQs=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.5/go.mod h1:pEQC6Y6K+g4yVrdt4TtlRjUDCzkseOm6yxB4jLCsAkc=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.9.0 h1:XqWgsONVqsPvciuEXxM/QU4hYouBVk0+5/pGqDMGUHQ=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.9.0/go.mod h1:7Ad2xp27vOQRQWQsIeHBdU/YiyEt6klBeh5gwnNnlwE=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0 h1:bivQYT1TALvU+3kC9gyx2qX+XS/9PgFnARRX4MbKprA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0/go.mod h1:CYlvo087PNZuihF0Pdnerfs9lhXAW/wLDxySNNkgpVo=
|
||||
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-majordomo v1.0.1/go.mod h1:QoT4S1nUQwdQK19+CfepDwV+Yr7cc3dbF+6JFdQnIqY=
|
||||
@@ -594,6 +610,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
@@ -631,8 +648,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
|
||||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -673,6 +690,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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=
|
||||
@@ -723,11 +741,12 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
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=
|
||||
@@ -755,8 +774,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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=
|
||||
@@ -838,13 +858,20 @@ golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -853,8 +880,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -918,11 +946,13 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
@@ -960,7 +990,6 @@ google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUb
|
||||
google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||
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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1043,8 +1072,8 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 h1:YxHp5zqIcAShDEvRr5/0rVESVS+njYF68PSdazrNLJo=
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 h1:Ezh2cpcnP5Rq60sLensUsFnxh7P6513NLvNtCm9iyJ4=
|
||||
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1074,9 +1103,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
|
||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
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=
|
||||
@@ -1090,8 +1118,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
|
||||
@@ -1103,8 +1132,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w=
|
||||
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -1116,8 +1145,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/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=
|
||||
|
||||
@@ -54,4 +54,6 @@ type Service interface {
|
||||
AltairInitialEpoch() phase0.Epoch
|
||||
// AltairInitialSyncCommitteePeriod provides the sync committee period in which the Altair hard fork takes place.
|
||||
AltairInitialSyncCommitteePeriod() uint64
|
||||
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
|
||||
CapellaInitialEpoch() phase0.Epoch
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type Service struct {
|
||||
epochsPerSyncCommitteePeriod uint64
|
||||
altairForkEpoch phase0.Epoch
|
||||
bellatrixForkEpoch phase0.Epoch
|
||||
capellaForkEpoch phase0.Epoch
|
||||
}
|
||||
|
||||
// module-wide log.
|
||||
@@ -100,6 +101,13 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
||||
}
|
||||
log.Trace().Uint64("epoch", uint64(bellatrixForkEpoch)).Msg("Obtained Bellatrix fork epoch")
|
||||
|
||||
capellaForkEpoch, err := fetchCapellaForkEpoch(ctx, parameters.forkScheduleProvider)
|
||||
if err != nil {
|
||||
// Set to far future epoch.
|
||||
capellaForkEpoch = 0xffffffffffffffff
|
||||
}
|
||||
log.Trace().Uint64("epoch", uint64(capellaForkEpoch)).Msg("Obtained Capella fork epoch")
|
||||
|
||||
s := &Service{
|
||||
genesisTime: genesisTime,
|
||||
slotDuration: slotDuration,
|
||||
@@ -107,6 +115,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
||||
epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod,
|
||||
altairForkEpoch: altairForkEpoch,
|
||||
bellatrixForkEpoch: bellatrixForkEpoch,
|
||||
capellaForkEpoch: capellaForkEpoch,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -247,3 +256,28 @@ func fetchBellatrixForkEpoch(ctx context.Context, provider eth2client.ForkSchedu
|
||||
}
|
||||
return 0, errors.New("no bellatrix fork obtained")
|
||||
}
|
||||
|
||||
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
|
||||
func (s *Service) CapellaInitialEpoch() phase0.Epoch {
|
||||
return s.capellaForkEpoch
|
||||
}
|
||||
|
||||
func fetchCapellaForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
|
||||
forkSchedule, err := provider.ForkSchedule(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count := 0
|
||||
for i := range forkSchedule {
|
||||
count++
|
||||
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
|
||||
// This is the genesis fork; ignore it.
|
||||
continue
|
||||
}
|
||||
if count == 2 {
|
||||
return forkSchedule[i].Epoch, nil
|
||||
}
|
||||
count++
|
||||
}
|
||||
return 0, errors.New("no capella fork obtained")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package shamir
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,6 +20,20 @@ type polynomial struct {
|
||||
coefficients []uint8
|
||||
}
|
||||
|
||||
type cryptoSource struct{}
|
||||
|
||||
func (s cryptoSource) Int63() int64 {
|
||||
bytes := make([]byte, 8)
|
||||
if _, err := cryptorand.Read(bytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return int64(binary.BigEndian.Uint64(bytes) >> 1)
|
||||
}
|
||||
|
||||
func (s cryptoSource) Seed(seed int64) {
|
||||
panic("seed")
|
||||
}
|
||||
|
||||
// makePolynomial constructs a random polynomial of the given
|
||||
// degree but with the provided intercept value.
|
||||
func makePolynomial(intercept, degree uint8) (polynomial, error) {
|
||||
@@ -32,7 +46,7 @@ func makePolynomial(intercept, degree uint8) (polynomial, error) {
|
||||
p.coefficients[0] = intercept
|
||||
|
||||
// Assign random co-efficients to the polynomial
|
||||
if _, err := rand.Read(p.coefficients[1:]); err != nil {
|
||||
if _, err := cryptorand.Read(p.coefficients[1:]); err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
@@ -143,8 +157,8 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
|
||||
}
|
||||
|
||||
// Generate random list of x coordinates
|
||||
mathrand.Seed(time.Now().UnixNano())
|
||||
xCoordinates := mathrand.Perm(255)
|
||||
rnd := mathrand.New(&cryptoSource{})
|
||||
xCoordinates := rnd.Perm(255)
|
||||
|
||||
// Allocate the output array, initialize the final byte
|
||||
// of the output with the offset. The representation of each
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Code generated by fastssz. DO NOT EDIT.
|
||||
// Hash: c953c4b72fdc3f250f4227b181e286fe53fd663c5aa4f5cc3de6cc9998c7fdb7
|
||||
// Version: 0.1.2
|
||||
package signing
|
||||
|
||||
import (
|
||||
@@ -15,15 +17,15 @@ func (c *Container) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'Root'
|
||||
if len(c.Root) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
if size := len(c.Root); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("Container.Root", size, 32)
|
||||
return
|
||||
}
|
||||
dst = append(dst, c.Root...)
|
||||
|
||||
// Field (1) 'Domain'
|
||||
if len(c.Domain) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
if size := len(c.Domain); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("Container.Domain", size, 32)
|
||||
return
|
||||
}
|
||||
dst = append(dst, c.Domain...)
|
||||
@@ -66,19 +68,19 @@ func (c *Container) HashTreeRoot() ([32]byte, error) {
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the Container object with a hasher
|
||||
func (c *Container) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
func (c *Container) HashTreeRootWith(hh ssz.HashWalker) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'Root'
|
||||
if len(c.Root) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
if size := len(c.Root); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("Container.Root", size, 32)
|
||||
return
|
||||
}
|
||||
hh.PutBytes(c.Root)
|
||||
|
||||
// Field (1) 'Domain'
|
||||
if len(c.Domain) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
if size := len(c.Domain); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("Container.Domain", size, 32)
|
||||
return
|
||||
}
|
||||
hh.PutBytes(c.Domain)
|
||||
@@ -86,3 +88,8 @@ func (c *Container) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTree ssz hashes the Container object
|
||||
func (c *Container) GetTree() (*ssz.Node, error) {
|
||||
return ssz.ProofTree(c)
|
||||
}
|
||||
|
||||
122
util/account.go
122
util/account.go
@@ -15,11 +15,126 @@ package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// ParseAccount parses input to obtain an account.
|
||||
func ParseAccount(ctx context.Context,
|
||||
accountStr string,
|
||||
supplementary []string,
|
||||
unlock bool,
|
||||
) (
|
||||
e2wtypes.Account,
|
||||
error,
|
||||
) {
|
||||
if accountStr == "" {
|
||||
return nil, errors.New("no account specified")
|
||||
}
|
||||
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(accountStr, "0x"):
|
||||
// A key. Could be public or private.
|
||||
data, err := hex.DecodeString(strings.TrimPrefix(accountStr, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse account key")
|
||||
}
|
||||
switch len(data) {
|
||||
case 48:
|
||||
// Public key.
|
||||
account, err = newScratchAccountFromPubKey(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create account from public key")
|
||||
}
|
||||
if unlock {
|
||||
return nil, errors.New("cannot unlock an account specified by its public key")
|
||||
}
|
||||
case 32:
|
||||
// Private key.
|
||||
account, err = newScratchAccountFromPrivKey(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create account from public key")
|
||||
}
|
||||
if unlock {
|
||||
_, err = UnlockAccount(ctx, account, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unlock account")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("key of length %d neither public nor private key", len(data))
|
||||
}
|
||||
case strings.Contains(accountStr, "/"):
|
||||
// An account.
|
||||
_, account, err = WalletAndAccountFromPath(ctx, accountStr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to obtain account")
|
||||
}
|
||||
if unlock {
|
||||
// Supplementary will be the unlock passphrase(s).
|
||||
_, err = UnlockAccount(ctx, account, supplementary)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unlock account")
|
||||
}
|
||||
}
|
||||
case strings.Contains(accountStr, " "):
|
||||
// A mnemonic.
|
||||
// Supplementary will be the path.
|
||||
if len(supplementary) == 0 {
|
||||
return nil, errors.New("missing derivation path")
|
||||
}
|
||||
account, err = accountFromMnemonicAndPath(accountStr, supplementary[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unlock {
|
||||
err = account.(e2wtypes.AccountLocker).Unlock(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unlock account")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown account specifier %s", accountStr)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func accountFromMnemonicAndPath(mnemonic string, path string) (e2wtypes.Account, error) {
|
||||
seed, err := SeedFromMnemonic(mnemonic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the path is valid.
|
||||
match := hdPathRegex.Match([]byte(path))
|
||||
if !match {
|
||||
return nil, errors.New("path does not match expected format m/…")
|
||||
}
|
||||
|
||||
// Derive private key from seed and path.
|
||||
key, err := util.PrivateKeyFromSeedAndPath(seed, path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate key")
|
||||
}
|
||||
|
||||
// Create a scratch account given the private key.
|
||||
account, err := newScratchAccountFromPrivKey(key.Marshal())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate scratch account")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// UnlockAccount attempts to unlock an account. It returns true if the account was already unlocked.
|
||||
func UnlockAccount(ctx context.Context, account e2wtypes.Account, passphrases []string) (bool, error) {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
@@ -46,6 +161,13 @@ func UnlockAccount(ctx context.Context, account e2wtypes.Account, passphrases []
|
||||
}
|
||||
}
|
||||
|
||||
// Also attempt to unlock without any passphrase.
|
||||
err = locker.Unlock(ctx, nil)
|
||||
if err == nil {
|
||||
// Unlocked.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Failed to unlock it.
|
||||
return false, errors.New("failed to unlock account")
|
||||
}
|
||||
|
||||
103
util/account_test.go
Normal file
103
util/account_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func TestParseAccount(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
accountStr string
|
||||
supplementary []string
|
||||
unlock bool
|
||||
err string
|
||||
expectedPubkey string
|
||||
expectedUnlocked bool
|
||||
}{
|
||||
{
|
||||
name: "Zero",
|
||||
err: "no account specified",
|
||||
},
|
||||
{
|
||||
name: "Bad",
|
||||
accountStr: "bad",
|
||||
err: "unknown account specifier bad",
|
||||
},
|
||||
{
|
||||
name: "PublicKey",
|
||||
accountStr: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
},
|
||||
{
|
||||
name: "PublicKeyUnlocked",
|
||||
accountStr: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
unlock: true,
|
||||
err: "cannot unlock an account specified by its public key",
|
||||
},
|
||||
{
|
||||
name: "PrivateKey",
|
||||
accountStr: "0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55",
|
||||
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
},
|
||||
{
|
||||
name: "PrivateKeyUnlocked",
|
||||
accountStr: "0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55",
|
||||
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
unlock: true,
|
||||
expectedUnlocked: true,
|
||||
},
|
||||
{
|
||||
name: "Mnemonic",
|
||||
accountStr: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
supplementary: []string{"m/12381/3600/0/0"},
|
||||
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
},
|
||||
{
|
||||
name: "MnemonicUnlocked",
|
||||
accountStr: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||||
supplementary: []string{"m/12381/3600/0/0"},
|
||||
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
|
||||
unlock: true,
|
||||
expectedUnlocked: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
account, err := util.ParseAccount(ctx, test.accountStr, test.supplementary, test.unlock)
|
||||
if test.err == "" {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, account)
|
||||
unlocked, err := account.(e2wtypes.AccountLocker).IsUnlocked(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expectedPubkey, fmt.Sprintf("%#x", account.PublicKey().Marshal()))
|
||||
require.Equal(t, test.expectedUnlocked, unlocked)
|
||||
} else {
|
||||
require.EqualError(t, err, test.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
80
util/attestations.go
Normal file
80
util/attestations.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright © 2022 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"
|
||||
"context"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
)
|
||||
|
||||
// AttestationHeadCorrect returns true if the given attestation had the correct head.
|
||||
func AttestationHeadCorrect(ctx context.Context,
|
||||
headersCache *BeaconBlockHeaderCache,
|
||||
attestation *phase0.Attestation,
|
||||
) (
|
||||
bool,
|
||||
error,
|
||||
) {
|
||||
slot := attestation.Data.Slot
|
||||
for {
|
||||
header, err := headersCache.Fetch(ctx, slot)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if header == nil {
|
||||
// No block.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
if !header.Canonical {
|
||||
// Not canonical.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
return bytes.Equal(header.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
|
||||
}
|
||||
}
|
||||
|
||||
// AttestationTargetCorrect returns true if the given attestation had the correct target.
|
||||
func AttestationTargetCorrect(ctx context.Context,
|
||||
headersCache *BeaconBlockHeaderCache,
|
||||
chainTime chaintime.Service,
|
||||
attestation *phase0.Attestation,
|
||||
) (
|
||||
bool,
|
||||
error,
|
||||
) {
|
||||
// Start with first slot of the target epoch.
|
||||
slot := chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
||||
for {
|
||||
header, err := headersCache.Fetch(ctx, slot)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if header == nil {
|
||||
// No block.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
if !header.Canonical {
|
||||
// Not canonical.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
return bytes.Equal(header.Root[:], attestation.Data.Target.Root[:]), nil
|
||||
}
|
||||
}
|
||||
70
util/beaconheadercache.go
Normal file
70
util/beaconheadercache.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright © 2022 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
)
|
||||
|
||||
// BeaconBlockHeaderCache is a cache of beacon block headers.
|
||||
type BeaconBlockHeaderCache struct {
|
||||
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||
entries map[phase0.Slot]*beaconBlockHeaderEntry
|
||||
}
|
||||
|
||||
// NewBeaconBlockHeaderCache makes a new beacon block header cache.
|
||||
func NewBeaconBlockHeaderCache(provider eth2client.BeaconBlockHeadersProvider) *BeaconBlockHeaderCache {
|
||||
return &BeaconBlockHeaderCache{
|
||||
beaconBlockHeadersProvider: provider,
|
||||
entries: make(map[phase0.Slot]*beaconBlockHeaderEntry),
|
||||
}
|
||||
}
|
||||
|
||||
type beaconBlockHeaderEntry struct {
|
||||
present bool
|
||||
value *apiv1.BeaconBlockHeader
|
||||
}
|
||||
|
||||
// Fetch the beacon block header for the given slot.
|
||||
func (b *BeaconBlockHeaderCache) Fetch(ctx context.Context,
|
||||
slot phase0.Slot,
|
||||
) (
|
||||
*apiv1.BeaconBlockHeader,
|
||||
error,
|
||||
) {
|
||||
entry, exists := b.entries[slot]
|
||||
if !exists {
|
||||
header, err := b.beaconBlockHeadersProvider.BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if header == nil {
|
||||
entry = &beaconBlockHeaderEntry{
|
||||
present: false,
|
||||
}
|
||||
} else {
|
||||
entry = &beaconBlockHeaderEntry{
|
||||
present: true,
|
||||
value: header,
|
||||
}
|
||||
}
|
||||
b.entries[slot] = entry
|
||||
}
|
||||
return entry.value, nil
|
||||
}
|
||||
11
util/misc.go
11
util/misc.go
@@ -46,14 +46,19 @@ func SetupStore() error {
|
||||
if GetBaseDir() != "" {
|
||||
return errors.New("basedir does not apply to the s3 store")
|
||||
}
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(GetStorePassphrase())))
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(GetStorePassphrase("s3"))),
|
||||
s3.WithEndpoint(viper.GetString("stores.s3.endpoint")),
|
||||
s3.WithRegion(viper.GetString("stores.s3.region")),
|
||||
s3.WithBucket(viper.GetString("stores.s3.bucket")),
|
||||
s3.WithPath(viper.GetString("stores.s3.path")),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(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 GetStorePassphrase("filesystem") != "" {
|
||||
opts = append(opts, filesystem.WithPassphrase([]byte(GetStorePassphrase("filesystem"))))
|
||||
}
|
||||
if GetBaseDir() != "" {
|
||||
opts = append(opts, filesystem.WithLocation(GetBaseDir()))
|
||||
|
||||
47
util/mnemonic.go
Normal file
47
util/mnemonic.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © 2020, 2022 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 (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// hdPathRegex is the regular expression that matches an HD path.
|
||||
var hdPathRegex = regexp.MustCompile("^m/[0-9]+/[0-9]+(/[0-9+])+")
|
||||
|
||||
// SeedFromMnemonic creates a seed from a mnemonic.
|
||||
func SeedFromMnemonic(mnemonic string) ([]byte, error) {
|
||||
// If there are more than 24 words we treat the additional characters as the passphrase.
|
||||
mnemonicParts := strings.Split(mnemonic, " ")
|
||||
mnemonicPassphrase := ""
|
||||
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)))
|
||||
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return nil, errors.New("mnemonic is invalid")
|
||||
}
|
||||
|
||||
// Create seed from mnemonic and passphrase.
|
||||
return bip39.NewSeed(mnemonic, mnemonicPassphrase), nil
|
||||
}
|
||||
@@ -14,13 +14,20 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// GetStorePassphrase fetches the store passphrase supplied by the user.
|
||||
func GetStorePassphrase() string {
|
||||
storePassphrase := viper.GetString("store-passphrase")
|
||||
func GetStorePassphrase(store string) string {
|
||||
// Try store-specific passphrase.
|
||||
storePassphrase := viper.GetString(fmt.Sprintf("stores.%s.passphrase", store))
|
||||
if storePassphrase == "" {
|
||||
// Try generic passphrase.
|
||||
storePassphrase = viper.GetString("store-passphrase")
|
||||
}
|
||||
if storePassphrase == "" {
|
||||
// Try deprecated name.
|
||||
storePassphrase = viper.GetString("storepassphrase")
|
||||
|
||||
@@ -24,10 +24,16 @@ import (
|
||||
func TestGetStorePassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
store string
|
||||
passphrase string
|
||||
}{
|
||||
{
|
||||
name: "Good",
|
||||
name: "Default",
|
||||
env: map[string]string{
|
||||
"store-passphrase": "pass",
|
||||
},
|
||||
store: "test",
|
||||
passphrase: "pass",
|
||||
},
|
||||
}
|
||||
@@ -35,8 +41,10 @@ func TestGetStorePassphrase(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
viper.Set("store-passphrase", test.passphrase)
|
||||
require.Equal(t, test.passphrase, util.GetStorePassphrase())
|
||||
for k, v := range test.env {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
require.Equal(t, test.passphrase, util.GetStorePassphrase(test.passphrase))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -165,6 +173,7 @@ func TestStorePassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputs map[string]interface{}
|
||||
store string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
@@ -192,6 +201,16 @@ func TestStorePassphrase(t *testing.T) {
|
||||
},
|
||||
expected: "secret2",
|
||||
},
|
||||
{
|
||||
name: "StoreSpecific",
|
||||
inputs: map[string]interface{}{
|
||||
"storepassphrase": "secret",
|
||||
"store-passphrase": "secret2",
|
||||
"stores.test.passphrase": "secret3",
|
||||
},
|
||||
store: "test",
|
||||
expected: "secret3",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -200,7 +219,7 @@ func TestStorePassphrase(t *testing.T) {
|
||||
for k, v := range test.inputs {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
res := util.GetStorePassphrase()
|
||||
res := util.GetStorePassphrase(test.store)
|
||||
require.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,6 +75,14 @@ func (a *ScratchAccount) PublicKey() e2types.PublicKey {
|
||||
return a.pubKey
|
||||
}
|
||||
|
||||
// PrivateKey returns the account private key.
|
||||
func (a *ScratchAccount) PrivateKey(ctx context.Context) (e2types.PrivateKey, error) {
|
||||
if a.privKey == nil {
|
||||
return nil, errors.New("no private key available")
|
||||
}
|
||||
return a.privKey, nil
|
||||
}
|
||||
|
||||
// Path returns the account path.
|
||||
func (a *ScratchAccount) Path() string {
|
||||
return ""
|
||||
|
||||
132
util/validators.go
Normal file
132
util/validators.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright © 2022 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 (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ParseValidators parses input to obtain the list of validators.
|
||||
func ParseValidators(ctx context.Context, validatorsProvider eth2client.ValidatorsProvider, validatorsStr []string, stateID string) ([]*apiv1.Validator, error) {
|
||||
validators := make([]*apiv1.Validator, 0, len(validatorsStr))
|
||||
for i := range validatorsStr {
|
||||
if strings.Contains(validatorsStr[i], "-") {
|
||||
// Range.
|
||||
bits := strings.Split(validatorsStr[i], "-")
|
||||
if len(bits) != 2 {
|
||||
return nil, fmt.Errorf("invalid range %s", validatorsStr[i])
|
||||
}
|
||||
low, err := strconv.ParseUint(bits[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid range start")
|
||||
}
|
||||
high, err := strconv.ParseUint(bits[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "invalid range end")
|
||||
}
|
||||
indices := make([]phase0.ValidatorIndex, 0)
|
||||
for index := low; index <= high; index++ {
|
||||
indices = append(indices, phase0.ValidatorIndex(index))
|
||||
}
|
||||
rangeValidators, err := validatorsProvider.Validators(ctx, stateID, indices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to obtain validators %s", validatorsStr[i]))
|
||||
}
|
||||
for _, validator := range rangeValidators {
|
||||
validators = append(validators, validator)
|
||||
}
|
||||
} else {
|
||||
validator, err := ParseValidator(ctx, validatorsProvider, validatorsStr[i], stateID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("unknown validator %s", validatorsStr[i]))
|
||||
}
|
||||
validators = append(validators, validator)
|
||||
}
|
||||
}
|
||||
return validators, nil
|
||||
}
|
||||
|
||||
// ParseValidator parses input to obtain the validator.
|
||||
func ParseValidator(ctx context.Context,
|
||||
validatorsProvider eth2client.ValidatorsProvider,
|
||||
validatorStr string,
|
||||
stateID string,
|
||||
) (
|
||||
*apiv1.Validator,
|
||||
error,
|
||||
) {
|
||||
var validators map[phase0.ValidatorIndex]*apiv1.Validator
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(validatorStr, "0x"):
|
||||
// A public key.
|
||||
data, err := hex.DecodeString(strings.TrimPrefix(validatorStr, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse validator public key")
|
||||
}
|
||||
pubKey := phase0.BLSPubKey{}
|
||||
copy(pubKey[:], data)
|
||||
validators, err = validatorsProvider.ValidatorsByPubKey(ctx,
|
||||
stateID,
|
||||
[]phase0.BLSPubKey{pubKey},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
case strings.Contains(validatorStr, "/"):
|
||||
// An account.
|
||||
_, account, err := WalletAndAccountFromPath(ctx, validatorStr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to obtain account")
|
||||
}
|
||||
accPubKey, err := BestPublicKey(account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to obtain public key for account")
|
||||
}
|
||||
pubKey := phase0.BLSPubKey{}
|
||||
copy(pubKey[:], accPubKey.Marshal())
|
||||
validators, err = validatorsProvider.ValidatorsByPubKey(ctx,
|
||||
stateID,
|
||||
[]phase0.BLSPubKey{pubKey},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
default:
|
||||
// An index.
|
||||
index, err := strconv.ParseUint(validatorStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse validator index")
|
||||
}
|
||||
validators, err = validatorsProvider.Validators(ctx, stateID, []phase0.ValidatorIndex{phase0.ValidatorIndex(index)})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
}
|
||||
// Validator is first entry in the map.
|
||||
for _, validator := range validators {
|
||||
return validator, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown validator")
|
||||
}
|
||||
Reference in New Issue
Block a user