mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
Delete staking miner (#1480)
* Delete staking miner New repo should be used instead https://github.com/paritytech/staking-miner-v2 Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove staking-miner CI jobs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This commit is contained in:
committed by
GitHub
parent
6df8909d94
commit
4b8bd9060e
@@ -47,7 +47,6 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- polkadot
|
||||
- staking-miner
|
||||
- polkadot-parachain
|
||||
|
||||
permissions:
|
||||
@@ -158,18 +157,6 @@ jobs:
|
||||
echo "tag=latest" >> $GITHUB_OUTPUT
|
||||
echo "release=${release}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Injected Container image for polkadot/staking-miner
|
||||
if: ${{ env.BINARY == 'polkadot' || env.BINARY == 'staking-miner' }}
|
||||
env:
|
||||
ARTIFACTS_FOLDER: ./release-artifacts
|
||||
IMAGE_NAME: ${{ env.BINARY }}
|
||||
OWNER: ${{ env.DOCKER_OWNER }}
|
||||
TAGS: ${{ join(steps.fetch_rc_refs.outputs.*, ',') || join(steps.fetch_release_refs.outputs.*, ',') }}
|
||||
run: |
|
||||
ls -al
|
||||
echo "Building container for $BINARY"
|
||||
./docker/scripts/build-injected.sh
|
||||
|
||||
- name: Build Injected Container image for polkadot-parachain
|
||||
if: ${{ env.BINARY == 'polkadot-parachain' }}
|
||||
env:
|
||||
|
||||
@@ -77,26 +77,6 @@ build-malus:
|
||||
- echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))"
|
||||
- cp -r ./docker/* ./artifacts
|
||||
|
||||
build-staking-miner:
|
||||
stage: build
|
||||
extends:
|
||||
- .docker-env
|
||||
- .common-refs
|
||||
# - .collect-artifacts
|
||||
# DAG
|
||||
needs:
|
||||
- job: build-malus
|
||||
artifacts: false
|
||||
script:
|
||||
- time cargo build -q --locked --release --package staging-staking-miner
|
||||
# # pack artifacts
|
||||
# - mkdir -p ./artifacts
|
||||
# - mv ./target/release/staking-miner ./artifacts/.
|
||||
# - echo -n "${CI_COMMIT_REF_NAME}" > ./artifacts/VERSION
|
||||
# - echo -n "${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}" > ./artifacts/EXTRATAG
|
||||
# - echo "staking-miner = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))"
|
||||
# - cp -r ./scripts/* ./artifacts
|
||||
|
||||
build-rustdoc:
|
||||
stage: build
|
||||
extends:
|
||||
@@ -358,7 +338,7 @@ build-subkey-linux:
|
||||
extends: .build-subkey
|
||||
# DAG
|
||||
needs:
|
||||
- job: build-staking-miner
|
||||
- job: build-malus
|
||||
artifacts: false
|
||||
# tbd
|
||||
# build-subkey-macos:
|
||||
|
||||
@@ -328,24 +328,6 @@ build-push-image-substrate-pr:
|
||||
# # this artifact is used in zombienet-tests job
|
||||
# dotenv: ./artifacts/malus.env
|
||||
|
||||
# publish-staking-miner-image:
|
||||
# stage: publish
|
||||
# extends:
|
||||
# - .kubernetes-env
|
||||
# - .build-push-image
|
||||
# - .publish-refs
|
||||
# variables:
|
||||
# CI_IMAGE: ${BUILDAH_IMAGE}
|
||||
# # scripts/ci/dockerfiles/staking-miner/staking-miner_injected.Dockerfile
|
||||
# DOCKERFILE: ci/dockerfiles/staking-miner/staking-miner_injected.Dockerfile
|
||||
# IMAGE_NAME: docker.io/paritytech/staking-miner
|
||||
# GIT_STRATEGY: none
|
||||
# DOCKER_USER: ${Docker_Hub_User_Parity}
|
||||
# DOCKER_PASS: ${Docker_Hub_Pass_Parity}
|
||||
# needs:
|
||||
# - job: build-staking-miner
|
||||
# artifacts: true
|
||||
|
||||
# substrate
|
||||
|
||||
# publish-substrate-image-pr:
|
||||
|
||||
Generated
+5
-115
@@ -494,7 +494,7 @@ dependencies = [
|
||||
"ark-ff",
|
||||
"ark-std",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4838,12 +4838,6 @@ dependencies = [
|
||||
"futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exitcode"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
|
||||
|
||||
[[package]]
|
||||
name = "expander"
|
||||
version = "0.0.4"
|
||||
@@ -7690,15 +7684,6 @@ dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
@@ -8542,16 +8527,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.1"
|
||||
@@ -8771,12 +8746,6 @@ version = "6.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
@@ -15117,7 +15086,7 @@ dependencies = [
|
||||
"substrate-test-runtime",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"tracing-subscriber",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -15815,7 +15784,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16394,18 +16363,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-tokio"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"libc",
|
||||
"signal-hook",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.6.4"
|
||||
@@ -17525,7 +17482,7 @@ dependencies = [
|
||||
"sp-std",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -17807,47 +17764,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "staging-staking-miner"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"clap 4.4.2",
|
||||
"exitcode",
|
||||
"frame-election-provider-support",
|
||||
"frame-remote-externalities",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"futures-util",
|
||||
"jsonrpsee",
|
||||
"log",
|
||||
"pallet-balances",
|
||||
"pallet-election-provider-multi-phase",
|
||||
"pallet-staking",
|
||||
"pallet-transaction-payment",
|
||||
"parity-scale-codec",
|
||||
"paste",
|
||||
"polkadot-core-primitives",
|
||||
"polkadot-runtime",
|
||||
"polkadot-runtime-common",
|
||||
"sc-transaction-pool-api",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
"sp-core",
|
||||
"sp-npos-elections",
|
||||
"sp-runtime",
|
||||
"sp-state-machine",
|
||||
"sp-version",
|
||||
"staging-kusama-runtime",
|
||||
"sub-tokens",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"westend-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "staging-xcm"
|
||||
version = "1.0.0"
|
||||
@@ -18022,14 +17938,6 @@ dependencies = [
|
||||
"webrtc-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sub-tokens"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/paritytech/substrate-debug-kit?branch=master#e12503ab781e913735dc389865a3b8b4a6c6399d"
|
||||
dependencies = [
|
||||
"separator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subkey"
|
||||
version = "3.0.0"
|
||||
@@ -19111,7 +19019,7 @@ dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"matchers 0.0.1",
|
||||
"matchers",
|
||||
"parking_lot 0.11.2",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -19125,24 +19033,6 @@ dependencies = [
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
dependencies = [
|
||||
"matchers 0.1.0",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trie-bench"
|
||||
version = "0.37.0"
|
||||
|
||||
@@ -172,7 +172,6 @@ members = [
|
||||
"polkadot/statement-table",
|
||||
"polkadot/utils/generate-bags",
|
||||
"polkadot/utils/remote-ext-tests/bags-list",
|
||||
"polkadot/utils/staking-miner",
|
||||
"polkadot/xcm",
|
||||
"polkadot/xcm/pallet-xcm",
|
||||
"polkadot/xcm/pallet-xcm-benchmarks",
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
FROM paritytech/ci-linux:production as builder
|
||||
|
||||
# metadata
|
||||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
ARG IMAGE_NAME="staking-miner"
|
||||
ARG PROFILE=release
|
||||
|
||||
LABEL description="This is the build stage. Here we create the binary."
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN cargo build --locked --$PROFILE --package staking-miner
|
||||
|
||||
# ===== SECOND STAGE ======
|
||||
|
||||
FROM docker.io/library/ubuntu:20.04
|
||||
LABEL description="This is the 2nd stage: a very small image where we copy the binary."
|
||||
LABEL io.parity.image.authors="devops-team@parity.io" \
|
||||
io.parity.image.vendor="Parity Technologies" \
|
||||
io.parity.image.title="${IMAGE_NAME}" \
|
||||
io.parity.image.description="${IMAGE_NAME} for substrate based chains" \
|
||||
io.parity.image.source="https://github.com/paritytech/polkadot/blob/${VCS_REF}/scripts/ci/dockerfiles/${IMAGE_NAME}/${IMAGE_NAME}_builder.Dockerfile" \
|
||||
io.parity.image.revision="${VCS_REF}" \
|
||||
io.parity.image.created="${BUILD_DATE}" \
|
||||
io.parity.image.documentation="https://github.com/paritytech/polkadot/"
|
||||
|
||||
ARG PROFILE=release
|
||||
COPY --from=builder /app/target/$PROFILE/staking-miner /usr/local/bin
|
||||
|
||||
RUN useradd -u 1000 -U -s /bin/sh miner && \
|
||||
rm -rf /usr/bin /usr/sbin
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
USER miner
|
||||
|
||||
ENV SEED=""
|
||||
ENV URI="wss://rpc.polkadot.io"
|
||||
ENV RUST_LOG="info"
|
||||
|
||||
# check if the binary works in this container
|
||||
RUN /usr/local/bin/staking-miner --version
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/staking-miner" ]
|
||||
@@ -1,43 +0,0 @@
|
||||
FROM docker.io/library/ubuntu:20.04
|
||||
|
||||
# metadata
|
||||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
ARG IMAGE_NAME="staking-miner"
|
||||
|
||||
LABEL io.parity.image.authors="devops-team@parity.io" \
|
||||
io.parity.image.vendor="Parity Technologies" \
|
||||
io.parity.image.title="${IMAGE_NAME}" \
|
||||
io.parity.image.description="${IMAGE_NAME} for substrate based chains" \
|
||||
io.parity.image.source="https://github.com/paritytech/polkadot/blob/${VCS_REF}/scripts/ci/dockerfiles/${IMAGE_NAME}/${IMAGE_NAME}_injected.Dockerfile" \
|
||||
io.parity.image.revision="${VCS_REF}" \
|
||||
io.parity.image.created="${BUILD_DATE}" \
|
||||
io.parity.image.documentation="https://github.com/paritytech/polkadot/"
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
libssl1.1 \
|
||||
ca-certificates && \
|
||||
# apt cleanup
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean && \
|
||||
find /var/lib/apt/lists/ -type f -not -name lock -delete; \
|
||||
useradd -u 1000 -U -s /bin/sh miner
|
||||
|
||||
# add binary to docker image
|
||||
COPY ./staking-miner /usr/local/bin
|
||||
|
||||
USER miner
|
||||
|
||||
ENV SEED=""
|
||||
ENV URI="wss://rpc.polkadot.io"
|
||||
ENV RUST_LOG="info"
|
||||
|
||||
# check if the binary works in this container
|
||||
RUN /usr/local/bin/staking-miner --version
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/staking-miner" ]
|
||||
@@ -1,37 +0,0 @@
|
||||
# staking-miner container image
|
||||
|
||||
## Build using the Builder
|
||||
|
||||
```
|
||||
./build.sh
|
||||
```
|
||||
|
||||
## Build the injected Image
|
||||
|
||||
You first need a valid Linux binary to inject. Let's assume this binary is located in `BIN_FOLDER`.
|
||||
|
||||
```
|
||||
./build-injected.sh "$BIN_FOLDER"
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
Here is how to test the image. We can generate a valid seed but the staking-miner will quickly notice that our
|
||||
account is not funded and "does not exist".
|
||||
|
||||
You may pass any ENV supported by the binary and must provide at least a few such as `SEED` and `URI`:
|
||||
```
|
||||
ENV SEED=""
|
||||
ENV URI="wss://rpc.polkadot.io:443"
|
||||
ENV RUST_LOG="info"
|
||||
```
|
||||
|
||||
```
|
||||
export SEED=$(subkey generate -n polkadot --output-type json | jq -r .secretSeed)
|
||||
podman run --rm -it \
|
||||
-e URI="wss://rpc.polkadot.io:443" \
|
||||
-e RUST_LOG="info" \
|
||||
-e SEED \
|
||||
localhost/parity/staking-miner \
|
||||
dry-run seq-phragmen
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Sample call:
|
||||
# $0 /path/to/folder_with_staking-miner_binary
|
||||
# This script replace the former dedicated staking-miner "injected" Dockerfile
|
||||
# and shows how to use the generic binary_injected.dockerfile
|
||||
|
||||
PROJECT_ROOT=`git rev-parse --show-toplevel`
|
||||
|
||||
export BINARY=staking-miner
|
||||
export ARTIFACTS_FOLDER=$1
|
||||
|
||||
$PROJECT_ROOT/docker/scripts/build-injected.sh
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Sample call:
|
||||
# $0 /path/to/folder_with_staking-miner_binary
|
||||
# This script replace the former dedicated staking-miner "injected" Dockerfile
|
||||
# and shows how to use the generic binary_injected.dockerfile
|
||||
|
||||
PROJECT_ROOT=`git rev-parse --show-toplevel`
|
||||
ENGINE=podman
|
||||
|
||||
echo "Building the staking-miner using the Builder image"
|
||||
echo "PROJECT_ROOT=$PROJECT_ROOT"
|
||||
$ENGINE build -t staking-miner -f "${PROJECT_ROOT}/docker/dockerfiles/staking-miner/staking-miner_builder.Dockerfile" "$PROJECT_ROOT"
|
||||
@@ -1,3 +0,0 @@
|
||||
# Staking-miner Docker image
|
||||
|
||||
## [GitHub](https://github.com/paritytech/polkadot/tree/master/utils/staking-miner)
|
||||
@@ -1,43 +0,0 @@
|
||||
FROM paritytech/ci-linux:production as builder
|
||||
|
||||
# metadata
|
||||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
ARG IMAGE_NAME="staking-miner"
|
||||
ARG PROFILE=production
|
||||
|
||||
LABEL description="This is the build stage. Here we create the binary."
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN cargo build --locked --profile $PROFILE --package staking-miner
|
||||
|
||||
# ===== SECOND STAGE ======
|
||||
|
||||
FROM docker.io/parity/base-bin:latest
|
||||
LABEL description="This is the 2nd stage: a very small image where we copy the binary."
|
||||
LABEL io.parity.image.authors="devops-team@parity.io" \
|
||||
io.parity.image.vendor="Parity Technologies" \
|
||||
io.parity.image.title="${IMAGE_NAME}" \
|
||||
io.parity.image.description="${IMAGE_NAME} for substrate based chains" \
|
||||
io.parity.image.source="https://github.com/paritytech/polkadot/blob/${VCS_REF}/scripts/ci/dockerfiles/${IMAGE_NAME}/${IMAGE_NAME}_builder.Dockerfile" \
|
||||
io.parity.image.revision="${VCS_REF}" \
|
||||
io.parity.image.created="${BUILD_DATE}" \
|
||||
io.parity.image.documentation="https://github.com/paritytech/polkadot/"
|
||||
|
||||
ARG PROFILE=release
|
||||
COPY --from=builder /app/target/$PROFILE/staking-miner /usr/local/bin
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
USER parity
|
||||
|
||||
ENV SEED=""
|
||||
ENV URI="wss://rpc.polkadot.io"
|
||||
ENV RUST_LOG="info"
|
||||
|
||||
# check if the binary works in this container
|
||||
RUN /usr/local/bin/staking-miner --version
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/staking-miner" ]
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
ENGINE=${ENGINE:-podman}
|
||||
|
||||
# You need to build an injected image first
|
||||
|
||||
# Fetch some binaries
|
||||
$ENGINE run --user root --rm -i \
|
||||
-v "$TMP:/export" \
|
||||
--entrypoint /bin/bash \
|
||||
parity/staking-miner -c \
|
||||
'cp "$(which staking-miner)" /export'
|
||||
|
||||
echo "Checking binaries we got:"
|
||||
tree $TMP
|
||||
|
||||
./build-injected.sh $TMP
|
||||
@@ -1,2 +0,0 @@
|
||||
*.key
|
||||
*.bin
|
||||
@@ -1,54 +0,0 @@
|
||||
[[bin]]
|
||||
name = "staging-staking-miner"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "staging-staking-miner"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
clap = { version = "4.4.2", features = ["derive", "env"] }
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
|
||||
jsonrpsee = { version = "0.16.2", features = ["ws-client", "macros"] }
|
||||
log = "0.4.17"
|
||||
paste = "1.0.7"
|
||||
serde = "1.0.188"
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0.48"
|
||||
tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread", "sync"] }
|
||||
remote-externalities = { package = "frame-remote-externalities" , path = "../../../substrate/utils/frame/remote-externalities" }
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
sp-core = { path = "../../../substrate/primitives/core" }
|
||||
sp-version = { path = "../../../substrate/primitives/version" }
|
||||
sp-state-machine = { path = "../../../substrate/primitives/state-machine" }
|
||||
sp-runtime = { path = "../../../substrate/primitives/runtime" }
|
||||
sp-npos-elections = { path = "../../../substrate/primitives/npos-elections" }
|
||||
sc-transaction-pool-api = { path = "../../../substrate/client/transaction-pool/api" }
|
||||
|
||||
frame-system = { path = "../../../substrate/frame/system" }
|
||||
frame-support = { path = "../../../substrate/frame/support" }
|
||||
frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support" }
|
||||
pallet-election-provider-multi-phase = { path = "../../../substrate/frame/election-provider-multi-phase" }
|
||||
pallet-staking = { path = "../../../substrate/frame/staking" }
|
||||
pallet-balances = { path = "../../../substrate/frame/balances" }
|
||||
pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment" }
|
||||
|
||||
core-primitives = { package = "polkadot-core-primitives", path = "../../core-primitives" }
|
||||
|
||||
runtime-common = { package = "polkadot-runtime-common", path = "../../runtime/common" }
|
||||
polkadot-runtime = { path = "../../runtime/polkadot" }
|
||||
kusama-runtime = { package = "staging-kusama-runtime", path = "../../runtime/kusama" }
|
||||
westend-runtime = { path = "../../runtime/westend" }
|
||||
exitcode = "1.1"
|
||||
|
||||
sub-tokens = { git = "https://github.com/paritytech/substrate-debug-kit", branch = "master" }
|
||||
signal-hook = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.4"
|
||||
@@ -1,81 +0,0 @@
|
||||
# Staking Miner
|
||||
|
||||
Substrate chains validators compute a basic solution for the NPoS election. The optimization of the solution is
|
||||
computing-intensive and can be delegated to the `staking-miner`. The `staking-miner` does not act as validator and
|
||||
focuses solely on the optimization of the solution.
|
||||
|
||||
The staking miner connects to a specified chain and keeps listening to new Signed phase of the
|
||||
[pallet-election-provider-multi-phase](https://crates.parity.io/pallet_election_provider_multi_phase/index.html) in
|
||||
order to submit solutions to the NPoS election. When the correct time comes, it computes its solution and submit it to
|
||||
the chain. The default miner algorithm is
|
||||
[sequential-phragmen](https://crates.parity.io/sp_npos_elections/phragmen/fn.seq_phragmen_core.html)] with a
|
||||
configurable number of balancing iterations that improve the score.
|
||||
|
||||
Running the staking-miner requires passing the seed of a funded account in order to pay the fees for the transactions
|
||||
that will be sent. The same account's balance is used to reserve deposits as well. The best solution in each round is
|
||||
rewarded. All correct solutions will get their bond back. Any invalid solution will lose their bond.
|
||||
|
||||
You can check the help with:
|
||||
```
|
||||
staking-miner --help
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
You can build from the root of the Polkadot repository using:
|
||||
```
|
||||
cargo build --profile production --locked --package staking-miner --bin staking-miner
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
There are 2 options to build a staking-miner Docker image:
|
||||
- injected binary: the binary is first built on a Linux host and then injected into a Docker base image. This method
|
||||
only works if you have a Linux host or access to a pre-built binary from a Linux host.
|
||||
- multi-stage: the binary is entirely built within the multi-stage Docker image. There is no requirement on the host in
|
||||
terms of OS and the host does not even need to have any Rust toolchain installed.
|
||||
|
||||
### Building the injected image
|
||||
|
||||
First build the binary as documented [above](#building). You may then inject the binary into a Docker base image:
|
||||
`parity/base-bin` (running the command from the root of the Polkadot repository):
|
||||
```
|
||||
TODO: UPDATE THAT
|
||||
docker build -t staking-miner -f scripts/ci/dockerfiles/staking-miner/staking-miner_injected.Dockerfile target/release
|
||||
```
|
||||
|
||||
### Building the multi-stage image
|
||||
|
||||
Unlike the injected image that requires a Linux pre-built binary, this option does not requires a Linux host, nor Rust
|
||||
to be installed. The trade-off however is that it takes a little longer to build and this option is less ideal for CI
|
||||
tasks. You may build the multi-stage image the root of the Polkadot repository with:
|
||||
```
|
||||
TODO: UPDATE THAT
|
||||
docker build -t staking-miner -f docker/dockerfiles/staking-miner/staking-miner_builder.Dockerfile .
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
A Docker container, especially one holding one of your `SEED` should be kept as secure as possible. While it won't
|
||||
prevent a malicious actor to read your `SEED` if they gain access to your container, it is nonetheless recommended
|
||||
running this container in `read-only` mode:
|
||||
|
||||
```
|
||||
# The following line starts with an extra space on purpose:
|
||||
SEED=0x1234...
|
||||
|
||||
docker run --rm -i \
|
||||
--name staking-miner \
|
||||
--read-only \
|
||||
-e RUST_LOG=info \
|
||||
-e SEED=$SEED \
|
||||
-e URI=wss://your-node:9944 \
|
||||
staking-miner dry-run
|
||||
```
|
||||
|
||||
### Test locally
|
||||
|
||||
Make sure you've built Polkadot, then:
|
||||
|
||||
1. `cargo run -p polkadot --features fast-runtime -- --chain polkadot-dev --tmp --alice -lruntime=debug`
|
||||
2. `cargo run -p staking-miner -- --uri ws://localhost:9944 monitor --seed-or-path //Alice phrag-mms`
|
||||
@@ -1,166 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The dry-run command.
|
||||
|
||||
use crate::{opts::DryRunConfig, prelude::*, rpc::*, signer::Signer, Error, SharedRpcClient};
|
||||
use codec::Encode;
|
||||
use frame_support::traits::Currency;
|
||||
use sp_core::Bytes;
|
||||
use sp_npos_elections::ElectionScore;
|
||||
|
||||
/// Forcefully create the snapshot. This can be used to compute the election at anytime.
|
||||
fn force_create_snapshot<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error<T>> {
|
||||
ext.execute_with(|| {
|
||||
if <EPM::Snapshot<T>>::exists() {
|
||||
log::info!(target: LOG_TARGET, "snapshot already exists.");
|
||||
Ok(())
|
||||
} else {
|
||||
log::info!(target: LOG_TARGET, "creating a fake snapshot now.");
|
||||
<EPM::Pallet<T>>::create_snapshot().map(|_| ()).map_err(Into::into)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper method to print the encoded size of the snapshot.
|
||||
async fn print_info<T: EPM::Config>(
|
||||
rpc: &SharedRpcClient,
|
||||
ext: &mut Ext,
|
||||
raw_solution: &EPM::RawSolution<EPM::SolutionOf<T::MinerConfig>>,
|
||||
extrinsic: &Bytes,
|
||||
) where
|
||||
<T as EPM::Config>::Currency: Currency<T::AccountId, Balance = Balance>,
|
||||
{
|
||||
ext.execute_with(|| {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Snapshot Metadata: {:?}",
|
||||
<EPM::Pallet<T>>::snapshot_metadata()
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Snapshot Encoded Length: {:?}",
|
||||
<EPM::Pallet<T>>::snapshot()
|
||||
.expect("snapshot must exist before calling `measure_snapshot_size`")
|
||||
.encode()
|
||||
.len()
|
||||
);
|
||||
|
||||
let snapshot_size =
|
||||
<EPM::Pallet<T>>::snapshot_metadata().expect("snapshot must exist by now; qed.");
|
||||
let deposit = EPM::Pallet::<T>::deposit_for(raw_solution, snapshot_size);
|
||||
|
||||
let score = {
|
||||
let ElectionScore { minimal_stake, sum_stake, sum_stake_squared } = raw_solution.score;
|
||||
[Token::from(minimal_stake), Token::from(sum_stake), Token::from(sum_stake_squared)]
|
||||
};
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"solution score {:?} / deposit {:?} / length {:?}",
|
||||
score,
|
||||
Token::from(deposit),
|
||||
raw_solution.encode().len(),
|
||||
);
|
||||
});
|
||||
|
||||
let info = rpc.payment_query_info(&extrinsic, None).await;
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"payment_queryInfo: (fee = {}) {:?}",
|
||||
info.as_ref()
|
||||
.map(|d| Token::from(d.partial_fee))
|
||||
.unwrap_or_else(|_| Token::from(0)),
|
||||
info,
|
||||
);
|
||||
}
|
||||
|
||||
/// Find the stake threshold in order to have at most `count` voters.
|
||||
#[allow(unused)]
|
||||
fn find_threshold<T: EPM::Config>(ext: &mut Ext, count: usize) {
|
||||
ext.execute_with(|| {
|
||||
let mut voters = <EPM::Pallet<T>>::snapshot()
|
||||
.expect("snapshot must exist before calling `measure_snapshot_size`")
|
||||
.voters;
|
||||
voters.sort_by_key(|(_voter, weight, _targets)| std::cmp::Reverse(*weight));
|
||||
match voters.get(count) {
|
||||
Some(threshold_voter) => println!("smallest allowed voter is {:?}", threshold_voter),
|
||||
None => {
|
||||
println!("requested truncation to {} voters but had only {}", count, voters.len());
|
||||
println!("smallest current voter: {:?}", voters.last());
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! dry_run_cmd_for { ($runtime:ident) => { paste::paste! {
|
||||
/// Execute the dry-run command.
|
||||
pub(crate) async fn [<dry_run_cmd_ $runtime>](
|
||||
rpc: SharedRpcClient,
|
||||
config: DryRunConfig,
|
||||
signer: Signer,
|
||||
) -> Result<(), Error<$crate::[<$runtime _runtime_exports>]::Runtime>> {
|
||||
use $crate::[<$runtime _runtime_exports>]::*;
|
||||
let pallets = if config.force_snapshot {
|
||||
vec!["Staking".to_string(), "BagsList".to_string()]
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let mut ext = crate::create_election_ext::<Runtime>(rpc.clone(), config.at, pallets).await?;
|
||||
if config.force_snapshot {
|
||||
force_create_snapshot::<Runtime>(&mut ext)?;
|
||||
};
|
||||
|
||||
log::debug!(target: LOG_TARGET, "solving with {:?}", config.solver);
|
||||
let raw_solution = crate::mine_with::<Runtime>(&config.solver, &mut ext, false)?;
|
||||
|
||||
let nonce = crate::get_account_info::<Runtime>(&rpc, &signer.account, config.at)
|
||||
.await?
|
||||
.map(|i| i.nonce)
|
||||
.expect("signer account is checked to exist upon startup; it can only die if it \
|
||||
transfers funds out of it, or get slashed. If it does not exist at this point, \
|
||||
it is likely due to a bug, or the signer got slashed. Terminating."
|
||||
);
|
||||
let tip = 0 as Balance;
|
||||
let era = sp_runtime::generic::Era::Immortal;
|
||||
let extrinsic = ext.execute_with(|| create_uxt(raw_solution.clone(), signer.clone(), nonce, tip, era));
|
||||
|
||||
let bytes = sp_core::Bytes(extrinsic.encode().to_vec());
|
||||
print_info::<Runtime>(&rpc, &mut ext, &raw_solution, &bytes).await;
|
||||
|
||||
let feasibility_result = ext.execute_with(|| {
|
||||
EPM::Pallet::<Runtime>::feasibility_check(raw_solution.clone(), EPM::ElectionCompute::Signed)
|
||||
});
|
||||
log::info!(target: LOG_TARGET, "feasibility result is {:?}", feasibility_result.map(|_| ()));
|
||||
|
||||
let dispatch_result = ext.execute_with(|| {
|
||||
// manually tweak the phase.
|
||||
EPM::CurrentPhase::<Runtime>::put(EPM::Phase::Signed);
|
||||
EPM::Pallet::<Runtime>::submit(frame_system::RawOrigin::Signed(signer.account).into(), Box::new(raw_solution))
|
||||
});
|
||||
log::info!(target: LOG_TARGET, "dispatch result is {:?}", dispatch_result);
|
||||
|
||||
let dry_run_fut = rpc.dry_run(&bytes, None);
|
||||
let outcome: sp_runtime::ApplyExtrinsicResult = await_request_and_decode(dry_run_fut).await.map_err::<Error<Runtime>, _>(Into::into)?;
|
||||
log::info!(target: LOG_TARGET, "dry-run outcome is {:?}", outcome);
|
||||
Ok(())
|
||||
}
|
||||
}}}
|
||||
|
||||
dry_run_cmd_for!(polkadot);
|
||||
dry_run_cmd_for!(kusama);
|
||||
dry_run_cmd_for!(westend);
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The emergency-solution command.
|
||||
|
||||
use crate::{prelude::*, EmergencySolutionConfig, Error, SharedRpcClient};
|
||||
use codec::Encode;
|
||||
use std::io::Write;
|
||||
|
||||
macro_rules! emergency_solution_cmd_for { ($runtime:ident) => { paste::paste! {
|
||||
/// Execute the emergency-solution command.
|
||||
pub(crate) async fn [<emergency_solution_cmd_ $runtime>](
|
||||
client: SharedRpcClient,
|
||||
config: EmergencySolutionConfig,
|
||||
) -> Result<(), Error<$crate::[<$runtime _runtime_exports>]::Runtime>> {
|
||||
use $crate::[<$runtime _runtime_exports>]::*;
|
||||
|
||||
let mut ext = crate::create_election_ext::<Runtime>(client, config.at, vec![]).await?;
|
||||
let raw_solution = crate::mine_with::<Runtime>(&config.solver, &mut ext, false)?;
|
||||
|
||||
ext.execute_with(|| {
|
||||
assert!(EPM::Pallet::<Runtime>::current_phase().is_emergency());
|
||||
|
||||
log::info!(target: LOG_TARGET, "mined solution with {:?}", &raw_solution.score);
|
||||
|
||||
let ready_solution = EPM::Pallet::<Runtime>::feasibility_check(raw_solution, EPM::ElectionCompute::Signed)?;
|
||||
let encoded_size = ready_solution.encoded_size();
|
||||
let score = ready_solution.score;
|
||||
let mut supports = ready_solution.supports.into_inner();
|
||||
// maybe truncate.
|
||||
if let Some(take) = config.take {
|
||||
log::info!(target: LOG_TARGET, "truncating {} winners to {}", supports.len(), take);
|
||||
supports.sort_unstable_by_key(|(_, s)| s.total);
|
||||
supports.truncate(take);
|
||||
}
|
||||
|
||||
// write to file and stdout.
|
||||
let encoded_support = supports.encode();
|
||||
let mut supports_file = std::fs::File::create("solution.supports.bin")?;
|
||||
supports_file.write_all(&encoded_support)?;
|
||||
|
||||
log::info!(target: LOG_TARGET, "ReadySolution: size {:?} / score = {:?}", encoded_size, score);
|
||||
log::trace!(target: LOG_TARGET, "Supports: {}", sp_core::hexdisplay::HexDisplay::from(&encoded_support));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}}}
|
||||
|
||||
emergency_solution_cmd_for!(polkadot);
|
||||
emergency_solution_cmd_for!(kusama);
|
||||
emergency_solution_cmd_for!(westend);
|
||||
@@ -1,665 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # Polkadot Staking Miner.
|
||||
//!
|
||||
//! Simple bot capable of monitoring a polkadot (and cousins) chain and submitting solutions to the
|
||||
//! `pallet-election-provider-multi-phase`. See `--help` for more details.
|
||||
//!
|
||||
//! # Implementation Notes:
|
||||
//!
|
||||
//! - First draft: Be aware that this is the first draft and there might be bugs, or undefined
|
||||
//! behaviors. Don't attach this bot to an account with lots of funds.
|
||||
//! - Quick to crash: The bot is written so that it only continues to work if everything goes well.
|
||||
//! In case of any failure (RPC, logic, IO), it will crash. This was a decision to simplify the
|
||||
//! development. It is intended to run this bot with a `restart = true` way, so that it reports it
|
||||
//! crash, but resumes work thereafter.
|
||||
|
||||
// Silence erroneous warning about unsafe not being required whereas it is
|
||||
// see https://github.com/rust-lang/rust/issues/49112
|
||||
#![allow(unused_unsafe)]
|
||||
|
||||
mod dry_run;
|
||||
mod emergency_solution;
|
||||
mod monitor;
|
||||
mod opts;
|
||||
mod prelude;
|
||||
mod rpc;
|
||||
mod runtime_versions;
|
||||
mod signer;
|
||||
|
||||
pub(crate) use prelude::*;
|
||||
pub(crate) use signer::get_account_info;
|
||||
|
||||
use crate::opts::*;
|
||||
use clap::Parser;
|
||||
use frame_election_provider_support::NposSolver;
|
||||
use frame_support::traits::Get;
|
||||
use futures_util::StreamExt;
|
||||
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
|
||||
use remote_externalities::{Builder, Mode, OnlineConfig, Transport};
|
||||
use rpc::{RpcApiClient, SharedRpcClient};
|
||||
use runtime_versions::RuntimeVersions;
|
||||
use signal_hook::consts::signal::*;
|
||||
use signal_hook_tokio::Signals;
|
||||
use sp_npos_elections::BalancingConfig;
|
||||
use std::{ops::Deref, sync::Arc, time::Duration};
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
pub(crate) enum AnyRuntime {
|
||||
Polkadot,
|
||||
Kusama,
|
||||
Westend,
|
||||
}
|
||||
|
||||
pub(crate) static mut RUNTIME: AnyRuntime = AnyRuntime::Polkadot;
|
||||
|
||||
macro_rules! construct_runtime_prelude {
|
||||
($runtime:ident) => { paste::paste! {
|
||||
pub(crate) mod [<$runtime _runtime_exports>] {
|
||||
pub(crate) use crate::prelude::EPM;
|
||||
pub(crate) use [<$runtime _runtime>]::*;
|
||||
pub(crate) use crate::monitor::[<monitor_cmd_ $runtime>] as monitor_cmd;
|
||||
pub(crate) use crate::dry_run::[<dry_run_cmd_ $runtime>] as dry_run_cmd;
|
||||
pub(crate) use crate::emergency_solution::[<emergency_solution_cmd_ $runtime>] as emergency_solution_cmd;
|
||||
pub(crate) use private::{[<create_uxt_ $runtime>] as create_uxt};
|
||||
|
||||
mod private {
|
||||
use super::*;
|
||||
pub(crate) fn [<create_uxt_ $runtime>](
|
||||
raw_solution: EPM::RawSolution<EPM::SolutionOf<Runtime>>,
|
||||
signer: crate::signer::Signer,
|
||||
nonce: crate::prelude::Nonce,
|
||||
tip: crate::prelude::Balance,
|
||||
era: sp_runtime::generic::Era,
|
||||
) -> UncheckedExtrinsic {
|
||||
use codec::Encode as _;
|
||||
use sp_core::Pair as _;
|
||||
use sp_runtime::traits::StaticLookup as _;
|
||||
|
||||
let crate::signer::Signer { account, pair, .. } = signer;
|
||||
|
||||
let local_call = EPMCall::<Runtime>::submit { raw_solution: Box::new(raw_solution) };
|
||||
let call: RuntimeCall = <EPMCall<Runtime> as std::convert::TryInto<RuntimeCall>>::try_into(local_call)
|
||||
.expect("election provider pallet must exist in the runtime, thus \
|
||||
inner call can be converted, qed."
|
||||
);
|
||||
|
||||
let extra: SignedExtra = crate::[<signed_ext_builder_ $runtime>](nonce, tip, era);
|
||||
let raw_payload = SignedPayload::new(call, extra).expect("creating signed payload infallible; qed.");
|
||||
let signature = raw_payload.using_encoded(|payload| {
|
||||
pair.sign(payload)
|
||||
});
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
let address = <Runtime as frame_system::Config>::Lookup::unlookup(account);
|
||||
let extrinsic = UncheckedExtrinsic::new_signed(call, address, signature.into(), extra);
|
||||
log::debug!(
|
||||
target: crate::LOG_TARGET, "constructed extrinsic {} with length {}",
|
||||
sp_core::hexdisplay::HexDisplay::from(&extrinsic.encode()),
|
||||
extrinsic.encode().len(),
|
||||
);
|
||||
extrinsic
|
||||
}
|
||||
}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: we might be able to use some code from the bridges repo here.
|
||||
fn signed_ext_builder_polkadot(
|
||||
nonce: Nonce,
|
||||
tip: Balance,
|
||||
era: sp_runtime::generic::Era,
|
||||
) -> polkadot_runtime_exports::SignedExtra {
|
||||
use polkadot_runtime_exports::Runtime;
|
||||
(
|
||||
frame_system::CheckNonZeroSender::<Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckMortality::<Runtime>::from(era),
|
||||
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
runtime_common::claims::PrevalidateAttests::<Runtime>::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn signed_ext_builder_kusama(
|
||||
nonce: Nonce,
|
||||
tip: Balance,
|
||||
era: sp_runtime::generic::Era,
|
||||
) -> kusama_runtime_exports::SignedExtra {
|
||||
use kusama_runtime_exports::Runtime;
|
||||
(
|
||||
frame_system::CheckNonZeroSender::<Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckMortality::<Runtime>::from(era),
|
||||
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
)
|
||||
}
|
||||
|
||||
fn signed_ext_builder_westend(
|
||||
nonce: Nonce,
|
||||
tip: Balance,
|
||||
era: sp_runtime::generic::Era,
|
||||
) -> westend_runtime_exports::SignedExtra {
|
||||
use westend_runtime_exports::Runtime;
|
||||
(
|
||||
frame_system::CheckNonZeroSender::<Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckMortality::<Runtime>::from(era),
|
||||
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
)
|
||||
}
|
||||
|
||||
construct_runtime_prelude!(polkadot);
|
||||
construct_runtime_prelude!(kusama);
|
||||
construct_runtime_prelude!(westend);
|
||||
|
||||
// NOTE: this is no longer used extensively, most of the per-runtime stuff us delegated to
|
||||
// `construct_runtime_prelude` and macro's the import directly from it. A part of the code is also
|
||||
// still generic over `T`. My hope is to still make everything generic over a `Runtime`, but sadly
|
||||
// that is not currently possible as each runtime has its unique `Call`, and all Calls are not
|
||||
// sharing any generic trait. In other words, to create the `UncheckedExtrinsic` of each chain, you
|
||||
// need the concrete `Call` of that chain as well.
|
||||
#[macro_export]
|
||||
macro_rules! any_runtime {
|
||||
($($code:tt)*) => {
|
||||
unsafe {
|
||||
match $crate::RUNTIME {
|
||||
$crate::AnyRuntime::Polkadot => {
|
||||
#[allow(unused)]
|
||||
use $crate::polkadot_runtime_exports::*;
|
||||
$($code)*
|
||||
},
|
||||
$crate::AnyRuntime::Kusama => {
|
||||
#[allow(unused)]
|
||||
use $crate::kusama_runtime_exports::*;
|
||||
$($code)*
|
||||
},
|
||||
$crate::AnyRuntime::Westend => {
|
||||
#[allow(unused)]
|
||||
use $crate::westend_runtime_exports::*;
|
||||
$($code)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`any_runtime`], but instead of returning a `Result`, this simply returns `()`. Useful
|
||||
/// for situations where the result is not useful and un-ergonomic to handle.
|
||||
#[macro_export]
|
||||
macro_rules! any_runtime_unit {
|
||||
($($code:tt)*) => {
|
||||
unsafe {
|
||||
match $crate::RUNTIME {
|
||||
$crate::AnyRuntime::Polkadot => {
|
||||
#[allow(unused)]
|
||||
use $crate::polkadot_runtime_exports::*;
|
||||
let _ = $($code)*;
|
||||
},
|
||||
$crate::AnyRuntime::Kusama => {
|
||||
#[allow(unused)]
|
||||
use $crate::kusama_runtime_exports::*;
|
||||
let _ = $($code)*;
|
||||
},
|
||||
$crate::AnyRuntime::Westend => {
|
||||
#[allow(unused)]
|
||||
use $crate::westend_runtime_exports::*;
|
||||
let _ = $($code)*;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(frame_support::DebugNoBound, thiserror::Error)]
|
||||
enum Error<T: EPM::Config> {
|
||||
Io(#[from] std::io::Error),
|
||||
JsonRpsee(#[from] jsonrpsee::core::Error),
|
||||
RpcHelperError(#[from] rpc::RpcHelperError),
|
||||
Codec(#[from] codec::Error),
|
||||
Crypto(sp_core::crypto::SecretStringError),
|
||||
RemoteExternalities(&'static str),
|
||||
PalletMiner(EPM::unsigned::MinerError),
|
||||
PalletElection(EPM::ElectionError<T>),
|
||||
PalletFeasibility(EPM::FeasibilityError),
|
||||
AccountDoesNotExists,
|
||||
IncorrectPhase,
|
||||
AlreadySubmitted,
|
||||
VersionMismatch,
|
||||
StrategyNotSatisfied,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl<T: EPM::Config> From<sp_core::crypto::SecretStringError> for Error<T> {
|
||||
fn from(e: sp_core::crypto::SecretStringError) -> Error<T> {
|
||||
Error::Crypto(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EPM::Config> From<EPM::unsigned::MinerError> for Error<T> {
|
||||
fn from(e: EPM::unsigned::MinerError) -> Error<T> {
|
||||
Error::PalletMiner(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EPM::Config> From<EPM::ElectionError<T>> for Error<T> {
|
||||
fn from(e: EPM::ElectionError<T>) -> Error<T> {
|
||||
Error::PalletElection(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EPM::Config> From<EPM::FeasibilityError> for Error<T> {
|
||||
fn from(e: EPM::FeasibilityError) -> Error<T> {
|
||||
Error::PalletFeasibility(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EPM::Config> std::fmt::Display for Error<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<Error<T> as std::fmt::Debug>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
frame_support::parameter_types! {
|
||||
/// Number of balancing iterations for a solution algorithm. Set based on the [`Solvers`] CLI
|
||||
/// config.
|
||||
pub static BalanceIterations: usize = 10;
|
||||
pub static Balancing: Option<BalancingConfig> = Some( BalancingConfig { iterations: BalanceIterations::get(), tolerance: 0 } );
|
||||
}
|
||||
|
||||
/// Build the Ext at hash with all the data of `ElectionProviderMultiPhase` and any additional
|
||||
/// pallets.
|
||||
async fn create_election_ext<T>(
|
||||
client: SharedRpcClient,
|
||||
at: Option<Hash>,
|
||||
additional: Vec<String>,
|
||||
) -> Result<Ext, Error<T>>
|
||||
where
|
||||
T: EPM::Config,
|
||||
{
|
||||
use frame_support::{storage::generator::StorageMap, traits::PalletInfo};
|
||||
use sp_core::hashing::twox_128;
|
||||
|
||||
let mut pallets = vec![<T as frame_system::Config>::PalletInfo::name::<EPM::Pallet<T>>()
|
||||
.expect("Pallet always has name; qed.")
|
||||
.to_string()];
|
||||
pallets.extend(additional);
|
||||
Builder::<Block>::new()
|
||||
.mode(Mode::Online(OnlineConfig {
|
||||
transport: Transport::Uri(client.uri().to_owned()),
|
||||
at,
|
||||
pallets,
|
||||
hashed_prefixes: vec![<frame_system::BlockHash<T>>::prefix_hash()],
|
||||
hashed_keys: vec![[twox_128(b"System"), twox_128(b"Number")].concat()],
|
||||
..Default::default()
|
||||
}))
|
||||
.build()
|
||||
.await
|
||||
.map_err(|why| Error::<T>::RemoteExternalities(why))
|
||||
.map(|rx| rx.inner_ext)
|
||||
}
|
||||
|
||||
/// Compute the election. It expects to NOT be `Phase::Off`. In other words, the snapshot must
|
||||
/// exists on the given externalities.
|
||||
fn mine_solution<T, S>(
|
||||
ext: &mut Ext,
|
||||
do_feasibility: bool,
|
||||
) -> Result<EPM::RawSolution<EPM::SolutionOf<T::MinerConfig>>, Error<T>>
|
||||
where
|
||||
T: EPM::Config,
|
||||
S: NposSolver<
|
||||
Error = <<T as EPM::Config>::Solver as NposSolver>::Error,
|
||||
AccountId = <<T as EPM::Config>::Solver as NposSolver>::AccountId,
|
||||
>,
|
||||
{
|
||||
ext.execute_with(|| {
|
||||
let (solution, _) = <EPM::Pallet<T>>::mine_solution().map_err::<Error<T>, _>(Into::into)?;
|
||||
if do_feasibility {
|
||||
let _ = <EPM::Pallet<T>>::feasibility_check(
|
||||
solution.clone(),
|
||||
EPM::ElectionCompute::Signed,
|
||||
)?;
|
||||
}
|
||||
Ok(solution)
|
||||
})
|
||||
}
|
||||
|
||||
/// Mine a solution with the given `solver`.
|
||||
fn mine_with<T>(
|
||||
solver: &Solver,
|
||||
ext: &mut Ext,
|
||||
do_feasibility: bool,
|
||||
) -> Result<EPM::RawSolution<EPM::SolutionOf<T::MinerConfig>>, Error<T>>
|
||||
where
|
||||
T: EPM::Config,
|
||||
T::Solver: NposSolver<Error = sp_npos_elections::Error>,
|
||||
{
|
||||
use frame_election_provider_support::{PhragMMS, SequentialPhragmen};
|
||||
|
||||
match solver {
|
||||
Solver::SeqPhragmen { iterations } => {
|
||||
BalanceIterations::set(*iterations);
|
||||
mine_solution::<
|
||||
T,
|
||||
SequentialPhragmen<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
sp_runtime::Perbill,
|
||||
Balancing,
|
||||
>,
|
||||
>(ext, do_feasibility)
|
||||
},
|
||||
Solver::PhragMMS { iterations } => {
|
||||
BalanceIterations::set(*iterations);
|
||||
mine_solution::<
|
||||
T,
|
||||
PhragMMS<<T as frame_system::Config>::AccountId, sp_runtime::Perbill, Balancing>,
|
||||
>(ext, do_feasibility)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn mine_dpos<T: EPM::Config>(ext: &mut Ext) -> Result<(), Error<T>> {
|
||||
ext.execute_with(|| {
|
||||
use std::collections::BTreeMap;
|
||||
use EPM::RoundSnapshot;
|
||||
let RoundSnapshot { voters, .. } = EPM::Snapshot::<T>::get().unwrap();
|
||||
let desired_targets = EPM::DesiredTargets::<T>::get().unwrap();
|
||||
let mut candidates_and_backing = BTreeMap::<T::AccountId, u128>::new();
|
||||
voters.into_iter().for_each(|(who, stake, targets)| {
|
||||
if targets.is_empty() {
|
||||
println!("target = {:?}", (who, stake, targets));
|
||||
return
|
||||
}
|
||||
let share: u128 = (stake as u128) / (targets.len() as u128);
|
||||
for target in targets {
|
||||
*candidates_and_backing.entry(target.clone()).or_default() += share
|
||||
}
|
||||
});
|
||||
|
||||
let mut candidates_and_backing =
|
||||
candidates_and_backing.into_iter().collect::<Vec<(_, _)>>();
|
||||
candidates_and_backing.sort_by_key(|(_, total_stake)| *total_stake);
|
||||
let winners = candidates_and_backing
|
||||
.into_iter()
|
||||
.rev()
|
||||
.take(desired_targets as usize)
|
||||
.collect::<Vec<_>>();
|
||||
let score = {
|
||||
let min_staker = *winners.last().map(|(_, stake)| stake).unwrap();
|
||||
let sum_stake = winners.iter().fold(0u128, |acc, (_, stake)| acc + stake);
|
||||
let sum_squared = winners.iter().fold(0u128, |acc, (_, stake)| acc + stake);
|
||||
[min_staker, sum_stake, sum_squared]
|
||||
};
|
||||
println!("mined a dpos-like solution with score = {:?}", score);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn check_versions<T: frame_system::Config + EPM::Config>(
|
||||
rpc: &SharedRpcClient,
|
||||
print: bool,
|
||||
) -> Result<(), Error<T>> {
|
||||
let linked_version = T::Version::get();
|
||||
let on_chain_version = rpc
|
||||
.runtime_version(None)
|
||||
.await
|
||||
.expect("runtime version RPC should always work; qed");
|
||||
|
||||
let do_print = || {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"linked version {:?}",
|
||||
(&linked_version.spec_name, &linked_version.spec_version)
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"on-chain version {:?}",
|
||||
(&on_chain_version.spec_name, &on_chain_version.spec_version)
|
||||
);
|
||||
};
|
||||
|
||||
if print {
|
||||
do_print();
|
||||
}
|
||||
|
||||
// we relax the checking here a bit, which should not cause any issues in production (a chain
|
||||
// that messes up its spec name is highly unlikely), but it allows us to do easier testing.
|
||||
if linked_version.spec_name != on_chain_version.spec_name ||
|
||||
linked_version.spec_version != on_chain_version.spec_version
|
||||
{
|
||||
if !print {
|
||||
do_print();
|
||||
}
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"VERSION MISMATCH: any transaction will fail with bad-proof"
|
||||
);
|
||||
Err(Error::VersionMismatch)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Control how we exit the application
|
||||
fn controlled_exit(code: i32) {
|
||||
log::info!(target: LOG_TARGET, "Exiting application");
|
||||
std::process::exit(code);
|
||||
}
|
||||
|
||||
/// Handles the various signal and exit the application
|
||||
/// when appropriate.
|
||||
async fn handle_signals(mut signals: Signals) {
|
||||
let mut keyboard_sig_count: u8 = 0;
|
||||
while let Some(signal) = signals.next().await {
|
||||
match signal {
|
||||
// Interrupts come from the keyboard
|
||||
SIGQUIT | SIGINT => {
|
||||
if keyboard_sig_count >= 1 {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Received keyboard termination signal #{}/{}, quitting...",
|
||||
keyboard_sig_count + 1,
|
||||
2
|
||||
);
|
||||
controlled_exit(exitcode::OK);
|
||||
}
|
||||
keyboard_sig_count += 1;
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Received keyboard termination signal #{}, if you keep doing that I will really quit",
|
||||
keyboard_sig_count
|
||||
);
|
||||
},
|
||||
|
||||
SIGKILL | SIGTERM => {
|
||||
log::info!(target: LOG_TARGET, "Received SIGKILL | SIGTERM, quitting...");
|
||||
controlled_exit(exitcode::OK);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
||||
|
||||
let Opt { uri, command, connection_timeout, request_timeout } = Opt::parse();
|
||||
log::debug!(target: LOG_TARGET, "attempting to connect to {:?}", uri);
|
||||
|
||||
let signals = Signals::new(&[SIGTERM, SIGINT, SIGQUIT]).expect("Failed initializing Signals");
|
||||
let handle = signals.handle();
|
||||
let signals_task = tokio::spawn(handle_signals(signals));
|
||||
|
||||
let rpc = loop {
|
||||
match SharedRpcClient::new(
|
||||
&uri,
|
||||
Duration::from_secs(connection_timeout as u64),
|
||||
Duration::from_secs(request_timeout as u64),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(client) => break client,
|
||||
Err(why) => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failed to connect to client due to {:?}, retrying soon..",
|
||||
why
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(2500)).await;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let chain: String = rpc.system_chain().await.expect("system_chain infallible; qed.");
|
||||
match chain.to_lowercase().as_str() {
|
||||
"polkadot" | "development" => {
|
||||
sp_core::crypto::set_default_ss58_version(
|
||||
sp_core::crypto::Ss58AddressFormatRegistry::PolkadotAccount.into(),
|
||||
);
|
||||
sub_tokens::dynamic::set_name("DOT");
|
||||
sub_tokens::dynamic::set_decimal_points(10_000_000_000);
|
||||
// safety: this program will always be single threaded, thus accessing global static is
|
||||
// safe.
|
||||
unsafe {
|
||||
RUNTIME = AnyRuntime::Polkadot;
|
||||
}
|
||||
},
|
||||
"kusama" | "kusama-dev" => {
|
||||
sp_core::crypto::set_default_ss58_version(
|
||||
sp_core::crypto::Ss58AddressFormatRegistry::KusamaAccount.into(),
|
||||
);
|
||||
sub_tokens::dynamic::set_name("KSM");
|
||||
sub_tokens::dynamic::set_decimal_points(1_000_000_000_000);
|
||||
// safety: this program will always be single threaded, thus accessing global static is
|
||||
// safe.
|
||||
unsafe {
|
||||
RUNTIME = AnyRuntime::Kusama;
|
||||
}
|
||||
},
|
||||
"westend" => {
|
||||
sp_core::crypto::set_default_ss58_version(
|
||||
sp_core::crypto::Ss58AddressFormatRegistry::PolkadotAccount.into(),
|
||||
);
|
||||
sub_tokens::dynamic::set_name("WND");
|
||||
sub_tokens::dynamic::set_decimal_points(1_000_000_000_000);
|
||||
// safety: this program will always be single threaded, thus accessing global static is
|
||||
// safe.
|
||||
unsafe {
|
||||
RUNTIME = AnyRuntime::Westend;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
eprintln!("unexpected chain: {:?}", chain);
|
||||
return
|
||||
},
|
||||
}
|
||||
log::info!(target: LOG_TARGET, "connected to chain {:?}", chain);
|
||||
|
||||
any_runtime_unit! {
|
||||
check_versions::<Runtime>(&rpc, true).await
|
||||
};
|
||||
|
||||
let outcome = any_runtime! {
|
||||
match command {
|
||||
Command::Monitor(monitor_config) =>
|
||||
{
|
||||
let signer_account = any_runtime! {
|
||||
signer::signer_uri_from_string::<Runtime>(&monitor_config.seed_or_path , &rpc)
|
||||
.await
|
||||
.expect("Provided account is invalid, terminating.")
|
||||
};
|
||||
monitor_cmd(rpc, monitor_config, signer_account).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "Monitor error: {:?}", e);
|
||||
})},
|
||||
Command::DryRun(dryrun_config) => {
|
||||
let signer_account = any_runtime! {
|
||||
signer::signer_uri_from_string::<Runtime>(&dryrun_config.seed_or_path , &rpc)
|
||||
.await
|
||||
.expect("Provided account is invalid, terminating.")
|
||||
};
|
||||
dry_run_cmd(rpc, dryrun_config, signer_account).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "DryRun error: {:?}", e);
|
||||
})},
|
||||
Command::EmergencySolution(emergency_solution_config) =>
|
||||
emergency_solution_cmd(rpc, emergency_solution_config).await
|
||||
.map_err(|e| {
|
||||
log::error!(target: LOG_TARGET, "EmergencySolution error: {:?}", e);
|
||||
}),
|
||||
Command::Info(info_opts) => {
|
||||
let remote_runtime_version = rpc.runtime_version(None).await.expect("runtime_version infallible; qed.");
|
||||
|
||||
let builtin_version = any_runtime! {
|
||||
Version::get()
|
||||
};
|
||||
|
||||
let versions = RuntimeVersions::new(&remote_runtime_version, &builtin_version);
|
||||
|
||||
if !info_opts.json {
|
||||
println!("{}", versions);
|
||||
} else {
|
||||
let versions = serde_json::to_string_pretty(&versions).expect("Failed serializing version info");
|
||||
println!("{}", versions);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
log::info!(target: LOG_TARGET, "round of execution finished. outcome = {:?}", outcome);
|
||||
|
||||
handle.close();
|
||||
let _ = signals_task.await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn get_version<T: frame_system::Config>() -> sp_version::RuntimeVersion {
|
||||
T::Version::get()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_runtime_works() {
|
||||
unsafe {
|
||||
RUNTIME = AnyRuntime::Polkadot;
|
||||
}
|
||||
let polkadot_version = any_runtime! { get_version::<Runtime>() };
|
||||
|
||||
unsafe {
|
||||
RUNTIME = AnyRuntime::Kusama;
|
||||
}
|
||||
let kusama_version = any_runtime! { get_version::<Runtime>() };
|
||||
|
||||
assert_eq!(polkadot_version.spec_name, "polkadot".into());
|
||||
assert_eq!(kusama_version.spec_name, "kusama".into());
|
||||
}
|
||||
}
|
||||
@@ -1,478 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The monitor command.
|
||||
|
||||
use crate::{
|
||||
prelude::*, rpc::*, signer::Signer, Error, MonitorConfig, SharedRpcClient, SubmissionStrategy,
|
||||
};
|
||||
use codec::Encode;
|
||||
use jsonrpsee::core::Error as RpcError;
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::Perbill;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use EPM::{signed::SubmissionIndicesOf, SignedSubmissionOf};
|
||||
|
||||
/// Ensure that now is the signed phase.
|
||||
async fn ensure_signed_phase<T: EPM::Config, B: BlockT<Hash = Hash>>(
|
||||
rpc: &SharedRpcClient,
|
||||
at: B::Hash,
|
||||
) -> Result<(), Error<T>> {
|
||||
let key = StorageKey(EPM::CurrentPhase::<T>::hashed_key().to_vec());
|
||||
let phase = rpc
|
||||
.get_storage_and_decode::<EPM::Phase<BlockNumber>>(&key, Some(at))
|
||||
.await
|
||||
.map_err::<Error<T>, _>(Into::into)?
|
||||
.unwrap_or_default();
|
||||
|
||||
if phase.is_signed() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::IncorrectPhase)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that our current `us` have not submitted anything previously.
|
||||
async fn ensure_no_previous_solution<T, B>(
|
||||
rpc: &SharedRpcClient,
|
||||
at: Hash,
|
||||
us: &AccountId,
|
||||
) -> Result<(), Error<T>>
|
||||
where
|
||||
T: EPM::Config + frame_system::Config<AccountId = AccountId, Hash = Hash>,
|
||||
B: BlockT,
|
||||
{
|
||||
let indices_key = StorageKey(EPM::SignedSubmissionIndices::<T>::hashed_key().to_vec());
|
||||
|
||||
let indices: SubmissionIndicesOf<T> = rpc
|
||||
.get_storage_and_decode(&indices_key, Some(at))
|
||||
.await
|
||||
.map_err::<Error<T>, _>(Into::into)?
|
||||
.unwrap_or_default();
|
||||
|
||||
for (_score, _bn, idx) in indices {
|
||||
let key = StorageKey(EPM::SignedSubmissionsMap::<T>::hashed_key_for(idx));
|
||||
|
||||
if let Some(submission) = rpc
|
||||
.get_storage_and_decode::<SignedSubmissionOf<T>>(&key, Some(at))
|
||||
.await
|
||||
.map_err::<Error<T>, _>(Into::into)?
|
||||
{
|
||||
if &submission.who == us {
|
||||
return Err(Error::AlreadySubmitted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `true` if `our_score` should pass the onchain `best_score` with the given strategy.
|
||||
pub(crate) fn score_passes_strategy(
|
||||
our_score: sp_npos_elections::ElectionScore,
|
||||
best_score: sp_npos_elections::ElectionScore,
|
||||
strategy: SubmissionStrategy,
|
||||
) -> bool {
|
||||
match strategy {
|
||||
SubmissionStrategy::Always => true,
|
||||
SubmissionStrategy::IfLeading =>
|
||||
our_score == best_score ||
|
||||
our_score.strict_threshold_better(best_score, Perbill::zero()),
|
||||
SubmissionStrategy::ClaimBetterThan(epsilon) =>
|
||||
our_score.strict_threshold_better(best_score, epsilon),
|
||||
SubmissionStrategy::ClaimNoWorseThan(epsilon) =>
|
||||
!best_score.strict_threshold_better(our_score, epsilon),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads all current solutions and checks the scores according to the `SubmissionStrategy`.
|
||||
async fn ensure_strategy_met<T: EPM::Config, B: BlockT>(
|
||||
rpc: &SharedRpcClient,
|
||||
at: Hash,
|
||||
score: sp_npos_elections::ElectionScore,
|
||||
strategy: SubmissionStrategy,
|
||||
max_submissions: u32,
|
||||
) -> Result<(), Error<T>> {
|
||||
// don't care about current scores.
|
||||
if matches!(strategy, SubmissionStrategy::Always) {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let indices_key = StorageKey(EPM::SignedSubmissionIndices::<T>::hashed_key().to_vec());
|
||||
|
||||
let indices: SubmissionIndicesOf<T> = rpc
|
||||
.get_storage_and_decode(&indices_key, Some(at))
|
||||
.await
|
||||
.map_err::<Error<T>, _>(Into::into)?
|
||||
.unwrap_or_default();
|
||||
|
||||
if indices.len() >= max_submissions as usize {
|
||||
log::debug!(target: LOG_TARGET, "The submissions queue is full");
|
||||
}
|
||||
|
||||
// default score is all zeros, any score is better than it.
|
||||
let best_score = indices.last().map(|(score, _, _)| *score).unwrap_or_default();
|
||||
log::debug!(target: LOG_TARGET, "best onchain score is {:?}", best_score);
|
||||
|
||||
if score_passes_strategy(score, best_score, strategy) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::StrategyNotSatisfied)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_latest_head<T: EPM::Config>(
|
||||
rpc: &SharedRpcClient,
|
||||
mode: &str,
|
||||
) -> Result<Hash, Error<T>> {
|
||||
if mode == "head" {
|
||||
match rpc.block_hash(None).await {
|
||||
Ok(Some(hash)) => Ok(hash),
|
||||
Ok(None) => Err(Error::Other("Best head not found".into())),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
} else {
|
||||
rpc.finalized_head().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! {
|
||||
|
||||
/// The monitor command.
|
||||
pub(crate) async fn [<monitor_cmd_ $runtime>](
|
||||
rpc: SharedRpcClient,
|
||||
config: MonitorConfig,
|
||||
signer: Signer,
|
||||
) -> Result<(), Error<$crate::[<$runtime _runtime_exports>]::Runtime>> {
|
||||
use $crate::[<$runtime _runtime_exports>]::*;
|
||||
type StakingMinerError = Error<$crate::[<$runtime _runtime_exports>]::Runtime>;
|
||||
|
||||
let heads_subscription = ||
|
||||
if config.listen == "head" {
|
||||
rpc.subscribe_new_heads()
|
||||
} else {
|
||||
rpc.subscribe_finalized_heads()
|
||||
};
|
||||
|
||||
let mut subscription = heads_subscription().await?;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<StakingMinerError>();
|
||||
let submit_lock = Arc::new(Mutex::new(()));
|
||||
|
||||
loop {
|
||||
let at = tokio::select! {
|
||||
maybe_rp = subscription.next() => {
|
||||
match maybe_rp {
|
||||
Some(Ok(r)) => r,
|
||||
Some(Err(e)) => {
|
||||
log::error!(target: LOG_TARGET, "subscription failed to decode Header {:?}, this is bug please file an issue", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
// The subscription was dropped, should only happen if:
|
||||
// - the connection was closed.
|
||||
// - the subscription could not keep up with the server.
|
||||
None => {
|
||||
log::warn!(target: LOG_TARGET, "subscription to `subscribeNewHeads/subscribeFinalizedHeads` terminated. Retrying..");
|
||||
subscription = heads_subscription().await?;
|
||||
continue
|
||||
}
|
||||
}
|
||||
},
|
||||
maybe_err = rx.recv() => {
|
||||
match maybe_err {
|
||||
Some(err) => return Err(err),
|
||||
None => unreachable!("at least one sender kept in the main loop should always return Some; qed"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn task and non-recoverable errors are sent back to the main task
|
||||
// such as if the connection has been closed.
|
||||
tokio::spawn(
|
||||
send_and_watch_extrinsic(rpc.clone(), tx.clone(), at, signer.clone(), config.clone(), submit_lock.clone())
|
||||
);
|
||||
}
|
||||
|
||||
/// Construct extrinsic at given block and watch it.
|
||||
async fn send_and_watch_extrinsic(
|
||||
rpc: SharedRpcClient,
|
||||
tx: mpsc::UnboundedSender<StakingMinerError>,
|
||||
at: Header,
|
||||
signer: Signer,
|
||||
config: MonitorConfig,
|
||||
submit_lock: Arc<Mutex<()>>,
|
||||
) {
|
||||
|
||||
async fn flatten<T>(
|
||||
handle: tokio::task::JoinHandle<Result<T, StakingMinerError>>
|
||||
) -> Result<T, StakingMinerError> {
|
||||
match handle.await {
|
||||
Ok(Ok(result)) => Ok(result),
|
||||
Ok(Err(err)) => Err(err),
|
||||
Err(err) => panic!("tokio spawn task failed; kill task: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
let hash = at.hash();
|
||||
log::trace!(target: LOG_TARGET, "new event at #{:?} ({:?})", at.number, hash);
|
||||
|
||||
// block on this because if this fails there is no way to recover from
|
||||
// that error i.e, upgrade/downgrade required.
|
||||
if let Err(err) = crate::check_versions::<Runtime>(&rpc, false).await {
|
||||
let _ = tx.send(err.into());
|
||||
return;
|
||||
}
|
||||
|
||||
let rpc1 = rpc.clone();
|
||||
let rpc2 = rpc.clone();
|
||||
let account = signer.account.clone();
|
||||
|
||||
let signed_phase_fut = tokio::spawn(async move {
|
||||
ensure_signed_phase::<Runtime, Block>(&rpc1, hash).await
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(config.delay as u64)).await;
|
||||
|
||||
let no_prev_sol_fut = tokio::spawn(async move {
|
||||
ensure_no_previous_solution::<Runtime, Block>(&rpc2, hash, &account).await
|
||||
});
|
||||
|
||||
// Run the calls in parallel and return once all has completed or any failed.
|
||||
if let Err(err) = tokio::try_join!(flatten(signed_phase_fut), flatten(no_prev_sol_fut)) {
|
||||
log::debug!(target: LOG_TARGET, "Skipping block {}; {}", at.number, err);
|
||||
return;
|
||||
}
|
||||
|
||||
let _lock = submit_lock.lock().await;
|
||||
|
||||
let mut ext = match crate::create_election_ext::<Runtime>(rpc.clone(), Some(hash), vec![]).await {
|
||||
Ok(ext) => ext,
|
||||
Err(err) => {
|
||||
log::debug!(target: LOG_TARGET, "Skipping block {}; {}", at.number, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// mine a solution, and run feasibility check on it as well.
|
||||
let raw_solution = match crate::mine_with::<Runtime>(&config.solver, &mut ext, true) {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
let _ = tx.send(err.into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let score = raw_solution.score;
|
||||
log::info!(target: LOG_TARGET, "mined solution with {:?}", score);
|
||||
|
||||
let nonce = match crate::get_account_info::<Runtime>(&rpc, &signer.account, Some(hash)).await {
|
||||
Ok(maybe_account) => {
|
||||
let acc = maybe_account.expect(crate::signer::SIGNER_ACCOUNT_WILL_EXIST);
|
||||
acc.nonce
|
||||
}
|
||||
Err(err) => {
|
||||
let _ = tx.send(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let tip = 0 as Balance;
|
||||
let period = <Runtime as frame_system::Config>::BlockHashCount::get() / 2;
|
||||
let current_block = at.number.saturating_sub(1);
|
||||
let era = sp_runtime::generic::Era::mortal(period.into(), current_block.into());
|
||||
|
||||
log::trace!(
|
||||
target: LOG_TARGET, "transaction mortality: {:?} -> {:?}",
|
||||
era.birth(current_block.into()),
|
||||
era.death(current_block.into()),
|
||||
);
|
||||
|
||||
let extrinsic = ext.execute_with(|| create_uxt(raw_solution, signer.clone(), nonce, tip, era));
|
||||
let bytes = sp_core::Bytes(extrinsic.encode());
|
||||
|
||||
let rpc1 = rpc.clone();
|
||||
let rpc2 = rpc.clone();
|
||||
let rpc3 = rpc.clone();
|
||||
|
||||
let latest_head = match get_latest_head::<Runtime>(&rpc, &config.listen).await {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => {
|
||||
log::debug!(target: LOG_TARGET, "Skipping to submit at block {}; {}", at.number, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let ensure_strategy_met_fut = tokio::spawn(async move {
|
||||
ensure_strategy_met::<Runtime, Block>(
|
||||
&rpc1,
|
||||
latest_head,
|
||||
score,
|
||||
config.submission_strategy,
|
||||
SignedMaxSubmissions::get()
|
||||
).await
|
||||
});
|
||||
|
||||
let ensure_signed_phase_fut = tokio::spawn(async move {
|
||||
ensure_signed_phase::<Runtime, Block>(&rpc2, latest_head).await
|
||||
});
|
||||
|
||||
let account = signer.account.clone();
|
||||
let no_prev_sol_fut = tokio::spawn(async move {
|
||||
ensure_no_previous_solution::<Runtime, Block>(&rpc3, latest_head, &account).await
|
||||
});
|
||||
|
||||
// Run the calls in parallel and return once all has completed or any failed.
|
||||
if let Err(err) = tokio::try_join!(
|
||||
flatten(ensure_strategy_met_fut),
|
||||
flatten(ensure_signed_phase_fut),
|
||||
flatten(no_prev_sol_fut),
|
||||
) {
|
||||
log::debug!(target: LOG_TARGET, "Skipping to submit at block {}; {}", at.number, err);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tx_subscription = match rpc.watch_extrinsic(&bytes).await {
|
||||
Ok(sub) => sub,
|
||||
Err(RpcError::RestartNeeded(e)) => {
|
||||
let _ = tx.send(RpcError::RestartNeeded(e).into());
|
||||
return
|
||||
},
|
||||
Err(why) => {
|
||||
// This usually happens when we've been busy with mining for a few blocks, and
|
||||
// now we're receiving the subscriptions of blocks in which we were busy. In
|
||||
// these blocks, we still don't have a solution, so we re-compute a new solution
|
||||
// and submit it with an outdated `Nonce`, which yields most often `Stale`
|
||||
// error. NOTE: to improve this overall, and to be able to introduce an array of
|
||||
// other fancy features, we should make this multi-threaded and do the
|
||||
// computation outside of this callback.
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failing to submit a transaction {:?}. ignore block: {}",
|
||||
why, at.number
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
while let Some(rp) = tx_subscription.next().await {
|
||||
let status_update = match rp {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
log::error!(target: LOG_TARGET, "subscription failed to decode TransactionStatus {:?}, this is a bug please file an issue", e);
|
||||
let _ = tx.send(e.into());
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
log::trace!(target: LOG_TARGET, "status update {:?}", status_update);
|
||||
match status_update {
|
||||
TransactionStatus::Ready |
|
||||
TransactionStatus::Broadcast(_) |
|
||||
TransactionStatus::Future => continue,
|
||||
TransactionStatus::InBlock((hash, _)) => {
|
||||
log::info!(target: LOG_TARGET, "included at {:?}", hash);
|
||||
let key = StorageKey(
|
||||
frame_support::storage::storage_prefix(b"System", b"Events").to_vec(),
|
||||
);
|
||||
|
||||
let events = match rpc.get_storage_and_decode::<
|
||||
Vec<frame_system::EventRecord<RuntimeEvent, <Block as BlockT>::Hash>>,
|
||||
>(&key, Some(hash))
|
||||
.await {
|
||||
Ok(rp) => rp.unwrap_or_default(),
|
||||
Err(RpcHelperError::JsonRpsee(RpcError::RestartNeeded(e))) => {
|
||||
let _ = tx.send(RpcError::RestartNeeded(e).into());
|
||||
return;
|
||||
}
|
||||
// Decoding or other RPC error => just terminate the task.
|
||||
Err(e) => {
|
||||
log::warn!(target: LOG_TARGET, "get_storage [key: {:?}, hash: {:?}] failed: {:?}; skip block: {}",
|
||||
key, hash, e, at.number
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
log::info!(target: LOG_TARGET, "events at inclusion {:?}", events);
|
||||
},
|
||||
TransactionStatus::Retracted(hash) => {
|
||||
log::info!(target: LOG_TARGET, "Retracted at {:?}", hash);
|
||||
},
|
||||
TransactionStatus::Finalized((hash, _)) => {
|
||||
log::info!(target: LOG_TARGET, "Finalized at {:?}", hash);
|
||||
break
|
||||
},
|
||||
_ => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Stopping listen due to other status {:?}",
|
||||
status_update
|
||||
);
|
||||
break
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
|
||||
monitor_cmd_for!(polkadot);
|
||||
monitor_cmd_for!(kusama);
|
||||
monitor_cmd_for!(westend);
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn score_passes_strategy_works() {
|
||||
let s = |x| sp_npos_elections::ElectionScore { minimal_stake: x, ..Default::default() };
|
||||
let two = Perbill::from_percent(2);
|
||||
|
||||
// anything passes Always
|
||||
assert!(score_passes_strategy(s(0), s(0), SubmissionStrategy::Always));
|
||||
assert!(score_passes_strategy(s(5), s(0), SubmissionStrategy::Always));
|
||||
assert!(score_passes_strategy(s(5), s(10), SubmissionStrategy::Always));
|
||||
|
||||
// if leading
|
||||
assert!(score_passes_strategy(s(0), s(0), SubmissionStrategy::IfLeading));
|
||||
assert!(score_passes_strategy(s(1), s(0), SubmissionStrategy::IfLeading));
|
||||
assert!(score_passes_strategy(s(2), s(0), SubmissionStrategy::IfLeading));
|
||||
assert!(!score_passes_strategy(s(5), s(10), SubmissionStrategy::IfLeading));
|
||||
assert!(!score_passes_strategy(s(9), s(10), SubmissionStrategy::IfLeading));
|
||||
assert!(score_passes_strategy(s(10), s(10), SubmissionStrategy::IfLeading));
|
||||
|
||||
// if better by 2%
|
||||
assert!(!score_passes_strategy(s(50), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
assert!(!score_passes_strategy(s(100), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
assert!(!score_passes_strategy(s(101), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
assert!(!score_passes_strategy(s(102), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
assert!(score_passes_strategy(s(103), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
assert!(score_passes_strategy(s(150), s(100), SubmissionStrategy::ClaimBetterThan(two)));
|
||||
|
||||
// if no less than 2% worse
|
||||
assert!(!score_passes_strategy(s(50), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(!score_passes_strategy(s(97), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(98), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(99), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(100), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(101), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(102), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(103), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
assert!(score_passes_strategy(s(150), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::prelude::*;
|
||||
use clap::Parser;
|
||||
use sp_runtime::Perbill;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[command(author, version, about)]
|
||||
pub(crate) struct Opt {
|
||||
/// The `ws` node to connect to.
|
||||
#[arg(long, short, default_value = DEFAULT_URI, env = "URI", global = true)]
|
||||
pub uri: String,
|
||||
|
||||
/// WS connection timeout in number of seconds.
|
||||
#[arg(long, default_value_t = 60)]
|
||||
pub connection_timeout: usize,
|
||||
|
||||
/// WS request timeout in number of seconds.
|
||||
#[arg(long, default_value_t = 60 * 10)]
|
||||
pub request_timeout: usize,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) enum Command {
|
||||
/// Monitor for the phase being signed, then compute.
|
||||
Monitor(MonitorConfig),
|
||||
|
||||
/// Just compute a solution now, and don't submit it.
|
||||
DryRun(DryRunConfig),
|
||||
|
||||
/// Provide a solution that can be submitted to the chain as an emergency response.
|
||||
EmergencySolution(EmergencySolutionConfig),
|
||||
|
||||
/// Return information about the current version
|
||||
Info(InfoOpts),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct MonitorConfig {
|
||||
/// The path to a file containing the seed of the account. If the file is not found, the seed
|
||||
/// is used as-is.
|
||||
///
|
||||
/// Can also be provided via the `SEED` environment variable.
|
||||
///
|
||||
/// WARNING: Don't use an account with a large stash for this. Based on how the bot is
|
||||
/// configured, it might re-try and lose funds through transaction fees/deposits.
|
||||
#[arg(long, short, env = "SEED")]
|
||||
pub seed_or_path: String,
|
||||
|
||||
/// They type of event to listen to.
|
||||
///
|
||||
/// Typically, finalized is safer and there is no chance of anything going wrong, but it can be
|
||||
/// slower. It is recommended to use finalized, if the duration of the signed phase is longer
|
||||
/// than the the finality delay.
|
||||
#[arg(long, default_value = "head", value_parser = ["head", "finalized"])]
|
||||
pub listen: String,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[command(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// Submission strategy to use.
|
||||
///
|
||||
/// Possible options:
|
||||
///
|
||||
/// `--submission-strategy if-leading`: only submit if leading.
|
||||
///
|
||||
/// `--submission-strategy always`: always submit.
|
||||
///
|
||||
/// `--submission-strategy "percent-better percent"`: submit if the submission is `n` percent
|
||||
/// better.
|
||||
///
|
||||
/// `--submission-strategy "no-worse-than percent"`: submit if submission is no more than
|
||||
/// `n` percent worse.
|
||||
#[clap(long, default_value = "if-leading")]
|
||||
pub submission_strategy: SubmissionStrategy,
|
||||
|
||||
/// Delay in number seconds to wait until starting mining a solution.
|
||||
///
|
||||
/// At every block when a solution is attempted
|
||||
/// a delay can be enforced to avoid submitting at
|
||||
/// "same time" and risk potential races with other miners.
|
||||
///
|
||||
/// When this is enabled and there are competing solutions, your solution might not be
|
||||
/// submitted if the scores are equal.
|
||||
#[arg(long, default_value_t = 0)]
|
||||
pub delay: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct DryRunConfig {
|
||||
/// The path to a file containing the seed of the account. If the file is not found, the seed
|
||||
/// is used as-is.
|
||||
///
|
||||
/// Can also be provided via the `SEED` environment variable.
|
||||
///
|
||||
/// WARNING: Don't use an account with a large stash for this. Based on how the bot is
|
||||
/// configured, it might re-try and lose funds through transaction fees/deposits.
|
||||
#[arg(long, short, env = "SEED")]
|
||||
pub seed_or_path: String,
|
||||
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[arg(long)]
|
||||
pub at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[command(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// Force create a new snapshot, else expect one to exist onchain.
|
||||
#[arg(long)]
|
||||
pub force_snapshot: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct EmergencySolutionConfig {
|
||||
/// The block hash at which scraping happens. If none is provided, the latest head is used.
|
||||
#[arg(long)]
|
||||
pub at: Option<Hash>,
|
||||
|
||||
/// The solver algorithm to use.
|
||||
#[command(subcommand)]
|
||||
pub solver: Solver,
|
||||
|
||||
/// The number of top backed winners to take. All are taken, if not provided.
|
||||
pub take: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) struct InfoOpts {
|
||||
/// Serialize the output as json
|
||||
#[arg(long, short)]
|
||||
pub json: bool,
|
||||
}
|
||||
|
||||
/// Submission strategy to use.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum SubmissionStrategy {
|
||||
/// Always submit.
|
||||
Always,
|
||||
/// Only submit if at the time, we are the best (or equal to it).
|
||||
IfLeading,
|
||||
/// Submit if we are no worse than `Perbill` worse than the best.
|
||||
ClaimNoWorseThan(Perbill),
|
||||
/// Submit if we are leading, or if the solution that's leading is more that the given
|
||||
/// `Perbill` better than us. This helps detect obviously fake solutions and still combat them.
|
||||
ClaimBetterThan(Perbill),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub(crate) enum Solver {
|
||||
SeqPhragmen {
|
||||
#[arg(long, default_value_t = 10)]
|
||||
iterations: usize,
|
||||
},
|
||||
PhragMMS {
|
||||
#[arg(long, default_value_t = 10)]
|
||||
iterations: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Custom `impl` to parse `SubmissionStrategy` from CLI.
|
||||
///
|
||||
/// Possible options:
|
||||
/// * --submission-strategy if-leading: only submit if leading
|
||||
/// * --submission-strategy always: always submit
|
||||
/// * --submission-strategy "percent-better percent": submit if submission is `n` percent better.
|
||||
/// * --submission-strategy "no-worse-than percent": submit if submission is no more than `n`
|
||||
/// percent worse.
|
||||
impl FromStr for SubmissionStrategy {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim();
|
||||
|
||||
let res = if s == "if-leading" {
|
||||
Self::IfLeading
|
||||
} else if s == "always" {
|
||||
Self::Always
|
||||
} else if let Some(percent) = s.strip_prefix("no-worse-than ") {
|
||||
let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?;
|
||||
Self::ClaimNoWorseThan(Perbill::from_percent(percent))
|
||||
} else if let Some(percent) = s.strip_prefix("percent-better ") {
|
||||
let percent: u32 = percent.parse().map_err(|e| format!("{:?}", e))?;
|
||||
Self::ClaimBetterThan(Perbill::from_percent(percent))
|
||||
} else {
|
||||
return Err(s.into())
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_super {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cli_monitor_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"monitor",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"--listen",
|
||||
"head",
|
||||
"--delay",
|
||||
"12",
|
||||
"seq-phragmen",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
connection_timeout: 60,
|
||||
request_timeout: 10 * 60,
|
||||
command: Command::Monitor(MonitorConfig {
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
listen: "head".to_string(),
|
||||
solver: Solver::SeqPhragmen { iterations: 10 },
|
||||
submission_strategy: SubmissionStrategy::IfLeading,
|
||||
delay: 12,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_dry_run_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"dry-run",
|
||||
"--seed-or-path",
|
||||
"//Alice",
|
||||
"phrag-mms",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
connection_timeout: 60,
|
||||
request_timeout: 10 * 60,
|
||||
command: Command::DryRun(DryRunConfig {
|
||||
seed_or_path: "//Alice".to_string(),
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 10 },
|
||||
force_snapshot: false,
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_emergency_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"emergency-solution",
|
||||
"99",
|
||||
"phrag-mms",
|
||||
"--iterations",
|
||||
"1337",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
connection_timeout: 60,
|
||||
request_timeout: 10 * 60,
|
||||
command: Command::EmergencySolution(EmergencySolutionConfig {
|
||||
take: Some(99),
|
||||
at: None,
|
||||
solver: Solver::PhragMMS { iterations: 1337 }
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_info_works() {
|
||||
let opt = Opt::try_parse_from([env!("CARGO_PKG_NAME"), "--uri", "hi", "info"]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
connection_timeout: 60,
|
||||
request_timeout: 10 * 60,
|
||||
command: Command::Info(InfoOpts { json: false })
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_request_conn_timeout_works() {
|
||||
let opt = Opt::try_parse_from([
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--uri",
|
||||
"hi",
|
||||
"--request-timeout",
|
||||
"10",
|
||||
"--connection-timeout",
|
||||
"9",
|
||||
"info",
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
opt,
|
||||
Opt {
|
||||
uri: "hi".to_string(),
|
||||
connection_timeout: 9,
|
||||
request_timeout: 10,
|
||||
command: Command::Info(InfoOpts { json: false })
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submission_strategy_from_str_works() {
|
||||
use std::str::FromStr;
|
||||
|
||||
assert_eq!(SubmissionStrategy::from_str("if-leading"), Ok(SubmissionStrategy::IfLeading));
|
||||
assert_eq!(SubmissionStrategy::from_str("always"), Ok(SubmissionStrategy::Always));
|
||||
assert_eq!(
|
||||
SubmissionStrategy::from_str(" percent-better 99 "),
|
||||
Ok(SubmissionStrategy::ClaimBetterThan(Perbill::from_percent(99)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Types that we don't fetch from a particular runtime and just assume that they are constant all
|
||||
//! of the place.
|
||||
//!
|
||||
//! It is actually easy to convert the rest as well, but it'll be a lot of noise in our codebase,
|
||||
//! needing to sprinkle `any_runtime` in a few extra places.
|
||||
|
||||
/// The account id type.
|
||||
pub type AccountId = core_primitives::AccountId;
|
||||
/// The block number type.
|
||||
pub type BlockNumber = core_primitives::BlockNumber;
|
||||
/// The balance type.
|
||||
pub type Balance = core_primitives::Balance;
|
||||
/// Index of a transaction in the chain.
|
||||
pub type Nonce = core_primitives::Nonce;
|
||||
/// The hash type. We re-export it here, but we can easily get it from block as well.
|
||||
pub type Hash = core_primitives::Hash;
|
||||
/// The header type. We re-export it here, but we can easily get it from block as well.
|
||||
pub type Header = core_primitives::Header;
|
||||
/// The block type.
|
||||
pub type Block = core_primitives::Block;
|
||||
|
||||
pub use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
|
||||
/// Default URI to connect to.
|
||||
pub const DEFAULT_URI: &str = "wss://rpc.polkadot.io:443";
|
||||
/// The logging target.
|
||||
pub const LOG_TARGET: &str = "staking-miner";
|
||||
|
||||
/// The election provider pallet.
|
||||
pub use pallet_election_provider_multi_phase as EPM;
|
||||
|
||||
/// The externalities type.
|
||||
pub type Ext = sp_state_machine::TestExternalities<sp_runtime::traits::HashingFor<Block>>;
|
||||
|
||||
/// The key pair type being used. We "strongly" assume sr25519 for simplicity.
|
||||
pub type Pair = sp_core::sr25519::Pair;
|
||||
|
||||
/// A dynamic token type used to represent account balances.
|
||||
pub type Token = sub_tokens::dynamic::DynamicToken;
|
||||
@@ -1,182 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! JSON-RPC related types and helpers.
|
||||
|
||||
use super::*;
|
||||
use jsonrpsee::{
|
||||
core::{Error as RpcError, RpcResult},
|
||||
proc_macros::rpc,
|
||||
};
|
||||
use pallet_transaction_payment::RuntimeDispatchInfo;
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::{future::Future, time::Duration};
|
||||
|
||||
#[derive(frame_support::DebugNoBound, thiserror::Error)]
|
||||
pub(crate) enum RpcHelperError {
|
||||
JsonRpsee(#[from] jsonrpsee::core::Error),
|
||||
Codec(#[from] codec::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RpcHelperError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<RpcHelperError as std::fmt::Debug>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(client)]
|
||||
pub trait RpcApi {
|
||||
/// Fetch system name.
|
||||
#[method(name = "system_chain")]
|
||||
async fn system_chain(&self) -> RpcResult<String>;
|
||||
|
||||
/// Fetch a storage key.
|
||||
#[method(name = "state_getStorage")]
|
||||
async fn storage(&self, key: &StorageKey, hash: Option<Hash>) -> RpcResult<Option<Bytes>>;
|
||||
|
||||
/// Fetch the runtime version.
|
||||
#[method(name = "state_getRuntimeVersion")]
|
||||
async fn runtime_version(&self, at: Option<Hash>) -> RpcResult<RuntimeVersion>;
|
||||
|
||||
/// Fetch the payment query info.
|
||||
#[method(name = "payment_queryInfo")]
|
||||
async fn payment_query_info(
|
||||
&self,
|
||||
encoded_xt: &Bytes,
|
||||
at: Option<&Hash>,
|
||||
) -> RpcResult<RuntimeDispatchInfo<Balance>>;
|
||||
|
||||
/// Dry run an extrinsic at a given block. Return SCALE encoded
|
||||
/// [`sp_runtime::ApplyExtrinsicResult`].
|
||||
#[method(name = "system_dryRun")]
|
||||
async fn dry_run(&self, extrinsic: &Bytes, at: Option<Hash>) -> RpcResult<Bytes>;
|
||||
|
||||
/// Get hash of the n-th block in the canon chain.
|
||||
///
|
||||
/// By default returns latest block hash.
|
||||
#[method(name = "chain_getBlockHash", aliases = ["chain_getHead"], blocking)]
|
||||
fn block_hash(&self, hash: Option<Hash>) -> RpcResult<Option<Hash>>;
|
||||
|
||||
/// Get hash of the last finalized block in the canon chain.
|
||||
#[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"], blocking)]
|
||||
fn finalized_head(&self) -> RpcResult<Hash>;
|
||||
|
||||
/// Submit an extrinsic to watch.
|
||||
///
|
||||
/// See [`TransactionStatus`](sc_transaction_pool_api::TransactionStatus) for details on
|
||||
/// transaction life cycle.
|
||||
#[subscription(
|
||||
name = "author_submitAndWatchExtrinsic" => "author_extrinsicUpdate",
|
||||
unsubscribe = "author_unwatchExtrinsic",
|
||||
item = TransactionStatus<Hash, Hash>
|
||||
)]
|
||||
fn watch_extrinsic(&self, bytes: &Bytes);
|
||||
|
||||
/// New head subscription.
|
||||
#[subscription(
|
||||
name = "chain_subscribeNewHeads" => "newHead",
|
||||
unsubscribe = "chain_unsubscribeNewHeads",
|
||||
item = Header
|
||||
)]
|
||||
fn subscribe_new_heads(&self);
|
||||
|
||||
/// Finalized head subscription.
|
||||
#[subscription(
|
||||
name = "chain_subscribeFinalizedHeads" => "chain_finalizedHead",
|
||||
unsubscribe = "chain_unsubscribeFinalizedHeads",
|
||||
item = Header
|
||||
)]
|
||||
fn subscribe_finalized_heads(&self);
|
||||
}
|
||||
|
||||
type Uri = String;
|
||||
|
||||
/// Wraps a shared web-socket JSON-RPC client that can be cloned.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SharedRpcClient(Arc<WsClient>, Uri);
|
||||
|
||||
impl Deref for SharedRpcClient {
|
||||
type Target = WsClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedRpcClient {
|
||||
/// Get the URI of the client.
|
||||
pub fn uri(&self) -> &str {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// Create a new shared JSON-RPC web-socket client.
|
||||
pub(crate) async fn new(
|
||||
uri: &str,
|
||||
connection_timeout: Duration,
|
||||
request_timeout: Duration,
|
||||
) -> Result<Self, RpcError> {
|
||||
let client = WsClientBuilder::default()
|
||||
.connection_timeout(connection_timeout)
|
||||
.max_request_body_size(u32::MAX)
|
||||
.request_timeout(request_timeout)
|
||||
.max_concurrent_requests(u32::MAX as usize)
|
||||
.build(uri)
|
||||
.await?;
|
||||
Ok(Self(Arc::new(client), uri.to_owned()))
|
||||
}
|
||||
|
||||
/// Get a storage item and decode it as `T`.
|
||||
///
|
||||
/// # Return value:
|
||||
///
|
||||
/// The function returns:
|
||||
///
|
||||
/// * `Ok(Some(val))` if successful.
|
||||
/// * `Ok(None)` if the storage item was not found.
|
||||
/// * `Err(e)` if the JSON-RPC call failed.
|
||||
pub(crate) async fn get_storage_and_decode<'a, T: codec::Decode>(
|
||||
&self,
|
||||
key: &StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> Result<Option<T>, RpcHelperError> {
|
||||
if let Some(bytes) = self.storage(key, hash).await? {
|
||||
let decoded = <T as codec::Decode>::decode(&mut &*bytes.0)
|
||||
.map_err::<RpcHelperError, _>(Into::into)?;
|
||||
Ok(Some(decoded))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a future that returns `Bytes` and tries to decode those bytes into the type `Dec`.
|
||||
/// Warning: don't use for storage, it will fail for non-existent storage items.
|
||||
///
|
||||
/// # Return value:
|
||||
///
|
||||
/// The function returns:
|
||||
///
|
||||
/// * `Ok(val)` if successful.
|
||||
/// * `Err(RpcHelperError::JsonRpsee)` if the JSON-RPC call failed.
|
||||
/// * `Err(RpcHelperError::Codec)` if `Bytes` could not be decoded.
|
||||
pub(crate) async fn await_request_and_decode<'a, Dec: codec::Decode>(
|
||||
req: impl Future<Output = Result<Bytes, RpcError>>,
|
||||
) -> Result<Dec, RpcHelperError> {
|
||||
let bytes = req.await?;
|
||||
Dec::decode(&mut &*bytes.0).map_err::<RpcHelperError, _>(Into::into)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub(crate) struct RuntimeWrapper<'a>(pub &'a RuntimeVersion);
|
||||
|
||||
impl<'a> fmt::Display for RuntimeWrapper<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let width = 16;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
r#" impl_name : {impl_name:>width$}
|
||||
spec_name : {spec_name:>width$}
|
||||
spec_version : {spec_version:>width$}
|
||||
transaction_version : {transaction_version:>width$}
|
||||
impl_version : {impl_version:>width$}
|
||||
authoringVersion : {authoring_version:>width$}
|
||||
state_version : {state_version:>width$}"#,
|
||||
spec_name = self.0.spec_name.to_string(),
|
||||
impl_name = self.0.impl_name.to_string(),
|
||||
spec_version = self.0.spec_version,
|
||||
impl_version = self.0.impl_version,
|
||||
authoring_version = self.0.authoring_version,
|
||||
transaction_version = self.0.transaction_version,
|
||||
state_version = self.0.state_version,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RuntimeVersion> for RuntimeWrapper<'a> {
|
||||
fn from(r: &'a RuntimeVersion) -> Self {
|
||||
RuntimeWrapper(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub(crate) struct RuntimeVersions<'a> {
|
||||
/// The `RuntimeVersion` linked in the staking-miner
|
||||
pub linked: RuntimeWrapper<'a>,
|
||||
|
||||
/// The `RuntimeVersion` reported by the node we connect to via RPC
|
||||
pub remote: RuntimeWrapper<'a>,
|
||||
|
||||
/// This `bool` reports whether both remote and linked `RuntimeVersion` are compatible
|
||||
/// and if the staking-miner is expected to work properly against the remote runtime
|
||||
compatible: bool,
|
||||
}
|
||||
|
||||
impl<'a> RuntimeVersions<'a> {
|
||||
pub fn new(
|
||||
remote_runtime_version: &'a RuntimeVersion,
|
||||
linked_runtime_version: &'a RuntimeVersion,
|
||||
) -> Self {
|
||||
Self {
|
||||
remote: remote_runtime_version.into(),
|
||||
linked: linked_runtime_version.into(),
|
||||
compatible: are_runtimes_compatible(remote_runtime_version, linked_runtime_version),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether runtimes are compatible. Currently we only support equality.
|
||||
fn are_runtimes_compatible(r1: &RuntimeVersion, r2: &RuntimeVersion) -> bool {
|
||||
r1 == r2
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for RuntimeVersions<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let _ = write!(f, "- linked:\n{}", self.linked);
|
||||
let _ = write!(f, "- remote :\n{}", self.remote);
|
||||
write!(f, "Compatible: {}", if self.compatible { "YES" } else { "NO" })
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wrappers around creating a signer account.
|
||||
|
||||
use crate::{prelude::*, rpc::SharedRpcClient, AccountId, Error, Nonce, Pair, LOG_TARGET};
|
||||
use frame_system::AccountInfo;
|
||||
use sp_core::{crypto::Pair as _, storage::StorageKey};
|
||||
|
||||
pub(crate) const SIGNER_ACCOUNT_WILL_EXIST: &str =
|
||||
"signer account is checked to exist upon startup; it can only die if it transfers funds out \
|
||||
of it, or get slashed. If it does not exist at this point, it is likely due to a bug, or the \
|
||||
signer got slashed. Terminating.";
|
||||
|
||||
/// Some information about the signer. Redundant at this point, but makes life easier.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Signer {
|
||||
/// The account id.
|
||||
pub(crate) account: AccountId,
|
||||
|
||||
/// The full crypto key-pair.
|
||||
pub(crate) pair: Pair,
|
||||
}
|
||||
|
||||
pub(crate) async fn get_account_info<T: frame_system::Config<Hash = Hash> + EPM::Config>(
|
||||
rpc: &SharedRpcClient,
|
||||
who: &T::AccountId,
|
||||
maybe_at: Option<T::Hash>,
|
||||
) -> Result<Option<AccountInfo<Nonce, T::AccountData>>, Error<T>> {
|
||||
rpc.get_storage_and_decode::<AccountInfo<Nonce, T::AccountData>>(
|
||||
&StorageKey(<frame_system::Account<T>>::hashed_key_for(&who)),
|
||||
maybe_at,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Read the signer account's URI
|
||||
pub(crate) async fn signer_uri_from_string<
|
||||
T: frame_system::Config<
|
||||
AccountId = AccountId,
|
||||
Nonce = Nonce,
|
||||
AccountData = pallet_balances::AccountData<Balance>,
|
||||
Hash = Hash,
|
||||
> + EPM::Config,
|
||||
>(
|
||||
mut seed_or_path: &str,
|
||||
client: &SharedRpcClient,
|
||||
) -> Result<Signer, Error<T>> {
|
||||
seed_or_path = seed_or_path.trim();
|
||||
|
||||
let seed = match std::fs::read(seed_or_path) {
|
||||
Ok(s) => String::from_utf8(s).map_err(|_| Error::<T>::AccountDoesNotExists)?,
|
||||
Err(_) => seed_or_path.to_string(),
|
||||
};
|
||||
let seed = seed.trim();
|
||||
|
||||
let pair = Pair::from_string(seed, None)?;
|
||||
let account = T::AccountId::from(pair.public());
|
||||
let _info = get_account_info::<T>(client, &account, None)
|
||||
.await?
|
||||
.ok_or(Error::<T>::AccountDoesNotExists)?;
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"loaded account {:?}, free: {:?}, info: {:?}",
|
||||
&account,
|
||||
Token::from(_info.data.free),
|
||||
_info
|
||||
);
|
||||
Ok(Signer { account, pair })
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use assert_cmd::{cargo::cargo_bin, Command};
|
||||
use serde_json::{Result, Value};
|
||||
|
||||
#[test]
|
||||
fn cli_version_works() {
|
||||
let crate_name = env!("CARGO_PKG_NAME");
|
||||
let output = Command::new(cargo_bin(crate_name)).arg("--version").output().unwrap();
|
||||
|
||||
assert!(output.status.success(), "command returned with non-success exit code");
|
||||
let version = String::from_utf8_lossy(&output.stdout).trim().to_owned();
|
||||
|
||||
assert_eq!(version, format!("{} {}", crate_name, env!("CARGO_PKG_VERSION")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_info_works() {
|
||||
let crate_name = env!("CARGO_PKG_NAME");
|
||||
let output = Command::new(cargo_bin(crate_name))
|
||||
.arg("info")
|
||||
.arg("--json")
|
||||
.env("RUST_LOG", "none")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success(), "command returned with non-success exit code");
|
||||
let info = String::from_utf8_lossy(&output.stdout).trim().to_owned();
|
||||
let v: Result<Value> = serde_json::from_str(&info);
|
||||
let v = v.unwrap();
|
||||
assert!(!v["builtin"].to_string().is_empty());
|
||||
assert!(!v["builtin"]["spec_name"].to_string().is_empty());
|
||||
assert!(!v["builtin"]["spec_version"].to_string().is_empty());
|
||||
assert!(!v["remote"].to_string().is_empty());
|
||||
}
|
||||
@@ -149,7 +149,8 @@
|
||||
//! while this binary lives in the Polkadot repository, this particular subcommand of it can work
|
||||
//! against any substrate-based chain.
|
||||
//!
|
||||
//! See the `staking-miner` documentation in the Polkadot repository for more information.
|
||||
//! See the [`staking-miner`](https://github.com/paritytech/staking-miner-v2) docs for more
|
||||
//! information.
|
||||
//!
|
||||
//! ## Feasible Solution (correct solution)
|
||||
//!
|
||||
|
||||
Reference in New Issue
Block a user