Compare commits
361 Commits
perf_dedup
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
|
3b9c95bd52 | ||
|
186bb19965 | ||
|
87b76aeeb0 | ||
|
a57c7ba5df | ||
|
2efb909051 | ||
|
418076ab3e | ||
|
36484f4f08 | ||
|
569d531573 | ||
|
0ad4757159 | ||
|
e2fa6a0f7a | ||
|
533eca3b4c | ||
|
ff04a5b989 | ||
|
cf18292fd3 | ||
|
92216c01ff | ||
|
0e5a58b883 | ||
|
d4292774c5 | ||
|
97b5a71ceb | ||
|
804fac8ea9 | ||
|
985af71241 | ||
|
98f059e89c | ||
|
274a6a5ccb | ||
|
2d55a8e1c3 | ||
|
3f35d7fad9 | ||
|
7111918596 | ||
|
5e0086c1ee | ||
|
d1f141484e | ||
|
db12d90735 | ||
|
2207e49633 | ||
|
39c86c6c44 | ||
|
6c53f7f588 | ||
|
2b0f16e7c3 | ||
|
017170c99d | ||
|
a14c7c37ee | ||
|
9d2232306e | ||
|
12bddbc4ec | ||
|
fcaf01e243 | ||
|
856d29c7b7 | ||
|
6a0d2fcfa7 | ||
|
4bc440666a | ||
|
c0d0724be0 | ||
|
7ee549e5ae | ||
|
227df52213 | ||
|
99a057927c | ||
|
cafc18c3f9 | ||
|
0dd36f3201 | ||
|
3ea9ca35fa | ||
|
a245efe83d | ||
|
c81dd602c4 | ||
|
084fb79ad8 | ||
|
2e3eafaa11 | ||
|
2996f1f783 | ||
|
05f04a22b7 | ||
|
d0e85c293f | ||
|
6872fc79ba | ||
|
0a3a18744f | ||
|
09d064c090 | ||
|
ff604efc44 | ||
|
20d031e2b8 | ||
|
5766567e9f | ||
|
88e1192c6b | ||
|
cadc2de77d | ||
|
8b32e80ee2 | ||
|
7ebf398ed7 | ||
|
ac70070e5b | ||
|
f00016b647 | ||
|
bcda74f42f | ||
|
c97f34a0fd | ||
|
72c68695b5 | ||
|
d0d256ee9a | ||
|
7e08ae1d0c | ||
|
ebe3d2d59d | ||
|
04d23a1597 | ||
|
42bdf1d864 | ||
|
8c872e9ce0 | ||
|
70ebab2c82 | ||
|
1add82aa9e | ||
|
e2d0ede630 | ||
|
dcd0a39cb6 | ||
|
1719d2349f | ||
|
ee7e411d68 | ||
|
970f543ef6 | ||
|
1351c1bbcf | ||
|
c696944d36 | ||
|
5726f42a7c | ||
|
ae7fedf0b1 | ||
|
b4100a9b5d | ||
|
1a68f81f89 | ||
|
da00b39f4f | ||
|
619335df1a | ||
|
fa680a35ea | ||
|
b66a304e7b | ||
|
3c235503de | ||
|
a102453bae | ||
|
bca1d51735 | ||
|
6e28a03c73 | ||
|
813725dfec | ||
|
de30699fa5 | ||
|
aeda27433f | ||
|
4213ece7bd | ||
|
f7118258d6 | ||
|
aaf657297f | ||
|
83d31c9e65 | ||
|
115d71536b | ||
|
c2435363f3 | ||
|
7939fdc3e5 | ||
|
577fa4ec0c | ||
|
88b66ae3a8 | ||
|
a6d736572c | ||
|
6ae87109d2 | ||
|
56e23432b0 | ||
|
64f5e57666 | ||
|
527f62c744 | ||
|
917113914d | ||
|
43393ea45d | ||
|
b44f40ee3a | ||
|
03bf66a51b | ||
|
ab92578b02 | ||
|
bb50259956 | ||
|
bf689303d4 | ||
|
197f42d346 | ||
|
a43d04d295 | ||
|
98707baec2 | ||
|
c42b80f099 | ||
|
d2a407a9a7 | ||
|
36167b032c | ||
|
21656c26e7 | ||
|
20bc37c9c9 | ||
|
89f5145f64 | ||
|
d15d495f17 | ||
|
d6927365ed | ||
|
22a2a4252a | ||
|
05b387a852 | ||
|
824446710b | ||
|
0855cef76c | ||
|
69b47915b1 | ||
|
c04438be4b | ||
|
817f47d970 | ||
|
3d9874b95a | ||
|
12dffc105a | ||
|
f7753ce85f | ||
|
a75c9378f2 | ||
|
6399647e64 | ||
|
78089941ff | ||
|
ac13d14e30 | ||
|
c078ca3fb3 | ||
|
34443a238e | ||
|
2f9e30a1f7 | ||
|
4bd6a231d2 | ||
|
e4a1799334 | ||
|
59e64d5e99 | ||
|
9e87eb5f71 | ||
|
9213fcb11b | ||
|
0a1ab945bc | ||
|
d5dec989b9 | ||
|
2ca7ae8e30 | ||
|
47e4291303 | ||
|
9d036be61b | ||
|
a7598b6d78 | ||
|
57a9146fa1 | ||
|
67f6787f7a | ||
|
226a71f073 | ||
|
e545828fc5 | ||
|
205cd4609b | ||
|
ae175a026b | ||
|
86d465c531 | ||
|
f67a27eeea | ||
|
1b287f1b59 | ||
|
f334931903 | ||
|
fb47c1f0a3 | ||
|
c7aa7fb66b | ||
|
869cfc9a1c | ||
|
ba2d83f580 | ||
|
7aa1fb4e24 | ||
|
6587dbfa47 | ||
|
a25ac1c988 | ||
|
c899685cb2 | ||
|
541b5a4826 | ||
|
dcd4ea9111 | ||
|
7873175764 | ||
|
86cf226395 | ||
|
f0f4042680 | ||
|
3c65fd7ba3 | ||
|
e52e48076e | ||
|
f2d406ad5d | ||
|
5acf0f6331 | ||
|
d7fcfee4db | ||
|
b2e475b5c4 | ||
|
a160fc30f2 | ||
|
514aab46d9 | ||
|
a146f2d853 | ||
|
c5d8560cdb | ||
|
dfef68f985 | ||
|
4de14e530b | ||
|
37afdd1a65 | ||
|
c7ca2f41f5 | ||
|
eaf2df99c6 | ||
|
86c3990c25 | ||
|
d2c89213ff | ||
|
9548ea61e5 | ||
|
ba215e94f6 | ||
|
e05cf4bf97 | ||
|
4d877567dd | ||
|
96c88d1a5e | ||
|
27aaf9df85 | ||
|
5a230f418d | ||
|
a47b76afcc | ||
|
f73b470ec0 | ||
|
812b2fff04 | ||
|
a9d9a5095b | ||
|
4bec182b32 | ||
|
28442aa922 | ||
|
660f6981c6 | ||
|
c16cf9cf8a | ||
|
60af1a4cce | ||
|
cc94a93b56 | ||
|
bd1850df25 | ||
|
c62f9839a2 | ||
|
ab02dba96f | ||
|
75563f6c7b | ||
|
58a70d76a3 | ||
|
dccbddad80 | ||
|
e3b137066d | ||
|
2fda90e414 | ||
|
eac4a6df68 | ||
|
fb0e71946f | ||
|
4bc3a1195f | ||
|
65f8f43665 | ||
|
c631a3e0e4 | ||
|
45e09664b8 | ||
|
93789ca5e5 | ||
|
545c97f903 | ||
|
22f6db2e4f | ||
|
551c24da57 | ||
|
9f1f7aff2b | ||
|
29bf1e2529 | ||
|
d60dac9749 | ||
|
fb95fa68a2 | ||
|
bc800a8d5a | ||
|
6a0c45fa2e | ||
|
604ca9316c | ||
|
17b4563a6f | ||
|
bf8ac87a78 | ||
|
13e631dcf7 | ||
|
ed61d249e0 | ||
|
24de5506c2 | ||
|
af8970ad7a | ||
|
9cc456937c | ||
|
aaae5b3ba6 | ||
|
fde5b77f6e | ||
|
41cc596559 | ||
|
1b7bc8b284 | ||
|
40a49081b9 | ||
|
cc74693176 | ||
|
f7da18a4a0 | ||
|
aab70b3aee | ||
|
4cce058e07 | ||
|
5b1d9739ce | ||
|
ee9ccaf414 | ||
|
220aa6ada0 | ||
|
94a5aee484 | ||
|
5cef4c0a4c | ||
|
89c42ebcbe | ||
|
a71f05f86c | ||
|
fa51e5b704 | ||
|
350207b05f | ||
|
7376efe8ea | ||
|
e3570a060a | ||
|
bd86459a94 | ||
|
c0638439be | ||
|
18d69c8d2b | ||
|
75658e2a96 | ||
|
331b953551 | ||
|
9d477d45c7 | ||
|
8c376f58cb | ||
|
a6a8a712e5 | ||
|
3fab5a3b14 | ||
|
322304f1d0 | ||
|
021c1f4f24 | ||
|
4e739bf7c4 | ||
|
1bac1bd58e | ||
|
2839266543 | ||
|
c1b543c74d | ||
|
3ab75fc8de | ||
|
c924b1250a | ||
|
e2de4fbfe7 | ||
|
aa49802119 | ||
|
bee586c6fd | ||
|
85e8bece2e | ||
|
db481e1799 | ||
|
115b488807 | ||
|
3993cd765c | ||
|
d9c259a231 | ||
|
071e97053f | ||
|
aea8f0df16 | ||
|
66b44b48a4 | ||
|
8b1cde83c1 | ||
|
1cf6c97779 | ||
|
1192e760a4 | ||
|
8b90084ebc | ||
|
f366e0f890 | ||
|
52045c761c | ||
|
fc21af4e6e | ||
|
0562426661 | ||
|
1c10677f82 | ||
|
2e56c59bcb | ||
|
7569f282c6 | ||
|
9666f4a8be | ||
|
fd0f5e4d12 | ||
|
714a344937 | ||
|
2b111cd631 | ||
|
1240217a73 | ||
|
31ed4c18f9 | ||
|
92e43df266 | ||
|
dec21524e1 | ||
|
8cb0b1ce9a | ||
|
b0486f0aaa | ||
|
ba5faa2582 | ||
|
e42316ba5a | ||
|
a300e2d2dc | ||
|
1ee6707bdd | ||
|
091929da12 | ||
|
90689585ef | ||
|
477cb539d0 | ||
|
88bf3c7063 | ||
|
d6011ba14d | ||
|
6d5bbca630 | ||
|
ce4f7601af | ||
|
d8cbb2a952 | ||
|
8dd62854fa | ||
|
a7f2fff219 | ||
|
0cf886302d | ||
|
fc8130955a | ||
|
4d00995d2d | ||
|
ae95540387 | ||
|
0e587488b5 | ||
|
aca1d577a4 | ||
|
7803e1be3d | ||
|
d137acf74d | ||
|
7be533a770 | ||
|
95bbb70c91 | ||
|
252b0c42d5 | ||
|
e3915e4b7a | ||
|
4abf7a7f88 | ||
|
38b02bbcc0 | ||
|
9977396d8f | ||
|
7d34a7acac | ||
|
373f200ab8 | ||
|
0eb488580d | ||
|
41fb98c771 | ||
|
a2d251ce1e | ||
|
e7777281d6 | ||
|
cca3dbc76d | ||
|
7ba57e7a7c | ||
|
f8db314134 | ||
|
fe7543c31a | ||
|
7f20c6149e | ||
|
d343713f61 | ||
|
b448472037 | ||
|
60850d71ce | ||
|
dcf44d2523 | ||
|
650882217c |
19
.buildkite/coverage-in-disk.sh
Normal file
19
.buildkite/coverage-in-disk.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script is used to upload the full buildkite pipeline. The steps defined
|
||||
# in the buildkite UI should simply be:
|
||||
#
|
||||
# steps:
|
||||
# - command: ".buildkite/pipeline-upload.sh"
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
source ci/_
|
||||
sudo chmod 0777 ci/buildkite-pipeline-in-disk.sh
|
||||
|
||||
_ ci/buildkite-pipeline-in-disk.sh pipeline.yml
|
||||
echo +++ pipeline
|
||||
cat pipeline.yml
|
||||
|
||||
_ buildkite-agent pipeline upload pipeline.yml
|
@@ -12,7 +12,14 @@ export PS4="++"
|
||||
# Restore target/ from the previous CI build on this machine
|
||||
#
|
||||
eval "$(ci/channel-info.sh)"
|
||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"
|
||||
eval "$(ci/sbf-tools-info.sh)"
|
||||
source "ci/rust-version.sh"
|
||||
HOST_RUST_VERSION="$rust_stable"
|
||||
pattern='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
if [[ ${HOST_RUST_VERSION} =~ ${pattern} ]]; then
|
||||
HOST_RUST_VERSION="${rust_stable%.*}"
|
||||
fi
|
||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"-"$SBF_TOOLS_VERSION"-"$HOST_RUST_VERSION"
|
||||
(
|
||||
set -x
|
||||
MAX_CACHE_SIZE=18 # gigabytes
|
||||
|
66
.github/workflows/client-targets.yml
vendored
Normal file
66
.github/workflows/client-targets.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: client_targets
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "client/**"
|
||||
- "sdk/**"
|
||||
- ".github/workflows/client-targets.yml"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
check_compilation:
|
||||
name: Client compilation
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64-apple-ios, x86_64-apple-ios, aarch64-apple-darwin, x86_64-apple-darwin, aarch64-linux-android, armv7-linux-androideabi, i686-linux-android, x86_64-linux-android]
|
||||
include:
|
||||
- target: aarch64-apple-ios
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-ios
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: aarch64-apple-darwin
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-darwin
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: aarch64-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: i686-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
- name: Install cargo-ndk
|
||||
if: ${{ matrix.platform == 'android' }}
|
||||
run: cargo install cargo-ndk
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.platform == 'android' }}
|
||||
with:
|
||||
command: ndk
|
||||
args: --target ${{ matrix.target }} build -p solana-client
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.platform == 'ios' }}
|
||||
with:
|
||||
command: build
|
||||
args: -p solana-client --target ${{ matrix.target }}
|
30
.github/workflows/explorer_preview.yml
vendored
30
.github/workflows/explorer_preview.yml
vendored
@@ -20,3 +20,33 @@ jobs:
|
||||
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
|
||||
working-directory: ./explorer
|
||||
scope: ${{ secrets.TEAM_ID }}
|
||||
|
||||
- name: vercel url
|
||||
run : |
|
||||
touch vercelfile.txt
|
||||
vercel --token ${{secrets.VERCEL_TOKEN}} ls explorer --scope team_8A2WD7p4uR7tmKX9M68loHXI > vercelfile.txt
|
||||
touch vercelfile1.txt
|
||||
head -n 2 vercelfile.txt > vercelfile1.txt
|
||||
touch vercelfile2.txt
|
||||
tail -n 1 vercelfile1.txt > vercelfile2.txt
|
||||
filtered_url7=$(cut -f7 -d" " vercelfile2.txt)
|
||||
echo "filtered_url7 is: $filtered_url7"
|
||||
touch .env.preview1
|
||||
echo "$filtered_url7" > .env.preview1
|
||||
#filtered_url=$(cat vercelfile2.txt )
|
||||
#echo "$filtered_url" >> .env.preview1
|
||||
|
||||
|
||||
- name: Run tests
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: tests2
|
||||
with:
|
||||
run: |
|
||||
echo "$(cat .env.preview1)"
|
||||
|
||||
- name: Slack Notification1
|
||||
uses: rtCamp/action-slack-notify@master
|
||||
env:
|
||||
SLACK_MESSAGE: ${{ steps.tests2.outputs.stdout }}
|
||||
SLACK_TITLE: Vercel "Explorer" Preview Deployment Link
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
|
19
.github/workflows/web3.yml
vendored
19
.github/workflows/web3.yml
vendored
@@ -65,20 +65,7 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web3.js/package-lock.json
|
||||
- run: npm i -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: |
|
||||
npm run build
|
||||
ls -l lib
|
||||
test -r lib/index.iife.js
|
||||
test -r lib/index.cjs.js
|
||||
test -r lib/index.esm.js
|
||||
- run: npm run doc
|
||||
- run: npm run codecov
|
||||
- run: |
|
||||
sh -c "$(curl -sSfL https://release.solana.com/edge/install)"
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
|
||||
solana --version
|
||||
- run: npm run test:live-with-test-validator
|
||||
source .travis/before_install.sh
|
||||
npm install
|
||||
source .travis/script.sh
|
||||
|
@@ -113,3 +113,10 @@ pull_request_rules:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.9
|
||||
|
||||
commands_restrictions:
|
||||
# The author of copied PRs is the Mergify user.
|
||||
# Restrict `copy` access to Core Contributors
|
||||
copy:
|
||||
conditions:
|
||||
- author=@core-contributors
|
||||
|
19
.travis.yml
19
.travis.yml
@@ -78,7 +78,24 @@ jobs:
|
||||
# - sudo apt-get install libssl-dev libudev-dev
|
||||
|
||||
# docs pull request
|
||||
|
||||
# - name: "explorer"
|
||||
# if: type = pull_request AND branch = master
|
||||
|
||||
# language: node_js
|
||||
# node_js:
|
||||
# - "lts/*"
|
||||
|
||||
# cache:
|
||||
# directories:
|
||||
# - ~/.npm
|
||||
|
||||
# before_install:
|
||||
# - .travis/affects.sh explorer/ .travis || travis_terminate 0
|
||||
# - cd explorer
|
||||
|
||||
# script:
|
||||
# - npm run build
|
||||
# - npm run format
|
||||
- name: "docs"
|
||||
if: type IN (push, pull_request) OR tag IS present
|
||||
language: node_js
|
||||
|
1052
Cargo.lock
generated
1052
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@
|
||||
members = [
|
||||
"accountsdb-plugin-interface",
|
||||
"accountsdb-plugin-manager",
|
||||
"accountsdb-plugin-postgres",
|
||||
"accounts-cluster-bench",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
@@ -12,6 +11,7 @@ members = [
|
||||
"banks-interface",
|
||||
"banks-server",
|
||||
"bucket_map",
|
||||
"bloom",
|
||||
"clap-utils",
|
||||
"cli-config",
|
||||
"cli-output",
|
||||
@@ -48,6 +48,7 @@ members = [
|
||||
"program-test",
|
||||
"programs/address-lookup-table",
|
||||
"programs/address-lookup-table-tests",
|
||||
"programs/ed25519-tests",
|
||||
"programs/bpf_loader",
|
||||
"programs/bpf_loader/gen-syscall-list",
|
||||
"programs/compute-budget",
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2020 Solana Foundation.
|
||||
Copyright 2022 Solana Foundation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@@ -16,15 +16,15 @@ bs58 = "0.4.0"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.78"
|
||||
solana-config-program = { path = "../programs/config", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
spl-token = { version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.9.2"
|
||||
zstd = "0.10.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -136,16 +136,13 @@ impl UiAccount {
|
||||
UiAccountData::Binary(blob, encoding) => match encoding {
|
||||
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
|
||||
UiAccountEncoding::Base64Zstd => base64::decode(blob)
|
||||
.ok()
|
||||
.map(|zstd_data| {
|
||||
let mut data = vec![];
|
||||
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
||||
.and_then(|mut reader| reader.read_to_end(&mut data))
|
||||
.map(|_| data)
|
||||
.ok()
|
||||
})
|
||||
.flatten(),
|
||||
UiAccountEncoding::Base64Zstd => base64::decode(blob).ok().and_then(|zstd_data| {
|
||||
let mut data = vec![];
|
||||
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
||||
.and_then(|mut reader| reader.read_to_end(&mut data))
|
||||
.map(|_| data)
|
||||
.ok()
|
||||
}),
|
||||
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
|
||||
},
|
||||
}?;
|
||||
|
@@ -5,7 +5,7 @@ use {
|
||||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
parse_sysvar::parse_sysvar,
|
||||
parse_token::{parse_token, spl_token_id},
|
||||
parse_token::{parse_token, spl_token_ids},
|
||||
parse_vote::parse_vote,
|
||||
},
|
||||
inflector::Inflector,
|
||||
@@ -21,7 +21,6 @@ lazy_static! {
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id();
|
||||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
@@ -31,7 +30,9 @@ lazy_static! {
|
||||
);
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
for spl_token_id in spl_token_ids() {
|
||||
m.insert(spl_token_id, ParsableAccount::SplToken);
|
||||
}
|
||||
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
|
||||
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
|
||||
|
@@ -15,16 +15,31 @@ use {
|
||||
|
||||
// A helper function to convert spl_token::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_id() -> Pubkey {
|
||||
fn spl_token_id() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token::id().to_bytes())
|
||||
}
|
||||
|
||||
// Returns all known SPL Token program ids
|
||||
pub fn spl_token_ids() -> Vec<Pubkey> {
|
||||
vec![spl_token_id()]
|
||||
}
|
||||
|
||||
// Check if the provided program id as a known SPL Token program id
|
||||
pub fn is_known_spl_token_id(program_id: &Pubkey) -> bool {
|
||||
*program_id == spl_token_id()
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_native_mint() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token::native_mint::id().to_bytes())
|
||||
}
|
||||
|
||||
// The program id of the `spl_token_native_mint` account
|
||||
pub fn spl_token_native_mint_program_id() -> Pubkey {
|
||||
spl_token_id()
|
||||
}
|
||||
|
||||
// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_pubkey(pubkey: &Pubkey) -> SplTokenPubkey {
|
||||
SplTokenPubkey::new_from_array(pubkey.to_bytes())
|
||||
|
@@ -116,7 +116,7 @@ fn make_create_message(
|
||||
|
||||
let instructions: Vec<_> = (0..num_instructions)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
.flat_map(|_| {
|
||||
let program_id = if mint.is_some() {
|
||||
inline_spl_token::id()
|
||||
} else {
|
||||
@@ -148,7 +148,6 @@ fn make_create_message(
|
||||
|
||||
instructions
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||
@@ -674,7 +673,7 @@ pub mod test {
|
||||
#[test]
|
||||
fn test_accounts_cluster_bench() {
|
||||
solana_logger::setup();
|
||||
let validator_config = ValidatorConfig::default();
|
||||
let validator_config = ValidatorConfig::default_for_test();
|
||||
let num_nodes = 1;
|
||||
let mut config = ClusterConfig {
|
||||
cluster_lamports: 10_000_000,
|
||||
|
@@ -12,11 +12,10 @@ documentation = "https://docs.rs/solana-validator"
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
crossbeam-channel = "0.5"
|
||||
libloading = "0.7.2"
|
||||
json5 = "0.4.1"
|
||||
libloading = "0.7.3"
|
||||
log = "0.4.11"
|
||||
serde = "1.0.133"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.78"
|
||||
solana-accountsdb-plugin-interface = { path = "../accountsdb-plugin-interface", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
|
@@ -9,7 +9,6 @@ use {
|
||||
},
|
||||
crossbeam_channel::Receiver,
|
||||
log::*,
|
||||
serde_json,
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::BankNotification,
|
||||
transaction_notifier_interface::TransactionNotifierLock,
|
||||
@@ -156,12 +155,12 @@ impl AccountsDbPluginService {
|
||||
)));
|
||||
}
|
||||
|
||||
let result: serde_json::Value = match serde_json::from_str(&contents) {
|
||||
let result: serde_json::Value = match json5::from_str(&contents) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Err(AccountsdbPluginServiceError::InvalidConfigFileFormat(
|
||||
format!(
|
||||
"The config file {:?} is not in a valid Json format, error: {:?}",
|
||||
"The config file {:?} is not in a valid Json5 format, error: {:?}",
|
||||
accountsdb_plugin_config_file, err
|
||||
),
|
||||
));
|
||||
@@ -171,13 +170,24 @@ impl AccountsDbPluginService {
|
||||
let libpath = result["libpath"]
|
||||
.as_str()
|
||||
.ok_or(AccountsdbPluginServiceError::LibPathNotSet)?;
|
||||
let mut libpath = PathBuf::from(libpath);
|
||||
if libpath.is_relative() {
|
||||
let config_dir = accountsdb_plugin_config_file.parent().ok_or_else(|| {
|
||||
AccountsdbPluginServiceError::CannotOpenConfigFile(format!(
|
||||
"Failed to resolve parent of {:?}",
|
||||
accountsdb_plugin_config_file,
|
||||
))
|
||||
})?;
|
||||
libpath = config_dir.join(libpath);
|
||||
}
|
||||
|
||||
let config_file = accountsdb_plugin_config_file
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or(AccountsdbPluginServiceError::InvalidPluginPath)?;
|
||||
|
||||
unsafe {
|
||||
let result = plugin_manager.load_plugin(libpath, config_file);
|
||||
let result = plugin_manager.load_plugin(libpath.to_str().unwrap(), config_file);
|
||||
if let Err(err) = result {
|
||||
let msg = format!(
|
||||
"Failed to load the plugin library: {:?}, error: {:?}",
|
||||
|
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accountsdb-plugin-postgres"
|
||||
description = "The Solana AccountsDb plugin for PostgreSQL database."
|
||||
version = "1.10.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-validator"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
crossbeam-channel = "0.5"
|
||||
log = "0.4.14"
|
||||
postgres = { version = "0.19.2", features = ["with-chrono-0_4"] }
|
||||
postgres-types = { version = "0.2.2", features = ["derive"] }
|
||||
serde = "1.0.133"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-accountsdb-plugin-interface = { path = "../accountsdb-plugin-interface", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
thiserror = "1.0.30"
|
||||
tokio-postgres = "0.7.4"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@@ -1,5 +0,0 @@
|
||||
This is an example implementing the AccountsDb plugin for PostgreSQL database.
|
||||
Please see the `src/accountsdb_plugin_postgres.rs` for the format of the plugin's configuration file.
|
||||
|
||||
To create the schema objects for the database, please use `scripts/create_schema.sql`.
|
||||
`scripts/drop_schema.sql` can be used to tear down the schema objects.
|
@@ -1,201 +0,0 @@
|
||||
/**
|
||||
* This plugin implementation for PostgreSQL requires the following tables
|
||||
*/
|
||||
-- The table storing accounts
|
||||
|
||||
|
||||
CREATE TABLE account (
|
||||
pubkey BYTEA PRIMARY KEY,
|
||||
owner BYTEA,
|
||||
lamports BIGINT NOT NULL,
|
||||
slot BIGINT NOT NULL,
|
||||
executable BOOL NOT NULL,
|
||||
rent_epoch BIGINT NOT NULL,
|
||||
data BYTEA,
|
||||
write_version BIGINT NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- The table storing slot information
|
||||
CREATE TABLE slot (
|
||||
slot BIGINT PRIMARY KEY,
|
||||
parent BIGINT,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Types for Transactions
|
||||
|
||||
Create TYPE "TransactionErrorCode" AS ENUM (
|
||||
'AccountInUse',
|
||||
'AccountLoadedTwice',
|
||||
'AccountNotFound',
|
||||
'ProgramAccountNotFound',
|
||||
'InsufficientFundsForFee',
|
||||
'InvalidAccountForFee',
|
||||
'AlreadyProcessed',
|
||||
'BlockhashNotFound',
|
||||
'InstructionError',
|
||||
'CallChainTooDeep',
|
||||
'MissingSignatureForFee',
|
||||
'InvalidAccountIndex',
|
||||
'SignatureFailure',
|
||||
'InvalidProgramForExecution',
|
||||
'SanitizeFailure',
|
||||
'ClusterMaintenance',
|
||||
'AccountBorrowOutstanding',
|
||||
'WouldExceedMaxAccountCostLimit',
|
||||
'WouldExceedMaxBlockCostLimit',
|
||||
'UnsupportedVersion',
|
||||
'InvalidWritableAccount',
|
||||
'WouldExceedMaxAccountDataCostLimit',
|
||||
'TooManyAccountLocks',
|
||||
'AddressLookupTableNotFound',
|
||||
'InvalidAddressLookupTableOwner',
|
||||
'InvalidAddressLookupTableData',
|
||||
'InvalidAddressLookupTableIndex',
|
||||
'InvalidRentPayingAccount'
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionError" AS (
|
||||
error_code "TransactionErrorCode",
|
||||
error_detail VARCHAR(256)
|
||||
);
|
||||
|
||||
CREATE TYPE "CompiledInstruction" AS (
|
||||
program_id_index SMALLINT,
|
||||
accounts SMALLINT[],
|
||||
data BYTEA
|
||||
);
|
||||
|
||||
CREATE TYPE "InnerInstructions" AS (
|
||||
index SMALLINT,
|
||||
instructions "CompiledInstruction"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionTokenBalance" AS (
|
||||
account_index SMALLINT,
|
||||
mint VARCHAR(44),
|
||||
ui_token_amount DOUBLE PRECISION,
|
||||
owner VARCHAR(44)
|
||||
);
|
||||
|
||||
Create TYPE "RewardType" AS ENUM (
|
||||
'Fee',
|
||||
'Rent',
|
||||
'Staking',
|
||||
'Voting'
|
||||
);
|
||||
|
||||
CREATE TYPE "Reward" AS (
|
||||
pubkey VARCHAR(44),
|
||||
lamports BIGINT,
|
||||
post_balance BIGINT,
|
||||
reward_type "RewardType",
|
||||
commission SMALLINT
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionStatusMeta" AS (
|
||||
error "TransactionError",
|
||||
fee BIGINT,
|
||||
pre_balances BIGINT[],
|
||||
post_balances BIGINT[],
|
||||
inner_instructions "InnerInstructions"[],
|
||||
log_messages TEXT[],
|
||||
pre_token_balances "TransactionTokenBalance"[],
|
||||
post_token_balances "TransactionTokenBalance"[],
|
||||
rewards "Reward"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageHeader" AS (
|
||||
num_required_signatures SMALLINT,
|
||||
num_readonly_signed_accounts SMALLINT,
|
||||
num_readonly_unsigned_accounts SMALLINT
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessage" AS (
|
||||
header "TransactionMessageHeader",
|
||||
account_keys BYTEA[],
|
||||
recent_blockhash BYTEA,
|
||||
instructions "CompiledInstruction"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageAddressTableLookup" AS (
|
||||
account_key BYTEA,
|
||||
writable_indexes SMALLINT[],
|
||||
readonly_indexes SMALLINT[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageV0" AS (
|
||||
header "TransactionMessageHeader",
|
||||
account_keys BYTEA[],
|
||||
recent_blockhash BYTEA,
|
||||
instructions "CompiledInstruction"[],
|
||||
address_table_lookups "TransactionMessageAddressTableLookup"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "LoadedAddresses" AS (
|
||||
writable BYTEA[],
|
||||
readonly BYTEA[]
|
||||
);
|
||||
|
||||
CREATE TYPE "LoadedMessageV0" AS (
|
||||
message "TransactionMessageV0",
|
||||
loaded_addresses "LoadedAddresses"
|
||||
);
|
||||
|
||||
-- The table storing transactions
|
||||
CREATE TABLE transaction (
|
||||
slot BIGINT NOT NULL,
|
||||
signature BYTEA NOT NULL,
|
||||
is_vote BOOL NOT NULL,
|
||||
message_type SMALLINT, -- 0: legacy, 1: v0 message
|
||||
legacy_message "TransactionMessage",
|
||||
v0_loaded_message "LoadedMessageV0",
|
||||
signatures BYTEA[],
|
||||
message_hash BYTEA,
|
||||
meta "TransactionStatusMeta",
|
||||
updated_on TIMESTAMP NOT NULL,
|
||||
CONSTRAINT transaction_pk PRIMARY KEY (slot, signature)
|
||||
);
|
||||
|
||||
-- The table storing block metadata
|
||||
CREATE TABLE block (
|
||||
slot BIGINT PRIMARY KEY,
|
||||
blockhash VARCHAR(44),
|
||||
rewards "Reward"[],
|
||||
block_time BIGINT,
|
||||
block_height BIGINT,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
/**
|
||||
* The following is for keeping historical data for accounts and is not required for plugin to work.
|
||||
*/
|
||||
-- The table storing historical data for accounts
|
||||
CREATE TABLE account_audit (
|
||||
pubkey BYTEA,
|
||||
owner BYTEA,
|
||||
lamports BIGINT NOT NULL,
|
||||
slot BIGINT NOT NULL,
|
||||
executable BOOL NOT NULL,
|
||||
rent_epoch BIGINT NOT NULL,
|
||||
data BYTEA,
|
||||
write_version BIGINT NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX account_audit_account_key ON account_audit (pubkey, write_version);
|
||||
|
||||
CREATE FUNCTION audit_account_update() RETURNS trigger AS $audit_account_update$
|
||||
BEGIN
|
||||
INSERT INTO account_audit (pubkey, owner, lamports, slot, executable, rent_epoch, data, write_version, updated_on)
|
||||
VALUES (OLD.pubkey, OLD.owner, OLD.lamports, OLD.slot,
|
||||
OLD.executable, OLD.rent_epoch, OLD.data, OLD.write_version, OLD.updated_on);
|
||||
RETURN NEW;
|
||||
END;
|
||||
|
||||
$audit_account_update$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER account_update_trigger AFTER UPDATE OR DELETE ON account
|
||||
FOR EACH ROW EXECUTE PROCEDURE audit_account_update();
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Script for cleaning up the schema for PostgreSQL used for the AccountsDb plugin.
|
||||
*/
|
||||
|
||||
DROP TRIGGER account_update_trigger ON account;
|
||||
DROP FUNCTION audit_account_update;
|
||||
DROP TABLE account_audit;
|
||||
DROP TABLE account;
|
||||
DROP TABLE slot;
|
||||
DROP TABLE transaction;
|
||||
DROP TABLE block;
|
||||
|
||||
DROP TYPE "TransactionError" CASCADE;
|
||||
DROP TYPE "TransactionErrorCode" CASCADE;
|
||||
DROP TYPE "LoadedMessageV0" CASCADE;
|
||||
DROP TYPE "LoadedAddresses" CASCADE;
|
||||
DROP TYPE "TransactionMessageV0" CASCADE;
|
||||
DROP TYPE "TransactionMessage" CASCADE;
|
||||
DROP TYPE "TransactionMessageHeader" CASCADE;
|
||||
DROP TYPE "TransactionMessageAddressTableLookup" CASCADE;
|
||||
DROP TYPE "TransactionStatusMeta" CASCADE;
|
||||
DROP TYPE "RewardType" CASCADE;
|
||||
DROP TYPE "Reward" CASCADE;
|
||||
DROP TYPE "TransactionTokenBalance" CASCADE;
|
||||
DROP TYPE "InnerInstructions" CASCADE;
|
||||
DROP TYPE "CompiledInstruction" CASCADE;
|
@@ -1,802 +0,0 @@
|
||||
# This a reference configuration file for the PostgreSQL database version 14.
|
||||
|
||||
# -----------------------------
|
||||
# PostgreSQL configuration file
|
||||
# -----------------------------
|
||||
#
|
||||
# This file consists of lines of the form:
|
||||
#
|
||||
# name = value
|
||||
#
|
||||
# (The "=" is optional.) Whitespace may be used. Comments are introduced with
|
||||
# "#" anywhere on a line. The complete list of parameter names and allowed
|
||||
# values can be found in the PostgreSQL documentation.
|
||||
#
|
||||
# The commented-out settings shown in this file represent the default values.
|
||||
# Re-commenting a setting is NOT sufficient to revert it to the default value;
|
||||
# you need to reload the server.
|
||||
#
|
||||
# This file is read on server startup and when the server receives a SIGHUP
|
||||
# signal. If you edit the file on a running system, you have to SIGHUP the
|
||||
# server for the changes to take effect, run "pg_ctl reload", or execute
|
||||
# "SELECT pg_reload_conf()". Some parameters, which are marked below,
|
||||
# require a server shutdown and restart to take effect.
|
||||
#
|
||||
# Any parameter can also be given as a command-line option to the server, e.g.,
|
||||
# "postgres -c log_connections=on". Some parameters can be changed at run time
|
||||
# with the "SET" SQL command.
|
||||
#
|
||||
# Memory units: B = bytes Time units: us = microseconds
|
||||
# kB = kilobytes ms = milliseconds
|
||||
# MB = megabytes s = seconds
|
||||
# GB = gigabytes min = minutes
|
||||
# TB = terabytes h = hours
|
||||
# d = days
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# FILE LOCATIONS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# The default values of these variables are driven from the -D command-line
|
||||
# option or PGDATA environment variable, represented here as ConfigDir.
|
||||
|
||||
data_directory = '/var/lib/postgresql/14/main' # use data in another directory
|
||||
# (change requires restart)
|
||||
|
||||
hba_file = '/etc/postgresql/14/main/pg_hba.conf' # host-based authentication file
|
||||
# (change requires restart)
|
||||
ident_file = '/etc/postgresql/14/main/pg_ident.conf' # ident configuration file
|
||||
# (change requires restart)
|
||||
|
||||
# If external_pid_file is not explicitly set, no extra PID file is written.
|
||||
external_pid_file = '/var/run/postgresql/14-main.pid' # write an extra PID file
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONNECTIONS AND AUTHENTICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Connection Settings -
|
||||
|
||||
#listen_addresses = 'localhost' # what IP address(es) to listen on;
|
||||
# comma-separated list of addresses;
|
||||
# defaults to 'localhost'; use '*' for all
|
||||
# (change requires restart)
|
||||
listen_addresses = '*'
|
||||
port = 5433 # (change requires restart)
|
||||
max_connections = 200 # (change requires restart)
|
||||
#superuser_reserved_connections = 3 # (change requires restart)
|
||||
unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories
|
||||
# (change requires restart)
|
||||
#unix_socket_group = '' # (change requires restart)
|
||||
#unix_socket_permissions = 0777 # begin with 0 to use octal notation
|
||||
# (change requires restart)
|
||||
#bonjour = off # advertise server via Bonjour
|
||||
# (change requires restart)
|
||||
#bonjour_name = '' # defaults to the computer name
|
||||
# (change requires restart)
|
||||
|
||||
# - TCP settings -
|
||||
# see "man tcp" for details
|
||||
|
||||
#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
|
||||
# 0 selects the system default
|
||||
#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
|
||||
# 0 selects the system default
|
||||
#tcp_keepalives_count = 0 # TCP_KEEPCNT;
|
||||
# 0 selects the system default
|
||||
#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds;
|
||||
# 0 selects the system default
|
||||
|
||||
#client_connection_check_interval = 0 # time between checks for client
|
||||
# disconnection while running queries;
|
||||
# 0 for never
|
||||
|
||||
# - Authentication -
|
||||
|
||||
#authentication_timeout = 1min # 1s-600s
|
||||
#password_encryption = scram-sha-256 # scram-sha-256 or md5
|
||||
#db_user_namespace = off
|
||||
|
||||
# GSSAPI using Kerberos
|
||||
#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab'
|
||||
#krb_caseins_users = off
|
||||
|
||||
# - SSL -
|
||||
|
||||
ssl = on
|
||||
#ssl_ca_file = ''
|
||||
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
|
||||
#ssl_crl_file = ''
|
||||
#ssl_crl_dir = ''
|
||||
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
|
||||
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
|
||||
#ssl_prefer_server_ciphers = on
|
||||
#ssl_ecdh_curve = 'prime256v1'
|
||||
#ssl_min_protocol_version = 'TLSv1.2'
|
||||
#ssl_max_protocol_version = ''
|
||||
#ssl_dh_params_file = ''
|
||||
#ssl_passphrase_command = ''
|
||||
#ssl_passphrase_command_supports_reload = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# RESOURCE USAGE (except WAL)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Memory -
|
||||
|
||||
shared_buffers = 1GB # min 128kB
|
||||
# (change requires restart)
|
||||
#huge_pages = try # on, off, or try
|
||||
# (change requires restart)
|
||||
#huge_page_size = 0 # zero for system default
|
||||
# (change requires restart)
|
||||
#temp_buffers = 8MB # min 800kB
|
||||
#max_prepared_transactions = 0 # zero disables the feature
|
||||
# (change requires restart)
|
||||
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
|
||||
# you actively intend to use prepared transactions.
|
||||
#work_mem = 4MB # min 64kB
|
||||
#hash_mem_multiplier = 1.0 # 1-1000.0 multiplier on hash table work_mem
|
||||
#maintenance_work_mem = 64MB # min 1MB
|
||||
#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
|
||||
#logical_decoding_work_mem = 64MB # min 64kB
|
||||
#max_stack_depth = 2MB # min 100kB
|
||||
#shared_memory_type = mmap # the default is the first option
|
||||
# supported by the operating system:
|
||||
# mmap
|
||||
# sysv
|
||||
# windows
|
||||
# (change requires restart)
|
||||
dynamic_shared_memory_type = posix # the default is the first option
|
||||
# supported by the operating system:
|
||||
# posix
|
||||
# sysv
|
||||
# windows
|
||||
# mmap
|
||||
# (change requires restart)
|
||||
#min_dynamic_shared_memory = 0MB # (change requires restart)
|
||||
|
||||
# - Disk -
|
||||
|
||||
#temp_file_limit = -1 # limits per-process temp file space
|
||||
# in kilobytes, or -1 for no limit
|
||||
|
||||
# - Kernel Resources -
|
||||
|
||||
#max_files_per_process = 1000 # min 64
|
||||
# (change requires restart)
|
||||
|
||||
# - Cost-Based Vacuum Delay -
|
||||
|
||||
#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
|
||||
#vacuum_cost_page_hit = 1 # 0-10000 credits
|
||||
#vacuum_cost_page_miss = 2 # 0-10000 credits
|
||||
#vacuum_cost_page_dirty = 20 # 0-10000 credits
|
||||
#vacuum_cost_limit = 200 # 1-10000 credits
|
||||
|
||||
# - Background Writer -
|
||||
|
||||
#bgwriter_delay = 200ms # 10-10000ms between rounds
|
||||
#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
|
||||
#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
|
||||
#bgwriter_flush_after = 512kB # measured in pages, 0 disables
|
||||
|
||||
# - Asynchronous Behavior -
|
||||
|
||||
#backend_flush_after = 0 # measured in pages, 0 disables
|
||||
effective_io_concurrency = 1000 # 1-1000; 0 disables prefetching
|
||||
#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching
|
||||
#max_worker_processes = 8 # (change requires restart)
|
||||
#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers
|
||||
#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers
|
||||
#max_parallel_workers = 8 # maximum number of max_worker_processes that
|
||||
# can be used in parallel operations
|
||||
#parallel_leader_participation = on
|
||||
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# WRITE-AHEAD LOG
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Settings -
|
||||
|
||||
wal_level = minimal # minimal, replica, or logical
|
||||
# (change requires restart)
|
||||
fsync = off # flush data to disk for crash safety
|
||||
# (turning this off can cause
|
||||
# unrecoverable data corruption)
|
||||
synchronous_commit = off # synchronization level;
|
||||
# off, local, remote_write, remote_apply, or on
|
||||
#wal_sync_method = fsync # the default is the first option
|
||||
# supported by the operating system:
|
||||
# open_datasync
|
||||
# fdatasync (default on Linux and FreeBSD)
|
||||
# fsync
|
||||
# fsync_writethrough
|
||||
# open_sync
|
||||
full_page_writes = off # recover from partial page writes
|
||||
#wal_log_hints = off # also do full page writes of non-critical updates
|
||||
# (change requires restart)
|
||||
#wal_compression = off # enable compression of full-page writes
|
||||
#wal_init_zero = on # zero-fill new WAL files
|
||||
#wal_recycle = on # recycle WAL files
|
||||
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
|
||||
# (change requires restart)
|
||||
#wal_writer_delay = 200ms # 1-10000 milliseconds
|
||||
#wal_writer_flush_after = 1MB # measured in pages, 0 disables
|
||||
#wal_skip_threshold = 2MB
|
||||
|
||||
#commit_delay = 0 # range 0-100000, in microseconds
|
||||
#commit_siblings = 5 # range 1-1000
|
||||
|
||||
# - Checkpoints -
|
||||
|
||||
#checkpoint_timeout = 5min # range 30s-1d
|
||||
#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
|
||||
#checkpoint_flush_after = 256kB # measured in pages, 0 disables
|
||||
#checkpoint_warning = 30s # 0 disables
|
||||
max_wal_size = 1GB
|
||||
min_wal_size = 80MB
|
||||
|
||||
# - Archiving -
|
||||
|
||||
#archive_mode = off # enables archiving; off, on, or always
|
||||
# (change requires restart)
|
||||
#archive_command = '' # command to use to archive a logfile segment
|
||||
# placeholders: %p = path of file to archive
|
||||
# %f = file name only
|
||||
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
|
||||
#archive_timeout = 0 # force a logfile segment switch after this
|
||||
# number of seconds; 0 disables
|
||||
|
||||
# - Archive Recovery -
|
||||
|
||||
# These are only used in recovery mode.
|
||||
|
||||
#restore_command = '' # command to use to restore an archived logfile segment
|
||||
# placeholders: %p = path of file to restore
|
||||
# %f = file name only
|
||||
# e.g. 'cp /mnt/server/archivedir/%f %p'
|
||||
#archive_cleanup_command = '' # command to execute at every restartpoint
|
||||
#recovery_end_command = '' # command to execute at completion of recovery
|
||||
|
||||
# - Recovery Target -
|
||||
|
||||
# Set these only when performing a targeted recovery.
|
||||
|
||||
#recovery_target = '' # 'immediate' to end recovery as soon as a
|
||||
# consistent state is reached
|
||||
# (change requires restart)
|
||||
#recovery_target_name = '' # the named restore point to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_time = '' # the time stamp up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_inclusive = on # Specifies whether to stop:
|
||||
# just after the specified recovery target (on)
|
||||
# just before the recovery target (off)
|
||||
# (change requires restart)
|
||||
#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
|
||||
# (change requires restart)
|
||||
#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPLICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Sending Servers -
|
||||
|
||||
# Set these on the primary and on any standby that will send replication data.
|
||||
|
||||
max_wal_senders = 0 # max number of walsender processes
|
||||
# (change requires restart)
|
||||
#max_replication_slots = 10 # max number of replication slots
|
||||
# (change requires restart)
|
||||
#wal_keep_size = 0 # in megabytes; 0 disables
|
||||
#max_slot_wal_keep_size = -1 # in megabytes; -1 disables
|
||||
#wal_sender_timeout = 60s # in milliseconds; 0 disables
|
||||
#track_commit_timestamp = off # collect timestamp of transaction commit
|
||||
# (change requires restart)
|
||||
|
||||
# - Primary Server -
|
||||
|
||||
# These settings are ignored on a standby server.
|
||||
|
||||
#synchronous_standby_names = '' # standby servers that provide sync rep
|
||||
# method to choose sync standbys, number of sync standbys,
|
||||
# and comma-separated list of application_name
|
||||
# from standby(s); '*' = all
|
||||
#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed
|
||||
|
||||
# - Standby Servers -
|
||||
|
||||
# These settings are ignored on a primary server.
|
||||
|
||||
#primary_conninfo = '' # connection string to sending server
|
||||
#primary_slot_name = '' # replication slot on sending server
|
||||
#promote_trigger_file = '' # file name whose presence ends recovery
|
||||
#hot_standby = on # "off" disallows queries during recovery
|
||||
# (change requires restart)
|
||||
#max_standby_archive_delay = 30s # max delay before canceling queries
|
||||
# when reading WAL from archive;
|
||||
# -1 allows indefinite delay
|
||||
#max_standby_streaming_delay = 30s # max delay before canceling queries
|
||||
# when reading streaming WAL;
|
||||
# -1 allows indefinite delay
|
||||
#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name
|
||||
# is not set
|
||||
#wal_receiver_status_interval = 10s # send replies at least this often
|
||||
# 0 disables
|
||||
#hot_standby_feedback = off # send info from standby to prevent
|
||||
# query conflicts
|
||||
#wal_receiver_timeout = 60s # time that receiver waits for
|
||||
# communication from primary
|
||||
# in milliseconds; 0 disables
|
||||
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
|
||||
# retrieve WAL after a failed attempt
|
||||
#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
|
||||
|
||||
# - Subscribers -
|
||||
|
||||
# These settings are ignored on a publisher.
|
||||
|
||||
#max_logical_replication_workers = 4 # taken from max_worker_processes
|
||||
# (change requires restart)
|
||||
#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# QUERY TUNING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Planner Method Configuration -
|
||||
|
||||
#enable_async_append = on
|
||||
#enable_bitmapscan = on
|
||||
#enable_gathermerge = on
|
||||
#enable_hashagg = on
|
||||
#enable_hashjoin = on
|
||||
#enable_incremental_sort = on
|
||||
#enable_indexscan = on
|
||||
#enable_indexonlyscan = on
|
||||
#enable_material = on
|
||||
#enable_memoize = on
|
||||
#enable_mergejoin = on
|
||||
#enable_nestloop = on
|
||||
#enable_parallel_append = on
|
||||
#enable_parallel_hash = on
|
||||
#enable_partition_pruning = on
|
||||
#enable_partitionwise_join = off
|
||||
#enable_partitionwise_aggregate = off
|
||||
#enable_seqscan = on
|
||||
#enable_sort = on
|
||||
#enable_tidscan = on
|
||||
|
||||
# - Planner Cost Constants -
|
||||
|
||||
#seq_page_cost = 1.0 # measured on an arbitrary scale
|
||||
#random_page_cost = 4.0 # same scale as above
|
||||
#cpu_tuple_cost = 0.01 # same scale as above
|
||||
#cpu_index_tuple_cost = 0.005 # same scale as above
|
||||
#cpu_operator_cost = 0.0025 # same scale as above
|
||||
#parallel_setup_cost = 1000.0 # same scale as above
|
||||
#parallel_tuple_cost = 0.1 # same scale as above
|
||||
#min_parallel_table_scan_size = 8MB
|
||||
#min_parallel_index_scan_size = 512kB
|
||||
#effective_cache_size = 4GB
|
||||
|
||||
#jit_above_cost = 100000 # perform JIT compilation if available
|
||||
# and query more expensive than this;
|
||||
# -1 disables
|
||||
#jit_inline_above_cost = 500000 # inline small functions if query is
|
||||
# more expensive than this; -1 disables
|
||||
#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if
|
||||
# query is more expensive than this;
|
||||
# -1 disables
|
||||
|
||||
# - Genetic Query Optimizer -
|
||||
|
||||
#geqo = on
|
||||
#geqo_threshold = 12
|
||||
#geqo_effort = 5 # range 1-10
|
||||
#geqo_pool_size = 0 # selects default based on effort
|
||||
#geqo_generations = 0 # selects default based on effort
|
||||
#geqo_selection_bias = 2.0 # range 1.5-2.0
|
||||
#geqo_seed = 0.0 # range 0.0-1.0
|
||||
|
||||
# - Other Planner Options -
|
||||
|
||||
#default_statistics_target = 100 # range 1-10000
|
||||
#constraint_exclusion = partition # on, off, or partition
|
||||
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
|
||||
#from_collapse_limit = 8
|
||||
#jit = on # allow JIT compilation
|
||||
#join_collapse_limit = 8 # 1 disables collapsing of explicit
|
||||
# JOIN clauses
|
||||
#plan_cache_mode = auto # auto, force_generic_plan or
|
||||
# force_custom_plan
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPORTING AND LOGGING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Where to Log -
|
||||
|
||||
#log_destination = 'stderr' # Valid values are combinations of
|
||||
# stderr, csvlog, syslog, and eventlog,
|
||||
# depending on platform. csvlog
|
||||
# requires logging_collector to be on.
|
||||
|
||||
# This is used when logging to stderr:
|
||||
#logging_collector = off # Enable capturing of stderr and csvlog
|
||||
# into log files. Required to be on for
|
||||
# csvlogs.
|
||||
# (change requires restart)
|
||||
|
||||
# These are only used if logging_collector is on:
|
||||
#log_directory = 'log' # directory where log files are written,
|
||||
# can be absolute or relative to PGDATA
|
||||
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
|
||||
# can include strftime() escapes
|
||||
#log_file_mode = 0600 # creation mode for log files,
|
||||
# begin with 0 to use octal notation
|
||||
#log_rotation_age = 1d # Automatic rotation of logfiles will
|
||||
# happen after that time. 0 disables.
|
||||
#log_rotation_size = 10MB # Automatic rotation of logfiles will
|
||||
# happen after that much log output.
|
||||
# 0 disables.
|
||||
#log_truncate_on_rotation = off # If on, an existing log file with the
|
||||
# same name as the new log file will be
|
||||
# truncated rather than appended to.
|
||||
# But such truncation only occurs on
|
||||
# time-driven rotation, not on restarts
|
||||
# or size-driven rotation. Default is
|
||||
# off, meaning append to existing files
|
||||
# in all cases.
|
||||
|
||||
# These are relevant when logging to syslog:
|
||||
#syslog_facility = 'LOCAL0'
|
||||
#syslog_ident = 'postgres'
|
||||
#syslog_sequence_numbers = on
|
||||
#syslog_split_messages = on
|
||||
|
||||
# This is only relevant when logging to eventlog (Windows):
|
||||
# (change requires restart)
|
||||
#event_source = 'PostgreSQL'
|
||||
|
||||
# - When to Log -
|
||||
|
||||
#log_min_messages = warning # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# info
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
# log
|
||||
# fatal
|
||||
# panic
|
||||
|
||||
#log_min_error_statement = error # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# info
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
# log
|
||||
# fatal
|
||||
# panic (effectively off)
|
||||
|
||||
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
|
||||
# and their durations, > 0 logs only
|
||||
# statements running at least this number
|
||||
# of milliseconds
|
||||
|
||||
#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements
|
||||
# and their durations, > 0 logs only a sample of
|
||||
# statements running at least this number
|
||||
# of milliseconds;
|
||||
# sample fraction is determined by log_statement_sample_rate
|
||||
|
||||
#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding
|
||||
# log_min_duration_sample to be logged;
|
||||
# 1.0 logs all such statements, 0.0 never logs
|
||||
|
||||
|
||||
#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements
|
||||
# are logged regardless of their duration; 1.0 logs all
|
||||
# statements from all transactions, 0.0 never logs
|
||||
|
||||
# - What to Log -
|
||||
|
||||
#debug_print_parse = off
|
||||
#debug_print_rewritten = off
|
||||
#debug_print_plan = off
|
||||
#debug_pretty_print = on
|
||||
#log_autovacuum_min_duration = -1 # log autovacuum activity;
|
||||
# -1 disables, 0 logs all actions and
|
||||
# their durations, > 0 logs only
|
||||
# actions running at least this number
|
||||
# of milliseconds.
|
||||
#log_checkpoints = off
|
||||
#log_connections = off
|
||||
#log_disconnections = off
|
||||
#log_duration = off
|
||||
#log_error_verbosity = default # terse, default, or verbose messages
|
||||
#log_hostname = off
|
||||
log_line_prefix = '%m [%p] %q%u@%d ' # special values:
|
||||
# %a = application name
|
||||
# %u = user name
|
||||
# %d = database name
|
||||
# %r = remote host and port
|
||||
# %h = remote host
|
||||
# %b = backend type
|
||||
# %p = process ID
|
||||
# %P = process ID of parallel group leader
|
||||
# %t = timestamp without milliseconds
|
||||
# %m = timestamp with milliseconds
|
||||
# %n = timestamp with milliseconds (as a Unix epoch)
|
||||
# %Q = query ID (0 if none or not computed)
|
||||
# %i = command tag
|
||||
# %e = SQL state
|
||||
# %c = session ID
|
||||
# %l = session line number
|
||||
# %s = session start timestamp
|
||||
# %v = virtual transaction ID
|
||||
# %x = transaction ID (0 if none)
|
||||
# %q = stop here in non-session
|
||||
# processes
|
||||
# %% = '%'
|
||||
# e.g. '<%u%%%d> '
|
||||
#log_lock_waits = off # log lock waits >= deadlock_timeout
|
||||
#log_recovery_conflict_waits = off # log standby recovery conflict waits
|
||||
# >= deadlock_timeout
|
||||
#log_parameter_max_length = -1 # when logging statements, limit logged
|
||||
# bind-parameter values to N bytes;
|
||||
# -1 means print in full, 0 disables
|
||||
#log_parameter_max_length_on_error = 0 # when logging an error, limit logged
|
||||
# bind-parameter values to N bytes;
|
||||
# -1 means print in full, 0 disables
|
||||
#log_statement = 'none' # none, ddl, mod, all
|
||||
#log_replication_commands = off
|
||||
#log_temp_files = -1 # log temporary files equal or larger
|
||||
# than the specified size in kilobytes;
|
||||
# -1 disables, 0 logs all temp files
|
||||
log_timezone = 'Etc/UTC'
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# PROCESS TITLE
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
cluster_name = '14/main' # added to process titles if nonempty
|
||||
# (change requires restart)
|
||||
#update_process_title = on
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# STATISTICS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Query and Index Statistics Collector -
|
||||
|
||||
#track_activities = on
|
||||
#track_activity_query_size = 1024 # (change requires restart)
|
||||
#track_counts = on
|
||||
#track_io_timing = off
|
||||
#track_wal_io_timing = off
|
||||
#track_functions = none # none, pl, all
|
||||
stats_temp_directory = '/var/run/postgresql/14-main.pg_stat_tmp'
|
||||
|
||||
|
||||
# - Monitoring -
|
||||
|
||||
#compute_query_id = auto
|
||||
#log_statement_stats = off
|
||||
#log_parser_stats = off
|
||||
#log_planner_stats = off
|
||||
#log_executor_stats = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# AUTOVACUUM
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#autovacuum = on # Enable autovacuum subprocess? 'on'
|
||||
# requires track_counts to also be on.
|
||||
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
|
||||
# (change requires restart)
|
||||
#autovacuum_naptime = 1min # time between autovacuum runs
|
||||
#autovacuum_vacuum_threshold = 50 # min number of row updates before
|
||||
# vacuum
|
||||
#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts
|
||||
# before vacuum; -1 disables insert
|
||||
# vacuums
|
||||
#autovacuum_analyze_threshold = 50 # min number of row updates before
|
||||
# analyze
|
||||
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
|
||||
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
|
||||
# size before insert vacuum
|
||||
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
|
||||
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
|
||||
# (change requires restart)
|
||||
#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
|
||||
# before forced vacuum
|
||||
# (change requires restart)
|
||||
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
|
||||
# autovacuum, in milliseconds;
|
||||
# -1 means use vacuum_cost_delay
|
||||
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
|
||||
# autovacuum, -1 means use
|
||||
# vacuum_cost_limit
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CLIENT CONNECTION DEFAULTS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Statement Behavior -
|
||||
|
||||
#client_min_messages = notice # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# log
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
#search_path = '"$user", public' # schema names
|
||||
#row_security = on
|
||||
#default_table_access_method = 'heap'
|
||||
#default_tablespace = '' # a tablespace name, '' uses the default
|
||||
#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
|
||||
#temp_tablespaces = '' # a list of tablespace names, '' uses
|
||||
# only default tablespace
|
||||
#check_function_bodies = on
|
||||
#default_transaction_isolation = 'read committed'
|
||||
#default_transaction_read_only = off
|
||||
#default_transaction_deferrable = off
|
||||
#session_replication_role = 'origin'
|
||||
#statement_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#lock_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#idle_session_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#vacuum_freeze_table_age = 150000000
|
||||
#vacuum_freeze_min_age = 50000000
|
||||
#vacuum_failsafe_age = 1600000000
|
||||
#vacuum_multixact_freeze_table_age = 150000000
|
||||
#vacuum_multixact_freeze_min_age = 5000000
|
||||
#vacuum_multixact_failsafe_age = 1600000000
|
||||
#bytea_output = 'hex' # hex, escape
|
||||
#xmlbinary = 'base64'
|
||||
#xmloption = 'content'
|
||||
#gin_pending_list_limit = 4MB
|
||||
|
||||
# - Locale and Formatting -
|
||||
|
||||
datestyle = 'iso, mdy'
|
||||
#intervalstyle = 'postgres'
|
||||
timezone = 'Etc/UTC'
|
||||
#timezone_abbreviations = 'Default' # Select the set of available time zone
|
||||
# abbreviations. Currently, there are
|
||||
# Default
|
||||
# Australia (historical usage)
|
||||
# India
|
||||
# You can create your own file in
|
||||
# share/timezonesets/.
|
||||
#extra_float_digits = 1 # min -15, max 3; any value >0 actually
|
||||
# selects precise output mode
|
||||
#client_encoding = sql_ascii # actually, defaults to database
|
||||
# encoding
|
||||
|
||||
# These settings are initialized by initdb, but they can be changed.
|
||||
lc_messages = 'C.UTF-8' # locale for system error message
|
||||
# strings
|
||||
lc_monetary = 'C.UTF-8' # locale for monetary formatting
|
||||
lc_numeric = 'C.UTF-8' # locale for number formatting
|
||||
lc_time = 'C.UTF-8' # locale for time formatting
|
||||
|
||||
# default configuration for text search
|
||||
default_text_search_config = 'pg_catalog.english'
|
||||
|
||||
# - Shared Library Preloading -
|
||||
|
||||
#local_preload_libraries = ''
|
||||
#session_preload_libraries = ''
|
||||
#shared_preload_libraries = '' # (change requires restart)
|
||||
#jit_provider = 'llvmjit' # JIT library to use
|
||||
|
||||
# - Other Defaults -
|
||||
|
||||
#dynamic_library_path = '$libdir'
|
||||
#extension_destdir = '' # prepend path when loading extensions
|
||||
# and shared objects (added by Debian)
|
||||
#gin_fuzzy_search_limit = 0
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# LOCK MANAGEMENT
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#deadlock_timeout = 1s
|
||||
#max_locks_per_transaction = 64 # min 10
|
||||
# (change requires restart)
|
||||
#max_pred_locks_per_transaction = 64 # min 10
|
||||
# (change requires restart)
|
||||
#max_pred_locks_per_relation = -2 # negative values mean
|
||||
# (max_pred_locks_per_transaction
|
||||
# / -max_pred_locks_per_relation) - 1
|
||||
#max_pred_locks_per_page = 2 # min 0
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# VERSION AND PLATFORM COMPATIBILITY
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Previous PostgreSQL Versions -
|
||||
|
||||
#array_nulls = on
|
||||
#backslash_quote = safe_encoding # on, off, or safe_encoding
|
||||
#escape_string_warning = on
|
||||
#lo_compat_privileges = off
|
||||
#quote_all_identifiers = off
|
||||
#standard_conforming_strings = on
|
||||
#synchronize_seqscans = on
|
||||
|
||||
# - Other Platforms and Clients -
|
||||
|
||||
#transform_null_equals = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# ERROR HANDLING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#exit_on_error = off # terminate session on any error?
|
||||
#restart_after_crash = on # reinitialize after backend crash?
|
||||
#data_sync_retry = off # retry or panic on failure to fsync
|
||||
# data?
|
||||
# (change requires restart)
|
||||
#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONFIG FILE INCLUDES
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# These options allow settings to be loaded from files other than the
|
||||
# default postgresql.conf. Note that these are directives, not variable
|
||||
# assignments, so they can usefully be given more than once.
|
||||
|
||||
include_dir = 'conf.d' # include files ending in '.conf' from
|
||||
# a directory, e.g., 'conf.d'
|
||||
#include_if_exists = '...' # include file only if it exists
|
||||
#include = '...' # include file
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CUSTOMIZED OPTIONS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Add settings for extensions here
|
@@ -1,74 +0,0 @@
|
||||
use {log::*, std::collections::HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AccountsSelector {
|
||||
pub accounts: HashSet<Vec<u8>>,
|
||||
pub owners: HashSet<Vec<u8>>,
|
||||
pub select_all_accounts: bool,
|
||||
}
|
||||
|
||||
impl AccountsSelector {
|
||||
pub fn default() -> Self {
|
||||
AccountsSelector {
|
||||
accounts: HashSet::default(),
|
||||
owners: HashSet::default(),
|
||||
select_all_accounts: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(accounts: &[String], owners: &[String]) -> Self {
|
||||
info!(
|
||||
"Creating AccountsSelector from accounts: {:?}, owners: {:?}",
|
||||
accounts, owners
|
||||
);
|
||||
|
||||
let select_all_accounts = accounts.iter().any(|key| key == "*");
|
||||
if select_all_accounts {
|
||||
return AccountsSelector {
|
||||
accounts: HashSet::default(),
|
||||
owners: HashSet::default(),
|
||||
select_all_accounts,
|
||||
};
|
||||
}
|
||||
let accounts = accounts
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
let owners = owners
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
AccountsSelector {
|
||||
accounts,
|
||||
owners,
|
||||
select_all_accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_account_selected(&self, account: &[u8], owner: &[u8]) -> bool {
|
||||
self.select_all_accounts || self.accounts.contains(account) || self.owners.contains(owner)
|
||||
}
|
||||
|
||||
/// Check if any account is of interested at all
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.select_all_accounts || !self.accounts.is_empty() || !self.owners.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_accounts_selector() {
|
||||
AccountsSelector::new(
|
||||
&["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin".to_string()],
|
||||
&[],
|
||||
);
|
||||
|
||||
AccountsSelector::new(
|
||||
&[],
|
||||
&["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin".to_string()],
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,466 +0,0 @@
|
||||
use solana_measure::measure::Measure;
|
||||
/// Main entry for the PostgreSQL plugin
|
||||
use {
|
||||
crate::{
|
||||
accounts_selector::AccountsSelector,
|
||||
postgres_client::{ParallelPostgresClient, PostgresClientBuilder},
|
||||
transaction_selector::TransactionSelector,
|
||||
},
|
||||
bs58,
|
||||
log::*,
|
||||
serde_derive::{Deserialize, Serialize},
|
||||
serde_json,
|
||||
solana_accountsdb_plugin_interface::accountsdb_plugin_interface::{
|
||||
AccountsDbPlugin, AccountsDbPluginError, ReplicaAccountInfoVersions,
|
||||
ReplicaBlockInfoVersions, ReplicaTransactionInfoVersions, Result, SlotStatus,
|
||||
},
|
||||
solana_metrics::*,
|
||||
std::{fs::File, io::Read},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AccountsDbPluginPostgres {
|
||||
client: Option<ParallelPostgresClient>,
|
||||
accounts_selector: Option<AccountsSelector>,
|
||||
transaction_selector: Option<TransactionSelector>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AccountsDbPluginPostgres {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AccountsDbPluginPostgresConfig {
|
||||
pub host: Option<String>,
|
||||
pub user: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub connection_str: Option<String>,
|
||||
pub threads: Option<usize>,
|
||||
pub batch_size: Option<usize>,
|
||||
pub panic_on_db_errors: Option<bool>,
|
||||
/// Indicates if to store historical data for accounts
|
||||
pub store_account_historical_data: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AccountsDbPluginPostgresError {
|
||||
#[error("Error connecting to the backend data store. Error message: ({msg})")]
|
||||
DataStoreConnectionError { msg: String },
|
||||
|
||||
#[error("Error preparing data store schema. Error message: ({msg})")]
|
||||
DataSchemaError { msg: String },
|
||||
|
||||
#[error("Error preparing data store schema. Error message: ({msg})")]
|
||||
ConfigurationError { msg: String },
|
||||
}
|
||||
|
||||
impl AccountsDbPlugin for AccountsDbPluginPostgres {
|
||||
fn name(&self) -> &'static str {
|
||||
"AccountsDbPluginPostgres"
|
||||
}
|
||||
|
||||
/// Do initialization for the PostgreSQL plugin.
|
||||
///
|
||||
/// # Format of the config file:
|
||||
/// * The `accounts_selector` section allows the user to controls accounts selections.
|
||||
/// "accounts_selector" : {
|
||||
/// "accounts" : \["pubkey-1", "pubkey-2", ..., "pubkey-n"\],
|
||||
/// }
|
||||
/// or:
|
||||
/// "accounts_selector" = {
|
||||
/// "owners" : \["pubkey-1", "pubkey-2", ..., "pubkey-m"\]
|
||||
/// }
|
||||
/// Accounts either satisyfing the accounts condition or owners condition will be selected.
|
||||
/// When only owners is specified,
|
||||
/// all accounts belonging to the owners will be streamed.
|
||||
/// The accounts field supports wildcard to select all accounts:
|
||||
/// "accounts_selector" : {
|
||||
/// "accounts" : \["*"\],
|
||||
/// }
|
||||
/// * "host", optional, specifies the PostgreSQL server.
|
||||
/// * "user", optional, specifies the PostgreSQL user.
|
||||
/// * "port", optional, specifies the PostgreSQL server's port.
|
||||
/// * "connection_str", optional, the custom PostgreSQL connection string.
|
||||
/// Please refer to https://docs.rs/postgres/0.19.2/postgres/config/struct.Config.html for the connection configuration.
|
||||
/// When `connection_str` is set, the values in "host", "user" and "port" are ignored. If `connection_str` is not given,
|
||||
/// `host` and `user` must be given.
|
||||
/// "store_account_historical_data", optional, set it to 'true', to store historical account data to account_audit
|
||||
/// table.
|
||||
/// * "threads" optional, specifies the number of worker threads for the plugin. A thread
|
||||
/// maintains a PostgreSQL connection to the server. The default is '10'.
|
||||
/// * "batch_size" optional, specifies the batch size of bulk insert when the AccountsDb is created
|
||||
/// from restoring a snapshot. The default is '10'.
|
||||
/// * "panic_on_db_errors", optional, contols if to panic when there are errors replicating data to the
|
||||
/// PostgreSQL database. The default is 'false'.
|
||||
/// * "transaction_selector", optional, controls if and what transaction to store. If this field is missing
|
||||
/// None of the transction is stored.
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["pubkey-1", "pubkey-2", ..., "pubkey-n"\],
|
||||
/// }
|
||||
/// The `mentions` field support wildcard to select all transaction or all 'vote' transactions:
|
||||
/// For example, to select all transactions:
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["*"\],
|
||||
/// }
|
||||
/// To select all vote transactions:
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["all_votes"\],
|
||||
/// }
|
||||
/// # Examples
|
||||
///
|
||||
/// {
|
||||
/// "libpath": "/home/solana/target/release/libsolana_accountsdb_plugin_postgres.so",
|
||||
/// "host": "host_foo",
|
||||
/// "user": "solana",
|
||||
/// "threads": 10,
|
||||
/// "accounts_selector" : {
|
||||
/// "owners" : ["9oT9R5ZyRovSVnt37QvVoBttGpNqR3J7unkb567NP8k3"]
|
||||
/// }
|
||||
/// }
|
||||
|
||||
fn on_load(&mut self, config_file: &str) -> Result<()> {
|
||||
solana_logger::setup_with_default("info");
|
||||
info!(
|
||||
"Loading plugin {:?} from config_file {:?}",
|
||||
self.name(),
|
||||
config_file
|
||||
);
|
||||
let mut file = File::open(config_file)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let result: serde_json::Value = serde_json::from_str(&contents).unwrap();
|
||||
self.accounts_selector = Some(Self::create_accounts_selector_from_config(&result));
|
||||
self.transaction_selector = Some(Self::create_transaction_selector_from_config(&result));
|
||||
|
||||
let result: serde_json::Result<AccountsDbPluginPostgresConfig> =
|
||||
serde_json::from_str(&contents);
|
||||
match result {
|
||||
Err(err) => {
|
||||
return Err(AccountsDbPluginError::ConfigFileReadError {
|
||||
msg: format!(
|
||||
"The config file is not in the JSON format expected: {:?}",
|
||||
err
|
||||
),
|
||||
})
|
||||
}
|
||||
Ok(config) => {
|
||||
let client = PostgresClientBuilder::build_pararallel_postgres_client(&config)?;
|
||||
self.client = Some(client);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_unload(&mut self) {
|
||||
info!("Unloading plugin: {:?}", self.name());
|
||||
|
||||
match &mut self.client {
|
||||
None => {}
|
||||
Some(client) => {
|
||||
client.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_account(
|
||||
&mut self,
|
||||
account: ReplicaAccountInfoVersions,
|
||||
slot: u64,
|
||||
is_startup: bool,
|
||||
) -> Result<()> {
|
||||
let mut measure_all = Measure::start("accountsdb-plugin-postgres-update-account-main");
|
||||
match account {
|
||||
ReplicaAccountInfoVersions::V0_0_1(account) => {
|
||||
let mut measure_select =
|
||||
Measure::start("accountsdb-plugin-postgres-update-account-select");
|
||||
if let Some(accounts_selector) = &self.accounts_selector {
|
||||
if !accounts_selector.is_account_selected(account.pubkey, account.owner) {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
measure_select.stop();
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-select-us",
|
||||
measure_select.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
debug!(
|
||||
"Updating account {:?} with owner {:?} at slot {:?} using account selector {:?}",
|
||||
bs58::encode(account.pubkey).into_string(),
|
||||
bs58::encode(account.owner).into_string(),
|
||||
slot,
|
||||
self.accounts_selector.as_ref().unwrap()
|
||||
);
|
||||
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database."
|
||||
.to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let mut measure_update =
|
||||
Measure::start("accountsdb-plugin-postgres-update-account-client");
|
||||
let result = { client.update_account(account, slot, is_startup) };
|
||||
measure_update.stop();
|
||||
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-client-us",
|
||||
measure_update.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::AccountsUpdateError {
|
||||
msg: format!("Failed to persist the update of account to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
measure_all.stop();
|
||||
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-main-us",
|
||||
measure_all.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_slot_status(
|
||||
&mut self,
|
||||
slot: u64,
|
||||
parent: Option<u64>,
|
||||
status: SlotStatus,
|
||||
) -> Result<()> {
|
||||
info!("Updating slot {:?} at with status {:?}", slot, status);
|
||||
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let result = client.update_slot_status(slot, parent, status);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the update of slot to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_end_of_startup(&mut self) -> Result<()> {
|
||||
info!("Notifying the end of startup for accounts notifications");
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let result = client.notify_end_of_startup();
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to notify the end of startup for accounts notifications. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_transaction(
|
||||
&mut self,
|
||||
transaction_info: ReplicaTransactionInfoVersions,
|
||||
slot: u64,
|
||||
) -> Result<()> {
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => match transaction_info {
|
||||
ReplicaTransactionInfoVersions::V0_0_1(transaction_info) => {
|
||||
if let Some(transaction_selector) = &self.transaction_selector {
|
||||
if !transaction_selector.is_transaction_selected(
|
||||
transaction_info.is_vote,
|
||||
transaction_info.transaction.message().account_keys_iter(),
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let result = client.log_transaction_info(transaction_info, slot);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the transaction info to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_block_metadata(&mut self, block_info: ReplicaBlockInfoVersions) -> Result<()> {
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => match block_info {
|
||||
ReplicaBlockInfoVersions::V0_0_1(block_info) => {
|
||||
let result = client.update_block_metadata(block_info);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the update of block metadata to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the plugin is interested in account data
|
||||
/// Default is true -- if the plugin is not interested in
|
||||
/// account data, please return false.
|
||||
fn account_data_notifications_enabled(&self) -> bool {
|
||||
self.accounts_selector
|
||||
.as_ref()
|
||||
.map_or_else(|| false, |selector| selector.is_enabled())
|
||||
}
|
||||
|
||||
/// Check if the plugin is interested in transaction data
|
||||
fn transaction_notifications_enabled(&self) -> bool {
|
||||
self.transaction_selector
|
||||
.as_ref()
|
||||
.map_or_else(|| false, |selector| selector.is_enabled())
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountsDbPluginPostgres {
|
||||
fn create_accounts_selector_from_config(config: &serde_json::Value) -> AccountsSelector {
|
||||
let accounts_selector = &config["accounts_selector"];
|
||||
|
||||
if accounts_selector.is_null() {
|
||||
AccountsSelector::default()
|
||||
} else {
|
||||
let accounts = &accounts_selector["accounts"];
|
||||
let accounts: Vec<String> = if accounts.is_array() {
|
||||
accounts
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
let owners = &accounts_selector["owners"];
|
||||
let owners: Vec<String> = if owners.is_array() {
|
||||
owners
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
AccountsSelector::new(&accounts, &owners)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_transaction_selector_from_config(config: &serde_json::Value) -> TransactionSelector {
|
||||
let transaction_selector = &config["transaction_selector"];
|
||||
|
||||
if transaction_selector.is_null() {
|
||||
TransactionSelector::default()
|
||||
} else {
|
||||
let accounts = &transaction_selector["mentions"];
|
||||
let accounts: Vec<String> = if accounts.is_array() {
|
||||
accounts
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
TransactionSelector::new(&accounts)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
/// # Safety
|
||||
///
|
||||
/// This function returns the AccountsDbPluginPostgres pointer as trait AccountsDbPlugin.
|
||||
pub unsafe extern "C" fn _create_plugin() -> *mut dyn AccountsDbPlugin {
|
||||
let plugin = AccountsDbPluginPostgres::new();
|
||||
let plugin: Box<dyn AccountsDbPlugin> = Box::new(plugin);
|
||||
Box::into_raw(plugin)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use {super::*, serde_json};
|
||||
|
||||
#[test]
|
||||
fn test_accounts_selector_from_config() {
|
||||
let config = "{\"accounts_selector\" : { \
|
||||
\"owners\" : [\"9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin\"] \
|
||||
}}";
|
||||
|
||||
let config: serde_json::Value = serde_json::from_str(config).unwrap();
|
||||
AccountsDbPluginPostgres::create_accounts_selector_from_config(&config);
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
pub mod accounts_selector;
|
||||
pub mod accountsdb_plugin_postgres;
|
||||
pub mod postgres_client;
|
||||
pub mod transaction_selector;
|
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
||||
use {
|
||||
crate::{
|
||||
accountsdb_plugin_postgres::{
|
||||
AccountsDbPluginPostgresConfig, AccountsDbPluginPostgresError,
|
||||
},
|
||||
postgres_client::{
|
||||
postgres_client_transaction::DbReward, SimplePostgresClient, UpdateBlockMetadataRequest,
|
||||
},
|
||||
},
|
||||
chrono::Utc,
|
||||
log::*,
|
||||
postgres::{Client, Statement},
|
||||
solana_accountsdb_plugin_interface::accountsdb_plugin_interface::{
|
||||
AccountsDbPluginError, ReplicaBlockInfo,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DbBlockInfo {
|
||||
pub slot: i64,
|
||||
pub blockhash: String,
|
||||
pub rewards: Vec<DbReward>,
|
||||
pub block_time: Option<i64>,
|
||||
pub block_height: Option<i64>,
|
||||
}
|
||||
|
||||
impl<'a> From<&ReplicaBlockInfo<'a>> for DbBlockInfo {
|
||||
fn from(block_info: &ReplicaBlockInfo) -> Self {
|
||||
Self {
|
||||
slot: block_info.slot as i64,
|
||||
blockhash: block_info.blockhash.to_string(),
|
||||
rewards: block_info.rewards.iter().map(DbReward::from).collect(),
|
||||
block_time: block_info.block_time,
|
||||
block_height: block_info
|
||||
.block_height
|
||||
.map(|block_height| block_height as i64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePostgresClient {
|
||||
pub(crate) fn build_block_metadata_upsert_statement(
|
||||
client: &mut Client,
|
||||
config: &AccountsDbPluginPostgresConfig,
|
||||
) -> Result<Statement, AccountsDbPluginError> {
|
||||
let stmt =
|
||||
"INSERT INTO block (slot, blockhash, rewards, block_time, block_height, updated_on) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6)";
|
||||
|
||||
let stmt = client.prepare(stmt);
|
||||
|
||||
match stmt {
|
||||
Err(err) => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(AccountsDbPluginPostgresError::DataSchemaError {
|
||||
msg: format!(
|
||||
"Error in preparing for the block metadata update PostgreSQL database: ({}) host: {:?} user: {:?} config: {:?}",
|
||||
err, config.host, config.user, config
|
||||
),
|
||||
})));
|
||||
}
|
||||
Ok(stmt) => Ok(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_block_metadata_impl(
|
||||
&mut self,
|
||||
block_info: UpdateBlockMetadataRequest,
|
||||
) -> Result<(), AccountsDbPluginError> {
|
||||
let client = self.client.get_mut().unwrap();
|
||||
let statement = &client.update_block_metadata_stmt;
|
||||
let client = &mut client.client;
|
||||
let updated_on = Utc::now().naive_utc();
|
||||
|
||||
let block_info = block_info.block_info;
|
||||
let result = client.query(
|
||||
statement,
|
||||
&[
|
||||
&block_info.slot,
|
||||
&block_info.blockhash,
|
||||
&block_info.rewards,
|
||||
&block_info.block_time,
|
||||
&block_info.block_height,
|
||||
&updated_on,
|
||||
],
|
||||
);
|
||||
|
||||
if let Err(err) = result {
|
||||
let msg = format!(
|
||||
"Failed to persist the update of block metadata to the PostgreSQL database. Error: {:?}",
|
||||
err);
|
||||
error!("{}", msg);
|
||||
return Err(AccountsDbPluginError::AccountsUpdateError { msg });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,194 +0,0 @@
|
||||
/// The transaction selector is responsible for filtering transactions
|
||||
/// in the plugin framework.
|
||||
use {log::*, solana_sdk::pubkey::Pubkey, std::collections::HashSet};
|
||||
|
||||
pub(crate) struct TransactionSelector {
|
||||
pub mentioned_addresses: HashSet<Vec<u8>>,
|
||||
pub select_all_transactions: bool,
|
||||
pub select_all_vote_transactions: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TransactionSelector {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions: false,
|
||||
select_all_vote_transactions: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a selector based on the mentioned addresses
|
||||
/// To select all transactions use ["*"] or ["all"]
|
||||
/// To select all vote transactions, use ["all_votes"]
|
||||
/// To select transactions mentioning specific addresses use ["<pubkey1>", "<pubkey2>", ...]
|
||||
pub fn new(mentioned_addresses: &[String]) -> Self {
|
||||
info!(
|
||||
"Creating TransactionSelector from addresses: {:?}",
|
||||
mentioned_addresses
|
||||
);
|
||||
|
||||
let select_all_transactions = mentioned_addresses
|
||||
.iter()
|
||||
.any(|key| key == "*" || key == "all");
|
||||
if select_all_transactions {
|
||||
return Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions,
|
||||
select_all_vote_transactions: true,
|
||||
};
|
||||
}
|
||||
let select_all_vote_transactions = mentioned_addresses.iter().any(|key| key == "all_votes");
|
||||
if select_all_vote_transactions {
|
||||
return Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions,
|
||||
select_all_vote_transactions: true,
|
||||
};
|
||||
}
|
||||
|
||||
let mentioned_addresses = mentioned_addresses
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
mentioned_addresses,
|
||||
select_all_transactions: false,
|
||||
select_all_vote_transactions: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a transaction is of interest.
|
||||
pub fn is_transaction_selected(
|
||||
&self,
|
||||
is_vote: bool,
|
||||
mentioned_addresses: Box<dyn Iterator<Item = &Pubkey> + '_>,
|
||||
) -> bool {
|
||||
if !self.is_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.select_all_transactions || (self.select_all_vote_transactions && is_vote) {
|
||||
return true;
|
||||
}
|
||||
for address in mentioned_addresses {
|
||||
if self.mentioned_addresses.contains(address.as_ref()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if any transaction is of interest at all
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.select_all_transactions
|
||||
|| self.select_all_vote_transactions
|
||||
|| !self.mentioned_addresses.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_select_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&[pubkey1.to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_transaction_using_wildcard() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["*".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_transaction_all() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["all".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_vote_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["all_votes".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_no_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&[]);
|
||||
|
||||
assert!(!selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(!selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(!selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
}
|
||||
}
|
@@ -5,12 +5,13 @@ use {
|
||||
log::*,
|
||||
rand::{thread_rng, Rng},
|
||||
rayon::prelude::*,
|
||||
solana_core::{banking_stage::BankingStage, packet_deduper::PacketDeduper},
|
||||
solana_core::banking_stage::BankingStage,
|
||||
solana_gossip::cluster_info::{ClusterInfo, Node},
|
||||
solana_ledger::{
|
||||
blockstore::Blockstore,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
leader_schedule_cache::LeaderScheduleCache,
|
||||
},
|
||||
solana_measure::measure::Measure,
|
||||
solana_perf::packet::to_packet_batches,
|
||||
@@ -174,6 +175,11 @@ fn main() {
|
||||
let mut bank_forks = BankForks::new(bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
// set cost tracker limits to MAX so it will not filter out TXs
|
||||
bank.write_cost_tracker()
|
||||
.unwrap()
|
||||
.set_limits(std::u64::MAX, std::u64::MAX, std::u64::MAX);
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, total_num_transactions);
|
||||
|
||||
let same_payer = matches.is_present("same_payer");
|
||||
@@ -218,15 +224,19 @@ fn main() {
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) = create_test_recorder(
|
||||
&bank,
|
||||
&blockstore,
|
||||
None,
|
||||
Some(leader_schedule_cache.clone()),
|
||||
);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let packet_deduper = PacketDeduper::default();
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -236,7 +246,6 @@ fn main() {
|
||||
None,
|
||||
replay_vote_sender,
|
||||
Arc::new(RwLock::new(CostModel::default())),
|
||||
packet_deduper.clone(),
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
@@ -331,9 +340,17 @@ fn main() {
|
||||
bank = bank_forks.working_bank();
|
||||
insert_time.stop();
|
||||
|
||||
// set cost tracker limits to MAX so it will not filter out TXs
|
||||
bank.write_cost_tracker().unwrap().set_limits(
|
||||
std::u64::MAX,
|
||||
std::u64::MAX,
|
||||
std::u64::MAX,
|
||||
);
|
||||
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
leader_schedule_cache.set_root(&bank);
|
||||
bank_forks.set_root(root, &AbsRequestSender::default(), None);
|
||||
root += 1;
|
||||
}
|
||||
@@ -351,7 +368,6 @@ fn main() {
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
packet_deduper.reset();
|
||||
total_us += duration_as_us(&now.elapsed());
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
|
@@ -10,7 +10,7 @@ documentation = "https://docs.rs/solana-banks-client"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
borsh = "0.9.1"
|
||||
borsh = "0.9.3"
|
||||
futures = "0.3"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.10.0" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.10.0" }
|
||||
|
@@ -5,9 +5,11 @@
|
||||
//! but they are undocumented, may change over time, and are generally more
|
||||
//! cumbersome to use.
|
||||
|
||||
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
|
||||
use {
|
||||
pub use {
|
||||
crate::error::BanksClientError,
|
||||
solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus},
|
||||
};
|
||||
use {
|
||||
borsh::BorshDeserialize,
|
||||
futures::{future::join_all, Future, FutureExt, TryFutureExt},
|
||||
solana_banks_interface::{BanksRequest, BanksResponse, BanksTransactionResultWithSimulation},
|
||||
|
@@ -10,7 +10,7 @@ documentation = "https://docs.rs/solana-banks-interface"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.133", features = ["derive"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
tarpc = { version = "0.27.2", features = ["full"] }
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
clap::{crate_description, crate_name, App, Arg},
|
||||
clap::{crate_description, crate_name, value_t, App, Arg},
|
||||
crossbeam_channel::unbounded,
|
||||
solana_streamer::{
|
||||
packet::{Packet, PacketBatch, PacketBatchRecycler, PACKET_DATA_SIZE},
|
||||
@@ -67,13 +67,22 @@ fn main() -> Result<()> {
|
||||
.takes_value(true)
|
||||
.help("Use NUM receive sockets"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-producers")
|
||||
.long("num-producers")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Use this many producer threads."),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(n) = matches.value_of("num-recv-sockets") {
|
||||
num_sockets = max(num_sockets, n.to_string().parse().expect("integer"));
|
||||
}
|
||||
|
||||
let mut port = 0;
|
||||
let num_producers = value_t!(matches, "num_producers", u64).unwrap_or(4);
|
||||
|
||||
let port = 0;
|
||||
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let mut addr = SocketAddr::new(ip_addr, 0);
|
||||
|
||||
@@ -82,13 +91,16 @@ fn main() -> Result<()> {
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketBatchRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
|
||||
let (_port, read_sockets) = solana_net_utils::multi_bind_in_range(
|
||||
ip_addr,
|
||||
(port, port + num_sockets as u16),
|
||||
num_sockets,
|
||||
)
|
||||
.unwrap();
|
||||
for read in read_sockets {
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
|
||||
addr = read.local_addr().unwrap();
|
||||
port = addr.port();
|
||||
|
||||
let (s_reader, r_reader) = unbounded();
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(
|
||||
@@ -102,9 +114,10 @@ fn main() -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
let t_producer1 = producer(&addr, exit.clone());
|
||||
let t_producer2 = producer(&addr, exit.clone());
|
||||
let t_producer3 = producer(&addr, exit.clone());
|
||||
let producer_threads: Vec<_> = (0..num_producers)
|
||||
.into_iter()
|
||||
.map(|_| producer(&addr, exit.clone()))
|
||||
.collect();
|
||||
|
||||
let rvs = Arc::new(AtomicUsize::new(0));
|
||||
let sink_threads: Vec<_> = read_channels
|
||||
@@ -124,9 +137,9 @@ fn main() -> Result<()> {
|
||||
for t_reader in read_threads {
|
||||
t_reader.join()?;
|
||||
}
|
||||
t_producer1.join()?;
|
||||
t_producer2.join()?;
|
||||
t_producer3.join()?;
|
||||
for t_producer in producer_threads {
|
||||
t_producer.join()?;
|
||||
}
|
||||
for t_sink in sink_threads {
|
||||
t_sink.join()?;
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ clap = "2.33.1"
|
||||
crossbeam-channel = "0.5"
|
||||
log = "0.4.14"
|
||||
rayon = "1.5.1"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.78"
|
||||
serde_yaml = "0.8.23"
|
||||
solana-core = { path = "../core", version = "=1.10.0" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.10.0" }
|
||||
|
@@ -110,7 +110,7 @@ fn generate_chunked_transfers(
|
||||
shared_txs: &SharedTransactions,
|
||||
shared_tx_active_thread_count: Arc<AtomicIsize>,
|
||||
source_keypair_chunks: Vec<Vec<&Keypair>>,
|
||||
dest_keypair_chunks: &mut Vec<VecDeque<&Keypair>>,
|
||||
dest_keypair_chunks: &mut [VecDeque<&Keypair>],
|
||||
threads: usize,
|
||||
duration: Duration,
|
||||
sustained: bool,
|
||||
|
@@ -21,7 +21,7 @@ pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_default("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
solana_metrics::set_panic_hook("bench-tps", /*version:*/ None);
|
||||
|
||||
let matches = cli::build_args(solana_version::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
@@ -29,7 +29,7 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: make_identical_validator_configs(
|
||||
&ValidatorConfig::default(),
|
||||
&ValidatorConfig::default_for_test(),
|
||||
NUM_NODES,
|
||||
),
|
||||
native_instruction_processors,
|
||||
|
32
bloom/Cargo.toml
Normal file
32
bloom/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "solana-bloom"
|
||||
version = "1.10.0"
|
||||
description = "Solana bloom filter"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-bloom"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
fnv = "1.0.7"
|
||||
rand = "0.7.0"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
rayon = "1.5.1"
|
||||
serde_derive = "1.0.103"
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.0" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
log = "0.4.14"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_bloom"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.4"
|
@@ -5,7 +5,7 @@ use {
|
||||
bv::BitVec,
|
||||
fnv::FnvHasher,
|
||||
rand::Rng,
|
||||
solana_runtime::bloom::{AtomicBloom, Bloom, BloomHashIndex},
|
||||
solana_bloom::bloom::{AtomicBloom, Bloom, BloomHashIndex},
|
||||
solana_sdk::{
|
||||
hash::{hash, Hash},
|
||||
signature::Signature,
|
1
bloom/build.rs
Symbolic link
1
bloom/build.rs
Symbolic link
@@ -0,0 +1 @@
|
||||
../frozen-abi/build.rs
|
@@ -101,7 +101,7 @@ impl<T: BloomHashIndex> Bloom<T> {
|
||||
}
|
||||
}
|
||||
fn pos(&self, key: &T, k: u64) -> u64 {
|
||||
key.hash_at_index(k) % self.bits.len()
|
||||
key.hash_at_index(k).wrapping_rem(self.bits.len())
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.bits = BitVec::new_fill(false, self.bits.len());
|
||||
@@ -111,7 +111,7 @@ impl<T: BloomHashIndex> Bloom<T> {
|
||||
for k in &self.keys {
|
||||
let pos = self.pos(key, *k);
|
||||
if !self.bits.get(pos) {
|
||||
self.num_bits_set += 1;
|
||||
self.num_bits_set = self.num_bits_set.saturating_add(1);
|
||||
self.bits.set(pos, true);
|
||||
}
|
||||
}
|
||||
@@ -164,21 +164,26 @@ impl<T: BloomHashIndex> From<Bloom<T>> for AtomicBloom<T> {
|
||||
|
||||
impl<T: BloomHashIndex> AtomicBloom<T> {
|
||||
fn pos(&self, key: &T, hash_index: u64) -> (usize, u64) {
|
||||
let pos = key.hash_at_index(hash_index) % self.num_bits;
|
||||
let pos = key.hash_at_index(hash_index).wrapping_rem(self.num_bits);
|
||||
// Divide by 64 to figure out which of the
|
||||
// AtomicU64 bit chunks we need to modify.
|
||||
let index = pos >> 6;
|
||||
let index = pos.wrapping_shr(6);
|
||||
// (pos & 63) is equivalent to mod 64 so that we can find
|
||||
// the index of the bit within the AtomicU64 to modify.
|
||||
let mask = 1u64 << (pos & 63);
|
||||
let mask = 1u64.wrapping_shl(u32::try_from(pos & 63).unwrap());
|
||||
(index as usize, mask)
|
||||
}
|
||||
|
||||
pub fn add(&self, key: &T) {
|
||||
/// Adds an item to the bloom filter and returns true if the item
|
||||
/// was not in the filter before.
|
||||
pub fn add(&self, key: &T) -> bool {
|
||||
let mut added = false;
|
||||
for k in &self.keys {
|
||||
let (index, mask) = self.pos(key, *k);
|
||||
self.bits[index].fetch_or(mask, Ordering::Relaxed);
|
||||
let prev_val = self.bits[index].fetch_or(mask, Ordering::Relaxed);
|
||||
added = added || prev_val & mask == 0u64;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
pub fn contains(&self, key: &T) -> bool {
|
||||
@@ -189,6 +194,12 @@ impl<T: BloomHashIndex> AtomicBloom<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear_for_tests(&mut self) {
|
||||
self.bits.iter().for_each(|bit| {
|
||||
bit.store(0u64, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
// Only for tests and simulations.
|
||||
pub fn mock_clone(&self) -> Self {
|
||||
Self {
|
||||
@@ -320,7 +331,9 @@ mod test {
|
||||
assert_eq!(bloom.keys.len(), 3);
|
||||
assert_eq!(bloom.num_bits, 6168);
|
||||
assert_eq!(bloom.bits.len(), 97);
|
||||
hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
let bloom: Bloom<Hash> = bloom.into();
|
||||
assert_eq!(bloom.keys.len(), 3);
|
||||
assert_eq!(bloom.bits.len(), 6168);
|
||||
@@ -362,7 +375,9 @@ mod test {
|
||||
}
|
||||
// Round trip, re-inserting the same hash values.
|
||||
let bloom: AtomicBloom<_> = bloom.into();
|
||||
hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
for hash_value in &hash_values {
|
||||
assert!(bloom.contains(hash_value));
|
||||
}
|
||||
@@ -380,7 +395,9 @@ mod test {
|
||||
let bloom: AtomicBloom<_> = bloom.into();
|
||||
assert_eq!(bloom.num_bits, 9731);
|
||||
assert_eq!(bloom.bits.len(), (9731 + 63) / 64);
|
||||
more_hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
more_hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
for hash_value in &hash_values {
|
||||
assert!(bloom.contains(hash_value));
|
||||
}
|
5
bloom/src/lib.rs
Normal file
5
bloom/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
||||
pub mod bloom;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana_frozen_abi_macro;
|
365
ci/buildkite-pipeline-in-disk.sh
Normal file
365
ci/buildkite-pipeline-in-disk.sh
Normal file
@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds a buildkite pipeline based on the environment variables
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
output_file=${1:-/dev/stderr}
|
||||
|
||||
if [[ -n $CI_PULL_REQUEST ]]; then
|
||||
IFS=':' read -ra affected_files <<< "$(buildkite-agent meta-data get affected_files)"
|
||||
if [[ ${#affected_files[*]} -eq 0 ]]; then
|
||||
echo "Unable to determine the files affected by this PR"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
affected_files=()
|
||||
fi
|
||||
|
||||
annotate() {
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
buildkite-agent annotate "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if a CI pull request affects one or more path patterns. Each
|
||||
# pattern argument is checked in series. If one of them found to be affected,
|
||||
# return immediately as such.
|
||||
#
|
||||
# Bash regular expressions are permitted in the pattern:
|
||||
# affects .rs$ -- any file or directory ending in .rs
|
||||
# affects .rs -- also matches foo.rs.bar
|
||||
# affects ^snap/ -- anything under the snap/ subdirectory
|
||||
# affects snap/ -- also matches foo/snap/
|
||||
# Any pattern starting with the ! character will be negated:
|
||||
# affects !^docs/ -- anything *not* under the docs/ subdirectory
|
||||
#
|
||||
affects() {
|
||||
if [[ -z $CI_PULL_REQUEST ]]; then
|
||||
# affected_files metadata is not currently available for non-PR builds so assume
|
||||
# the worse (affected)
|
||||
return 0
|
||||
fi
|
||||
# Assume everyting needs to be tested when any Dockerfile changes
|
||||
for pattern in ^ci/docker-rust/Dockerfile ^ci/docker-rust-nightly/Dockerfile "$@"; do
|
||||
if [[ ${pattern:0:1} = "!" ]]; then
|
||||
for file in "${affected_files[@]}"; do
|
||||
if [[ ! $file =~ ${pattern:1} ]]; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
else
|
||||
for file in "${affected_files[@]}"; do
|
||||
if [[ $file =~ $pattern ]]; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
return 1 # not affected
|
||||
}
|
||||
|
||||
|
||||
# Checks if a CI pull request affects anything other than the provided path patterns
|
||||
#
|
||||
# Syntax is the same as `affects()` except that the negation prefix is not
|
||||
# supported
|
||||
#
|
||||
affects_other_than() {
|
||||
if [[ -z $CI_PULL_REQUEST ]]; then
|
||||
# affected_files metadata is not currently available for non-PR builds so assume
|
||||
# the worse (affected)
|
||||
return 0
|
||||
fi
|
||||
|
||||
for file in "${affected_files[@]}"; do
|
||||
declare matched=false
|
||||
for pattern in "$@"; do
|
||||
if [[ $file =~ $pattern ]]; then
|
||||
matched=true
|
||||
fi
|
||||
done
|
||||
if ! $matched; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
|
||||
return 1 # not affected
|
||||
}
|
||||
|
||||
|
||||
start_pipeline() {
|
||||
echo "# $*" > "$output_file"
|
||||
echo "steps:" >> "$output_file"
|
||||
}
|
||||
|
||||
command_step() {
|
||||
cat >> "$output_file" <<EOF
|
||||
- name: "$1"
|
||||
command: "$2"
|
||||
timeout_in_minutes: $3
|
||||
artifact_paths: "log-*.txt"
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
trigger_secondary_step() {
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
message: "${BUILDKITE_MESSAGE}"
|
||||
commit: "${BUILDKITE_COMMIT}"
|
||||
branch: "${BUILDKITE_BRANCH}"
|
||||
env:
|
||||
TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}"
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_step() {
|
||||
echo " - wait" >> "$output_file"
|
||||
}
|
||||
|
||||
all_test_steps() {
|
||||
command_step checks ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-checks.sh" 20
|
||||
wait_step
|
||||
|
||||
# Coverage...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^scripts/coverage.sh \
|
||||
; then
|
||||
command_step coverage ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-coverage.sh" 40
|
||||
wait_step
|
||||
else
|
||||
annotate --style info --context test-coverage \
|
||||
"Coverage skipped as no .rs files were modified"
|
||||
fi
|
||||
# Coverage in disk...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^scripts/coverage-in-disk.sh \
|
||||
; then
|
||||
command_step coverage-in-disk ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-coverage.sh" 40
|
||||
wait_step
|
||||
else
|
||||
annotate --style info --context test-coverage \
|
||||
"Coverage skipped as no .rs files were modified"
|
||||
fi
|
||||
# Full test suite
|
||||
command_step stable ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-stable.sh" 60
|
||||
wait_step
|
||||
|
||||
# BPF test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-bpf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-bpf.sh"
|
||||
name: "stable-bpf"
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "bpf-dumps.tar.bz2"
|
||||
agents:
|
||||
- "queue=default"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"Stable-BPF skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Perf test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"Stable-perf skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-projects.sh"
|
||||
name: "downstream-projects"
|
||||
timeout_in_minutes: 30
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream Anchor projects backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-anchor-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-anchor-projects.sh"
|
||||
name: "downstream-anchor-projects"
|
||||
timeout_in_minutes: 10
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-anchor-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Wasm support
|
||||
if affects \
|
||||
^ci/test-wasm.sh \
|
||||
^ci/test-stable.sh \
|
||||
^sdk/ \
|
||||
; then
|
||||
command_step wasm ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-wasm.sh" 20
|
||||
else
|
||||
annotate --style info \
|
||||
"wasm skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Benches...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^ci/test-bench.sh \
|
||||
; then
|
||||
command_step bench "ci/test-bench.sh" 30
|
||||
else
|
||||
annotate --style info --context test-bench \
|
||||
"Bench skipped as no .rs files were modified"
|
||||
fi
|
||||
|
||||
command_step "local-cluster" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster.sh" \
|
||||
40
|
||||
|
||||
command_step "local-cluster-flakey" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster-flakey.sh" \
|
||||
10
|
||||
|
||||
command_step "local-cluster-slow" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster-slow.sh" \
|
||||
30
|
||||
}
|
||||
|
||||
pull_or_push_steps() {
|
||||
command_step sanity "ci/test-sanity.sh" 5
|
||||
wait_step
|
||||
|
||||
# Check for any .sh file changes
|
||||
if affects .sh$; then
|
||||
command_step shellcheck "ci/shellcheck.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
|
||||
# Run the full test suite by default, skipping only if modifications are local
|
||||
# to some particular areas of the tree
|
||||
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
|
||||
all_test_steps
|
||||
fi
|
||||
|
||||
# web3.js, explorer and docs changes run on Travis or Github actions...
|
||||
}
|
||||
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
start_pipeline "Tag pipeline for $BUILDKITE_TAG"
|
||||
|
||||
annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
|
||||
# Jump directly to the secondary build to publish release artifacts quickly
|
||||
trigger_secondary_step
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
echo "+++ Affected files in this PR"
|
||||
for file in "${affected_files[@]}"; do
|
||||
echo "- $file"
|
||||
done
|
||||
|
||||
start_pipeline "Pull request pipeline for $BUILDKITE_BRANCH"
|
||||
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
|
||||
if [[ $GITHUB_USER = "dependabot[bot]" ]]; then
|
||||
command_step dependabot "ci/dependabot-pr.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
pull_or_push_steps
|
||||
exit 0
|
||||
fi
|
||||
|
||||
start_pipeline "Push pipeline for ${BUILDKITE_BRANCH:-?unknown branch?}"
|
||||
pull_or_push_steps
|
||||
wait_step
|
||||
trigger_secondary_step
|
||||
exit 0
|
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.57.0
|
||||
FROM solanalabs/rust:1.59.0
|
||||
ARG date
|
||||
|
||||
RUN set -x \
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.57.0
|
||||
FROM rust:1.59.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@@ -150,7 +150,7 @@ elif [[ -n $BUILDKITE ]]; then
|
||||
cat > release.solana.com-install <<EOF
|
||||
SOLANA_RELEASE=$CHANNEL_OR_TAG
|
||||
SOLANA_INSTALL_INIT_ARGS=$CHANNEL_OR_TAG
|
||||
SOLANA_DOWNLOAD_ROOT=http://release.solana.com
|
||||
SOLANA_DOWNLOAD_ROOT=https://release.solana.com
|
||||
EOF
|
||||
cat install/solana-install-init.sh >> release.solana.com-install
|
||||
|
||||
|
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./scripts/run.sh &
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--full-snapshot-interval-slots 200" timeout 120 ./scripts/run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
|
@@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.57.0
|
||||
stable_version=1.59.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2021-12-03
|
||||
nightly_version=2022-02-24
|
||||
fi
|
||||
|
||||
|
||||
|
24
ci/sbf-tools-info.sh
Executable file
24
ci/sbf-tools-info.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Finds the version of sbf-tools used by this source tree.
|
||||
#
|
||||
# stdout of this script may be eval-ed.
|
||||
#
|
||||
|
||||
here="$(dirname "$0")"
|
||||
|
||||
SBF_TOOLS_VERSION=unknown
|
||||
|
||||
cargo_build_bpf_main="${here}/../sdk/cargo-build-bpf/src/main.rs"
|
||||
if [[ -f "${cargo_build_bpf_main}" ]]; then
|
||||
version=$(sed -e 's/^.*bpf_tools_version\s*=\s*"\(v[0-9.]\+\)".*/\1/;t;d' "${cargo_build_bpf_main}")
|
||||
if [[ ${version} != '' ]]; then
|
||||
SBF_TOOLS_VERSION="${version}"
|
||||
else
|
||||
echo '--- unable to parse SBF_TOOLS_VERSION'
|
||||
fi
|
||||
else
|
||||
echo "--- '${cargo_build_bpf_main}' not present"
|
||||
fi
|
||||
|
||||
echo SBF_TOOLS_VERSION="${SBF_TOOLS_VERSION}"
|
@@ -69,20 +69,14 @@ _ ci/order-crates-for-publishing.py
|
||||
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
_ "$cargo" nightly fmt --all -- --check
|
||||
|
||||
_ ci/do-audit.sh
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
for project in rust/*/ ; do
|
||||
echo "+++ do_bpf_checks $project"
|
||||
(
|
||||
cd "$project"
|
||||
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
_ "$cargo" stable fmt -- --check
|
||||
)
|
||||
done
|
||||
_ "$cargo" nightly clippy --all -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
_ "$cargo" nightly fmt --all -- --check
|
||||
}
|
||||
|
||||
echo --- ok
|
||||
|
@@ -21,15 +21,16 @@ export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
source scripts/ulimit-n.sh
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 2gb/thread of memory
|
||||
# limit jobs to 4gb/thread
|
||||
JOBS=$(grep MemTotal /proc/meminfo | awk '{printf "%.0f", ($2 / (4 * 1024 * 1024))}')
|
||||
NPROC=$(nproc)
|
||||
NPROC=$((NPROC>14 ? 14 : NPROC))
|
||||
JOBS=$((JOBS>NPROC ? NPROC : JOBS))
|
||||
|
||||
|
||||
echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ "$cargo" stable test --jobs "$JOBS" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-bpf)
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
|
@@ -328,7 +328,7 @@ pub fn is_derivation<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let value = value.as_ref().replace("'", "");
|
||||
let value = value.as_ref().replace('\'', "");
|
||||
let mut parts = value.split('/');
|
||||
let account = parts.next().unwrap();
|
||||
account
|
||||
|
@@ -12,13 +12,13 @@ documentation = "https://docs.rs/solana-cli-config"
|
||||
[dependencies]
|
||||
dirs-next = "2.0.0"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.23"
|
||||
url = "2.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.53"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -17,8 +17,8 @@ console = "0.15.0"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.16.2"
|
||||
serde = "1.0.133"
|
||||
serde_json = "1.0.74"
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.78"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
|
@@ -46,6 +46,8 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
@@ -391,19 +393,19 @@ impl fmt::Display for CliValidators {
|
||||
) -> fmt::Result {
|
||||
fn non_zero_or_dash(v: u64, max_v: u64) -> String {
|
||||
if v == 0 {
|
||||
"- ".into()
|
||||
" - ".into()
|
||||
} else if v == max_v {
|
||||
format!("{:>8} ( 0)", v)
|
||||
format!("{:>9} ( 0)", v)
|
||||
} else if v > max_v.saturating_sub(100) {
|
||||
format!("{:>8} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
|
||||
format!("{:>9} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
|
||||
} else {
|
||||
format!("{:>8} ", v)
|
||||
format!("{:>9} ", v)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {}",
|
||||
"{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {:>22} ({:.2}%)",
|
||||
if validator.delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
@@ -417,19 +419,19 @@ impl fmt::Display for CliValidators {
|
||||
if let Some(skip_rate) = validator.skip_rate {
|
||||
format!("{:.2}%", skip_rate)
|
||||
} else {
|
||||
"- ".to_string()
|
||||
"- ".to_string()
|
||||
},
|
||||
validator.epoch_credits,
|
||||
validator.version,
|
||||
if validator.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64,
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
build_balance_message_with_config(
|
||||
validator.activated_stake,
|
||||
&BuildBalanceMessageConfig {
|
||||
use_lamports_unit,
|
||||
trim_trailing_zeros: false,
|
||||
..BuildBalanceMessageConfig::default()
|
||||
}
|
||||
),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -439,13 +441,13 @@ impl fmt::Display for CliValidators {
|
||||
0
|
||||
};
|
||||
let header = style(format!(
|
||||
"{:padding$} {:<44} {:<38} {} {} {} {} {} {} {}",
|
||||
"{:padding$} {:<44} {:<38} {} {} {} {} {} {} {}",
|
||||
" ",
|
||||
"Identity",
|
||||
"Vote Account",
|
||||
"Commission",
|
||||
"Last Vote ",
|
||||
"Root Slot ",
|
||||
"Last Vote ",
|
||||
"Root Slot ",
|
||||
"Skip Rate",
|
||||
"Credits",
|
||||
"Version",
|
||||
@@ -2285,6 +2287,7 @@ impl fmt::Display for CliBlock {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
#[allow(clippy::format_in_format_args)]
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:>15} {} {}",
|
||||
@@ -2448,6 +2451,8 @@ pub struct CliGossipNode {
|
||||
pub rpc_host: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feature_set: Option<u32>,
|
||||
}
|
||||
|
||||
impl CliGossipNode {
|
||||
@@ -2460,6 +2465,7 @@ impl CliGossipNode {
|
||||
tpu_port: info.tpu.map(|addr| addr.port()),
|
||||
rpc_host: info.rpc.map(|addr| addr.to_string()),
|
||||
version: info.version,
|
||||
feature_set: info.feature_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2485,7 +2491,7 @@ impl fmt::Display for CliGossipNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:15} | {:44} | {:6} | {:5} | {:21} | {}",
|
||||
"{:15} | {:44} | {:6} | {:5} | {:21} | {:8}| {}",
|
||||
unwrap_to_string_or_none(self.ip_address.as_ref()),
|
||||
self.identity_label
|
||||
.as_ref()
|
||||
@@ -2494,6 +2500,7 @@ impl fmt::Display for CliGossipNode {
|
||||
unwrap_to_string_or_none(self.tpu_port.as_ref()),
|
||||
unwrap_to_string_or_none(self.rpc_host.as_ref()),
|
||||
unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
|
||||
unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2508,10 +2515,10 @@ impl fmt::Display for CliGossipNodes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"IP Address | Node identifier \
|
||||
| Gossip | TPU | RPC Address | Version\n\
|
||||
"IP Address | Identity \
|
||||
| Gossip | TPU | RPC Address | Version | Feature Set\n\
|
||||
----------------+----------------------------------------------+\
|
||||
--------+-------+-----------------------+----------------",
|
||||
--------+-------+-----------------------+---------+----------------",
|
||||
)?;
|
||||
for node in self.0.iter() {
|
||||
writeln!(f, "{}", node)?;
|
||||
@@ -2523,6 +2530,172 @@ impl fmt::Display for CliGossipNodes {
|
||||
impl QuietDisplay for CliGossipNodes {}
|
||||
impl VerboseDisplay for CliGossipNodes {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPing {
|
||||
pub source_pubkey: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fixed_blockhash: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub blockhash_from_cluster: bool,
|
||||
pub pings: Vec<CliPingData>,
|
||||
pub transaction_stats: CliPingTxStats,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub confirmation_stats: Option<CliPingConfirmationStats>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliPing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
|
||||
if let Some(fixed_blockhash) = &self.fixed_blockhash {
|
||||
let blockhash_origin = if self.blockhash_from_cluster {
|
||||
"fetched from cluster"
|
||||
} else {
|
||||
"supplied from cli arguments"
|
||||
};
|
||||
writeln!(
|
||||
f,
|
||||
"Fixed blockhash is used: {} ({})",
|
||||
fixed_blockhash, blockhash_origin
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
for ping in &self.pings {
|
||||
write!(f, "{}", ping)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(f, "--- transaction statistics ---")?;
|
||||
write!(f, "{}", self.transaction_stats)?;
|
||||
if let Some(confirmation_stats) = &self.confirmation_stats {
|
||||
write!(f, "{}", confirmation_stats)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPing {}
|
||||
impl VerboseDisplay for CliPing {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingData {
|
||||
pub success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub signature: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ms: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub print_timestamp: bool,
|
||||
pub timestamp: String,
|
||||
pub sequence: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lamports: Option<u64>,
|
||||
}
|
||||
impl fmt::Display for CliPingData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let (mark, msg) = if let Some(signature) = &self.signature {
|
||||
if self.success {
|
||||
(
|
||||
CHECK_MARK,
|
||||
format!(
|
||||
"{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
|
||||
self.lamports.unwrap(),
|
||||
self.sequence,
|
||||
self.ms.unwrap(),
|
||||
signature
|
||||
),
|
||||
)
|
||||
} else if let Some(error) = &self.error {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Transaction failed: seq={:<3} error={:?} signature={}",
|
||||
self.sequence, error, signature
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Confirmation timeout: seq={:<3} signature={}",
|
||||
self.sequence, signature
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Submit failed: seq={:<3} error={:?}",
|
||||
self.sequence,
|
||||
self.error.as_ref().unwrap(),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
if self.print_timestamp {
|
||||
&self.timestamp
|
||||
} else {
|
||||
""
|
||||
},
|
||||
mark,
|
||||
msg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPingData {}
|
||||
impl VerboseDisplay for CliPingData {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingTxStats {
|
||||
pub num_transactions: u32,
|
||||
pub num_transaction_confirmed: u32,
|
||||
}
|
||||
impl fmt::Display for CliPingTxStats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
|
||||
self.num_transactions,
|
||||
self.num_transaction_confirmed,
|
||||
(100.
|
||||
- f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
|
||||
* 100.)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPingTxStats {}
|
||||
impl VerboseDisplay for CliPingTxStats {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingConfirmationStats {
|
||||
pub min: f64,
|
||||
pub mean: f64,
|
||||
pub max: f64,
|
||||
pub std_dev: f64,
|
||||
}
|
||||
impl fmt::Display for CliPingConfirmationStats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
|
||||
self.min, self.mean, self.max, self.std_dev,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl QuietDisplay for CliPingConfirmationStats {}
|
||||
impl VerboseDisplay for CliPingConfirmationStats {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
|
@@ -22,11 +22,11 @@ log = "0.4.14"
|
||||
humantime = "2.0.1"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.11.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.4"
|
||||
serde = "1.0.133"
|
||||
reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.5"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.78"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
@@ -37,7 +37,7 @@ solana-config-program = { path = "../programs/config", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-program-runtime = { path = "../program-runtime", version = "=1.10.0" }
|
||||
solana_rbpf = "=0.2.21"
|
||||
solana_rbpf = "=0.2.24"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
|
@@ -83,7 +83,6 @@ pub enum CliCommand {
|
||||
filter: RpcTransactionLogsFilter,
|
||||
},
|
||||
Ping {
|
||||
lamports: u64,
|
||||
interval: Duration,
|
||||
count: Option<u64>,
|
||||
timeout: Duration,
|
||||
@@ -973,7 +972,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
CliCommand::LiveSlots => process_live_slots(config),
|
||||
CliCommand::Logs { filter } => process_logs(config, filter),
|
||||
CliCommand::Ping {
|
||||
lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
@@ -982,7 +980,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
} => process_ping(
|
||||
&rpc_client,
|
||||
config,
|
||||
*lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
@@ -1709,7 +1706,7 @@ mod tests {
|
||||
serde_json::{json, Value},
|
||||
solana_client::{
|
||||
blockhash_query,
|
||||
mock_sender::SIGNATURE,
|
||||
mock_sender_for_cli::SIGNATURE,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcResponseContext},
|
||||
},
|
||||
|
@@ -4,7 +4,7 @@ use {
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
},
|
||||
clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
|
||||
console::{style, Emoji},
|
||||
console::style,
|
||||
crossbeam_channel::unbounded,
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_clap_utils::{
|
||||
@@ -16,7 +16,7 @@ use {
|
||||
solana_cli_output::{
|
||||
display::{
|
||||
build_balance_message, format_labeled_address, new_spinner_progress_bar,
|
||||
println_name_value, println_transaction, unix_timestamp_to_string, writeln_name_value,
|
||||
println_transaction, unix_timestamp_to_string, writeln_name_value,
|
||||
},
|
||||
*,
|
||||
},
|
||||
@@ -44,13 +44,13 @@ use {
|
||||
message::Message,
|
||||
native_token::lamports_to_sol,
|
||||
nonce::State as NonceState,
|
||||
pubkey::{self, Pubkey},
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
rpc_port::DEFAULT_RPC_PORT_STR,
|
||||
signature::Signature,
|
||||
slot_history,
|
||||
stake::{self, state::StakeState},
|
||||
system_instruction, system_program,
|
||||
system_instruction,
|
||||
sysvar::{
|
||||
self,
|
||||
slot_history::SlotHistory,
|
||||
@@ -75,9 +75,6 @@ use {
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||
|
||||
pub trait ClusterQuerySubCommands {
|
||||
fn cluster_query_subcommands(self) -> Self;
|
||||
}
|
||||
@@ -263,15 +260,6 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.help("Print timestamp (unix time + microseconds as in gettimeofday) before each line"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.value_name("NUMBER")
|
||||
.takes_value(true)
|
||||
.default_value("1")
|
||||
.validator(is_amount)
|
||||
.help("Number of lamports to transfer for each transaction"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("timeout")
|
||||
.short("t")
|
||||
@@ -516,7 +504,6 @@ pub fn parse_cluster_ping(
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let lamports = value_t_or_exit!(matches, "lamports", u64);
|
||||
let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64));
|
||||
let count = if matches.is_present("count") {
|
||||
Some(value_t_or_exit!(matches, "count", u64))
|
||||
@@ -528,7 +515,6 @@ pub fn parse_cluster_ping(
|
||||
let print_timestamp = matches.is_present("print_timestamp");
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Ping {
|
||||
lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
@@ -1359,40 +1345,34 @@ pub fn process_get_transaction_count(rpc_client: &RpcClient, _config: &CliConfig
|
||||
pub fn process_ping(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
lamports: u64,
|
||||
interval: &Duration,
|
||||
count: &Option<u64>,
|
||||
timeout: &Duration,
|
||||
fixed_blockhash: &Option<Hash>,
|
||||
print_timestamp: bool,
|
||||
) -> ProcessResult {
|
||||
println_name_value("Source Account:", &config.signers[0].pubkey().to_string());
|
||||
println!();
|
||||
|
||||
let (signal_sender, signal_receiver) = unbounded();
|
||||
ctrlc::set_handler(move || {
|
||||
let _ = signal_sender.send(());
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
let mut cli_pings = vec![];
|
||||
|
||||
let mut submit_count = 0;
|
||||
let mut confirmed_count = 0;
|
||||
let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
|
||||
|
||||
let mut blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let mut blockhash_transaction_count = 0;
|
||||
let mut lamports = 0;
|
||||
let mut blockhash_acquired = Instant::now();
|
||||
let mut blockhash_from_cluster = false;
|
||||
if let Some(fixed_blockhash) = fixed_blockhash {
|
||||
let blockhash_origin = if *fixed_blockhash != Hash::default() {
|
||||
if *fixed_blockhash != Hash::default() {
|
||||
blockhash = *fixed_blockhash;
|
||||
"supplied from cli arguments"
|
||||
} else {
|
||||
"fetched from cluster"
|
||||
};
|
||||
println!(
|
||||
"Fixed blockhash is used: {} ({})",
|
||||
blockhash, blockhash_origin
|
||||
);
|
||||
blockhash_from_cluster = true;
|
||||
}
|
||||
}
|
||||
'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) {
|
||||
let now = Instant::now();
|
||||
@@ -1400,15 +1380,12 @@ pub fn process_ping(
|
||||
// Fetch a new blockhash every minute
|
||||
let new_blockhash = rpc_client.get_new_latest_blockhash(&blockhash)?;
|
||||
blockhash = new_blockhash;
|
||||
blockhash_transaction_count = 0;
|
||||
lamports = 0;
|
||||
blockhash_acquired = Instant::now();
|
||||
}
|
||||
|
||||
let seed =
|
||||
&format!("{}{}", blockhash_transaction_count, blockhash)[0..pubkey::MAX_SEED_LEN];
|
||||
let to = Pubkey::create_with_seed(&config.signers[0].pubkey(), seed, &system_program::id())
|
||||
.unwrap();
|
||||
blockhash_transaction_count += 1;
|
||||
let to = config.signers[0].pubkey();
|
||||
lamports += 1;
|
||||
|
||||
let build_message = |lamports| {
|
||||
let ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports);
|
||||
@@ -1431,11 +1408,7 @@ pub fn process_ping(
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_micros();
|
||||
if print_timestamp {
|
||||
format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
|
||||
};
|
||||
|
||||
match rpc_client.send_transaction(&tx) {
|
||||
@@ -1449,35 +1422,51 @@ pub fn process_ping(
|
||||
Ok(()) => {
|
||||
let elapsed_time_millis = elapsed_time.as_millis() as u64;
|
||||
confirmation_time.push_back(elapsed_time_millis);
|
||||
println!(
|
||||
"{}{}{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
|
||||
timestamp(),
|
||||
CHECK_MARK, lamports, seq, elapsed_time_millis, signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: true,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: Some(elapsed_time_millis),
|
||||
error: None,
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: Some(lamports),
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
confirmed_count += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{}{}Transaction failed: seq={:<3} error={:?} signature={}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
err,
|
||||
signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: None,
|
||||
error: Some(err.to_string()),
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if elapsed_time >= *timeout {
|
||||
println!(
|
||||
"{}{}Confirmation timeout: seq={:<3} signature={}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: None,
|
||||
error: None,
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1491,13 +1480,18 @@ pub fn process_ping(
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{}{}Submit failed: seq={:<3} error={:?}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
err
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: None,
|
||||
ms: None,
|
||||
error: Some(err.to_string()),
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
}
|
||||
}
|
||||
submit_count += 1;
|
||||
@@ -1507,28 +1501,34 @@ pub fn process_ping(
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("--- transaction statistics ---");
|
||||
println!(
|
||||
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
|
||||
submit_count,
|
||||
confirmed_count,
|
||||
(100. - f64::from(confirmed_count) / f64::from(submit_count) * 100.)
|
||||
);
|
||||
if !confirmation_time.is_empty() {
|
||||
let transaction_stats = CliPingTxStats {
|
||||
num_transactions: submit_count,
|
||||
num_transaction_confirmed: confirmed_count,
|
||||
};
|
||||
let confirmation_stats = if !confirmation_time.is_empty() {
|
||||
let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect();
|
||||
let dist = criterion_stats::Distribution::from(samples.into_boxed_slice());
|
||||
let mean = dist.mean();
|
||||
println!(
|
||||
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
|
||||
dist.min(),
|
||||
Some(CliPingConfirmationStats {
|
||||
min: dist.min(),
|
||||
mean,
|
||||
dist.max(),
|
||||
dist.std_dev(Some(mean))
|
||||
);
|
||||
}
|
||||
max: dist.max(),
|
||||
std_dev: dist.std_dev(Some(mean)),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok("".to_string())
|
||||
let cli_ping = CliPing {
|
||||
source_pubkey: config.signers[0].pubkey().to_string(),
|
||||
fixed_blockhash: fixed_blockhash.map(|_| blockhash.to_string()),
|
||||
blockhash_from_cluster,
|
||||
pings: cli_pings,
|
||||
transaction_stats,
|
||||
confirmation_stats,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&cli_ping))
|
||||
}
|
||||
|
||||
pub fn parse_logs(
|
||||
@@ -2305,7 +2305,6 @@ mod tests {
|
||||
parse_command(&test_ping, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Ping {
|
||||
lamports: 1,
|
||||
interval: Duration::from_secs(1),
|
||||
count: Some(2),
|
||||
timeout: Duration::from_secs(3),
|
||||
|
@@ -1991,7 +1991,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
|
||||
let mut program_data = Vec::new();
|
||||
file.read_to_end(&mut program_data)
|
||||
.map_err(|err| format!("Unable to read program file: {}", err))?;
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1);
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
|
||||
// Verify the program
|
||||
@@ -1999,10 +1999,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
|
||||
&program_data,
|
||||
Some(verifier::check),
|
||||
Config {
|
||||
reject_unresolved_syscalls: true,
|
||||
verify_mul64_imm_nonzero: false,
|
||||
verify_shift32_imm: true,
|
||||
reject_section_virtual_address_file_offset_mismatch: true,
|
||||
reject_broken_elfs: true,
|
||||
..Config::default()
|
||||
},
|
||||
register_syscalls(&mut invoke_context).unwrap(),
|
||||
|
@@ -1384,7 +1384,13 @@ pub fn process_stake_authorize(
|
||||
if let Some(authorized) = authorized {
|
||||
match authorization_type {
|
||||
StakeAuthorize::Staker => {
|
||||
check_current_authority(&authorized.staker, &authority.pubkey())?;
|
||||
// first check authorized withdrawer
|
||||
check_current_authority(&authorized.withdrawer, &authority.pubkey())
|
||||
.or_else(|_| {
|
||||
// ...then check authorized staker. If neither matches, error will
|
||||
// print the stake key as `expected`
|
||||
check_current_authority(&authorized.staker, &authority.pubkey())
|
||||
})?;
|
||||
}
|
||||
StakeAuthorize::Withdrawer => {
|
||||
check_current_authority(&authorized.withdrawer, &authority.pubkey())?;
|
||||
|
@@ -273,11 +273,14 @@ impl WalletSubCommands for App<'_, '_> {
|
||||
}
|
||||
|
||||
fn resolve_derived_address_program_id(matches: &ArgMatches<'_>, arg_name: &str) -> Option<Pubkey> {
|
||||
matches.value_of(arg_name).and_then(|v| match v {
|
||||
"NONCE" => Some(system_program::id()),
|
||||
"STAKE" => Some(stake::program::id()),
|
||||
"VOTE" => Some(solana_vote_program::id()),
|
||||
_ => pubkey_of(matches, arg_name),
|
||||
matches.value_of(arg_name).and_then(|v| {
|
||||
let upper = v.to_ascii_uppercase();
|
||||
match upper.as_str() {
|
||||
"NONCE" | "SYSTEM" => Some(system_program::id()),
|
||||
"STAKE" => Some(stake::program::id()),
|
||||
"VOTE" => Some(solana_vote_program::id()),
|
||||
_ => pubkey_of(matches, arg_name),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -238,6 +238,7 @@ fn full_battery_tests(
|
||||
#[test]
|
||||
#[allow(clippy::redundant_closure)]
|
||||
fn test_create_account_with_seed() {
|
||||
const ONE_SIG_FEE: f64 = 0.000005;
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
@@ -310,7 +311,7 @@ fn test_create_account_with_seed() {
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(4000.999999999),
|
||||
sol_to_lamports(4001.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
);
|
||||
@@ -381,12 +382,12 @@ fn test_create_account_with_seed() {
|
||||
process_command(&submit_config).unwrap();
|
||||
check_balance!(sol_to_lamports(241.0), &rpc_client, &nonce_address);
|
||||
check_balance!(
|
||||
sol_to_lamports(31.999999999),
|
||||
sol_to_lamports(32.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(4000.999999999),
|
||||
sol_to_lamports(4001.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
);
|
||||
|
@@ -78,7 +78,7 @@ fn test_cli_program_deploy_non_upgradeable() {
|
||||
assert_eq!(account0.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account0.owner, bpf_loader::id());
|
||||
assert!(account0.executable);
|
||||
let mut file = File::open(noop_path.to_str().unwrap().to_string()).unwrap();
|
||||
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
|
||||
let mut elf = Vec::new();
|
||||
file.read_to_end(&mut elf).unwrap();
|
||||
assert_eq!(account0.data, elf);
|
||||
|
@@ -18,6 +18,7 @@ use {
|
||||
solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
commitment_config::CommitmentConfig,
|
||||
fee::FeeStructure,
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
@@ -876,14 +877,15 @@ fn test_stake_authorize() {
|
||||
#[test]
|
||||
fn test_stake_authorize_with_fee_payer() {
|
||||
solana_logger::setup();
|
||||
const SIG_FEE: u64 = 42;
|
||||
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
|
||||
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
SIG_FEE,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
@@ -912,14 +914,14 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 100_000).unwrap();
|
||||
check_balance!(100_000, &rpc_client, &config.signers[0].pubkey());
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 100_000).unwrap();
|
||||
check_balance!(100_000, &rpc_client, &payer_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &payer_pubkey);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance!(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -934,7 +936,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
amount: SpendAmount::Some(1_000_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
@@ -945,8 +947,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance should be 50,000 - 1 stake account sig - 1 fee sig
|
||||
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
|
||||
// Assign authority with separate fee payer
|
||||
config.signers = vec![&default_signer, &payer_keypair];
|
||||
@@ -970,10 +971,10 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance has not changed, despite submitting the TX
|
||||
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
// `config_payer` however has paid `config`'s authority sig
|
||||
// and `config_payer`'s fee sig
|
||||
check_balance!(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
|
||||
check_balance!(5_000_000 - fee_two_sig, &rpc_client, &payer_pubkey);
|
||||
|
||||
// Assign authority with offline fee payer
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
@@ -1021,10 +1022,10 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config`'s balance again has not changed
|
||||
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
// `config_offline` however has paid 1 sig due to being both authority
|
||||
// and fee payer
|
||||
check_balance!(100_000 - SIG_FEE, &rpc_client, &offline_pubkey);
|
||||
check_balance!(5_000_000 - fee_one_sig, &rpc_client, &offline_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1058,12 +1059,17 @@ fn test_stake_split() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
|
||||
.unwrap();
|
||||
check_balance!(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&config,
|
||||
&config.signers[0].pubkey(),
|
||||
50_000_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance!(50_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance!(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
|
||||
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
let minimum_stake_balance = rpc_client
|
||||
@@ -1207,12 +1213,12 @@ fn test_stake_set_lockup() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 5_000_000)
|
||||
.unwrap();
|
||||
check_balance!(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance!(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
|
||||
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
let minimum_stake_balance = rpc_client
|
||||
|
@@ -16,6 +16,7 @@ use {
|
||||
solana_faucet::faucet::run_local_faucet,
|
||||
solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
fee::FeeStructure,
|
||||
native_token::sol_to_lamports,
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
@@ -29,6 +30,8 @@ use {
|
||||
#[test]
|
||||
fn test_transfer() {
|
||||
solana_logger::setup();
|
||||
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
|
||||
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -77,7 +80,11 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&sender_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||
|
||||
// Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
|
||||
@@ -98,7 +105,11 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
check_balance!(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&sender_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||
|
||||
let mut offline = CliConfig::recent_for_tests();
|
||||
@@ -154,7 +165,11 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(sol_to_lamports(0.5) - 1, &rpc_client, &offline_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(0.5) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&offline_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.5), &rpc_client, &recipient_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
@@ -172,7 +187,7 @@ fn test_transfer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - 3 - minimum_nonce_balance,
|
||||
sol_to_lamports(4.0) - fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
@@ -210,7 +225,7 @@ fn test_transfer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(
|
||||
sol_to_lamports(3.0) - 4 - minimum_nonce_balance,
|
||||
sol_to_lamports(3.0) - 2 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
@@ -235,7 +250,7 @@ fn test_transfer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(
|
||||
sol_to_lamports(3.0) - 5 - minimum_nonce_balance,
|
||||
sol_to_lamports(3.0) - 3 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
@@ -293,13 +308,18 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(sol_to_lamports(0.1) - 2, &rpc_client, &offline_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(0.1) - 2 * fee_one_sig,
|
||||
&rpc_client,
|
||||
&offline_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(2.9), &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_multisession_signing() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(2, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -329,7 +349,7 @@ fn test_transfer_multisession_signing() {
|
||||
&rpc_client,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
sol_to_lamports(1.0) + 3,
|
||||
sol_to_lamports(1.0) + 2 * fee,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance!(
|
||||
@@ -338,7 +358,7 @@ fn test_transfer_multisession_signing() {
|
||||
&offline_from_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(1.0) + 3,
|
||||
sol_to_lamports(1.0) + 2 * fee,
|
||||
&rpc_client,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
);
|
||||
@@ -438,7 +458,7 @@ fn test_transfer_multisession_signing() {
|
||||
&offline_from_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(1.0) + 1,
|
||||
sol_to_lamports(1.0) + fee,
|
||||
&rpc_client,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
);
|
||||
@@ -448,6 +468,7 @@ fn test_transfer_multisession_signing() {
|
||||
#[test]
|
||||
fn test_transfer_all() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(1, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -470,8 +491,8 @@ fn test_transfer_all() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
|
||||
check_balance!(50_000, &rpc_client, &sender_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 500_000).unwrap();
|
||||
check_balance!(500_000, &rpc_client, &sender_pubkey);
|
||||
check_balance!(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
@@ -495,7 +516,7 @@ fn test_transfer_all() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(0, &rpc_client, &sender_pubkey);
|
||||
check_balance!(49_999, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(500_000 - fee, &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -554,6 +575,7 @@ fn test_transfer_unfunded_recipient() {
|
||||
#[test]
|
||||
fn test_transfer_with_seed() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(1, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -612,7 +634,7 @@ fn test_transfer_with_seed() {
|
||||
derived_address_program_id: Some(derived_address_program_id),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance!(sol_to_lamports(1.0) - 1, &rpc_client, &sender_pubkey);
|
||||
check_balance!(sol_to_lamports(1.0) - fee, &rpc_client, &sender_pubkey);
|
||||
check_balance!(sol_to_lamports(5.0), &rpc_client, &recipient_pubkey);
|
||||
check_balance!(0, &rpc_client, &derived_address);
|
||||
}
|
||||
|
@@ -10,7 +10,8 @@ documentation = "https://docs.rs/solana-client-test"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.74"
|
||||
futures-util = "0.3.19"
|
||||
serde_json = "1.0.78"
|
||||
serial_test = "0.5.1"
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.10.0" }
|
||||
@@ -27,6 +28,7 @@ solana-test-validator = { path = "../test-validator", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
systemstat = "0.1.10"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
|
@@ -1,14 +1,16 @@
|
||||
use {
|
||||
futures_util::StreamExt,
|
||||
serde_json::{json, Value},
|
||||
serial_test::serial,
|
||||
solana_client::{
|
||||
nonblocking,
|
||||
pubsub_client::PubsubClient,
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
||||
RpcProgramAccountsConfig,
|
||||
},
|
||||
rpc_response::{RpcBlockUpdate, SlotInfo},
|
||||
rpc_response::SlotInfo,
|
||||
},
|
||||
solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path},
|
||||
solana_rpc::{
|
||||
@@ -34,7 +36,7 @@ use {
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_test_validator::TestValidator,
|
||||
solana_transaction_status::{TransactionDetails, UiTransactionEncoding},
|
||||
solana_transaction_status::{ConfirmedBlock, TransactionDetails, UiTransactionEncoding},
|
||||
std::{
|
||||
collections::HashSet,
|
||||
net::{IpAddr, SocketAddr},
|
||||
@@ -212,6 +214,7 @@ fn test_block_subscription() {
|
||||
..
|
||||
} = create_genesis_config(10_000);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
|
||||
// setup Blockstore
|
||||
@@ -225,6 +228,8 @@ fn test_block_subscription() {
|
||||
let keypair2 = Keypair::new();
|
||||
let keypair3 = Keypair::new();
|
||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
bank.transfer(rent_exempt_amount, &alice, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
@@ -276,23 +281,15 @@ fn test_block_subscription() {
|
||||
match maybe_actual {
|
||||
Ok(actual) => {
|
||||
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
|
||||
let legacy_block = versioned_block.into_legacy_block().unwrap();
|
||||
let block = legacy_block.clone().configure(
|
||||
UiTransactionEncoding::Json,
|
||||
TransactionDetails::Signatures,
|
||||
false,
|
||||
);
|
||||
let expected = RpcBlockUpdate {
|
||||
slot,
|
||||
block: Some(block),
|
||||
err: None,
|
||||
};
|
||||
let legacy_block = ConfirmedBlock::from(versioned_block)
|
||||
.into_legacy_block()
|
||||
.unwrap();
|
||||
let block = legacy_block.configure(
|
||||
UiTransactionEncoding::Json,
|
||||
TransactionDetails::Signatures,
|
||||
false,
|
||||
);
|
||||
assert_eq!(actual.value.slot, expected.slot);
|
||||
assert_eq!(actual.value.slot, slot);
|
||||
assert!(block.eq(&actual.value.block.unwrap()));
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -514,3 +511,86 @@ fn test_slot_subscription() {
|
||||
|
||||
assert_eq!(errors, [].to_vec());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_slot_subscription_async() {
|
||||
let sync_service = Arc::new(AtomicU64::new(0));
|
||||
let sync_client = Arc::clone(&sync_service);
|
||||
fn wait_until(atomic: &Arc<AtomicU64>, value: u64) {
|
||||
while atomic.load(Ordering::Relaxed) != value {
|
||||
sleep(Duration::from_millis(1))
|
||||
}
|
||||
}
|
||||
|
||||
let pubsub_addr = SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
rpc_port::DEFAULT_RPC_PUBSUB_PORT,
|
||||
);
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let optimistically_confirmed_bank =
|
||||
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
|
||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::default());
|
||||
let subscriptions = Arc::new(RpcSubscriptions::new_for_tests(
|
||||
&exit,
|
||||
max_complete_transaction_status_slot,
|
||||
bank_forks,
|
||||
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
||||
optimistically_confirmed_bank,
|
||||
));
|
||||
let (trigger, pubsub_service) =
|
||||
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr);
|
||||
sleep(Duration::from_millis(100));
|
||||
sync_service.store(1, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 2);
|
||||
subscriptions.notify_slot(1, 0, 0);
|
||||
sync_service.store(3, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 4);
|
||||
subscriptions.notify_slot(2, 1, 1);
|
||||
sync_service.store(5, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 6);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
trigger.cancel();
|
||||
pubsub_service.close().unwrap();
|
||||
});
|
||||
|
||||
wait_until(&sync_client, 1);
|
||||
let url = format!("ws://0.0.0.0:{}/", pubsub_addr.port());
|
||||
let pubsub_client = nonblocking::pubsub_client::PubsubClient::new(&url)
|
||||
.await
|
||||
.unwrap();
|
||||
let (mut notifications, unsubscribe) = pubsub_client.slot_subscribe().await.unwrap();
|
||||
sync_client.store(2, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_client, 3);
|
||||
assert_eq!(
|
||||
tokio::time::timeout(Duration::from_millis(25), notifications.next()).await,
|
||||
Ok(Some(SlotInfo {
|
||||
slot: 1,
|
||||
parent: 0,
|
||||
root: 0,
|
||||
}))
|
||||
);
|
||||
sync_client.store(4, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_client, 5);
|
||||
assert_eq!(
|
||||
tokio::time::timeout(Duration::from_millis(25), notifications.next()).await,
|
||||
Ok(Some(SlotInfo {
|
||||
slot: 2,
|
||||
parent: 1,
|
||||
root: 1,
|
||||
}))
|
||||
);
|
||||
sync_client.store(6, Ordering::Relaxed);
|
||||
|
||||
unsubscribe().await;
|
||||
}
|
||||
|
@@ -10,20 +10,22 @@ license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.52"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1.3.3"
|
||||
bs58 = "0.4.0"
|
||||
clap = "2.33.0"
|
||||
crossbeam-channel = "0.5"
|
||||
futures-util = "0.3.19"
|
||||
indicatif = "0.16.2"
|
||||
jsonrpc-core = "18.0.0"
|
||||
log = "0.4.14"
|
||||
rayon = "1.5.1"
|
||||
reqwest = { version = "0.11.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.4"
|
||||
serde = "1.0.133"
|
||||
reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.5"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.78"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
@@ -35,7 +37,9 @@ solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tungstenite = { version = "0.16.0", features = ["rustls-tls-webpki-roots"] }
|
||||
tokio-stream = "0.1.8"
|
||||
tokio-tungstenite = { version = "0.17.1", features = ["rustls-tls-webpki-roots"] }
|
||||
tungstenite = { version = "0.17.2", features = ["rustls-tls-webpki-roots"] }
|
||||
url = "2.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! The standard [`RpcSender`] over HTTP.
|
||||
//! Nonblocking [`RpcSender`] over HTTP.
|
||||
|
||||
use {
|
||||
crate::{
|
||||
@@ -8,6 +8,7 @@ use {
|
||||
rpc_response::RpcSimulateTransactionResult,
|
||||
rpc_sender::*,
|
||||
},
|
||||
async_trait::async_trait,
|
||||
log::*,
|
||||
reqwest::{
|
||||
self,
|
||||
@@ -25,40 +26,36 @@ use {
|
||||
};
|
||||
|
||||
pub struct HttpSender {
|
||||
client: Arc<reqwest::blocking::Client>,
|
||||
client: Arc<reqwest::Client>,
|
||||
url: String,
|
||||
request_id: AtomicU64,
|
||||
stats: RwLock<RpcTransportStats>,
|
||||
}
|
||||
|
||||
/// The standard [`RpcSender`] over HTTP.
|
||||
/// Nonblocking [`RpcSender`] over HTTP.
|
||||
impl HttpSender {
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899, as in
|
||||
/// "http://localhost:8899". The sender has a default timeout of 30 seconds.
|
||||
pub fn new(url: String) -> Self {
|
||||
pub fn new<U: ToString>(url: U) -> Self {
|
||||
Self::new_with_timeout(url, Duration::from_secs(30))
|
||||
}
|
||||
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899.
|
||||
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
|
||||
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
|
||||
// request to a different tokio thread to avoid this
|
||||
pub fn new_with_timeout<U: ToString>(url: U, timeout: Duration) -> Self {
|
||||
let client = Arc::new(
|
||||
tokio::task::block_in_place(move || {
|
||||
reqwest::blocking::Client::builder()
|
||||
.timeout(timeout)
|
||||
.build()
|
||||
})
|
||||
.expect("build rpc client"),
|
||||
reqwest::Client::builder()
|
||||
.timeout(timeout)
|
||||
.build()
|
||||
.expect("build rpc client"),
|
||||
);
|
||||
|
||||
Self {
|
||||
client,
|
||||
url,
|
||||
url: url.to_string(),
|
||||
request_id: AtomicU64::new(0),
|
||||
stats: RwLock::new(RpcTransportStats::default()),
|
||||
}
|
||||
@@ -66,9 +63,9 @@ impl HttpSender {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RpcErrorObject {
|
||||
code: i64,
|
||||
message: String,
|
||||
pub(crate) struct RpcErrorObject {
|
||||
pub code: i64,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
struct StatsUpdater<'a> {
|
||||
@@ -100,12 +97,17 @@ impl<'a> Drop for StatsUpdater<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RpcSender for HttpSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
self.stats.read().unwrap().clone()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
async fn send(
|
||||
&self,
|
||||
request: RpcRequest,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut stats_updater = StatsUpdater::new(&self.stats);
|
||||
|
||||
let request_id = self.request_id.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -113,18 +115,15 @@ impl RpcSender for HttpSender {
|
||||
|
||||
let mut too_many_requests_retries = 5;
|
||||
loop {
|
||||
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
|
||||
// request to a different tokio thread to avoid this
|
||||
let response = {
|
||||
let client = self.client.clone();
|
||||
let request_json = request_json.clone();
|
||||
tokio::task::block_in_place(move || {
|
||||
client
|
||||
.post(&self.url)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.body(request_json)
|
||||
.send()
|
||||
})
|
||||
client
|
||||
.post(&self.url)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.body(request_json)
|
||||
.send()
|
||||
.await
|
||||
}?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
@@ -155,8 +154,7 @@ impl RpcSender for HttpSender {
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
|
||||
let mut json =
|
||||
tokio::task::block_in_place(move || response.json::<serde_json::Value>())?;
|
||||
let mut json = response.json::<serde_json::Value>().await?;
|
||||
if json["error"].is_object() {
|
||||
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone()) {
|
||||
Ok(rpc_error_object) => {
|
||||
@@ -208,14 +206,16 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn http_sender_on_tokio_multi_thread() {
|
||||
let http_sender = HttpSender::new("http://localhost:1234".to_string());
|
||||
let _ = http_sender.send(RpcRequest::GetVersion, serde_json::Value::Null);
|
||||
let _ = http_sender
|
||||
.send(RpcRequest::GetVersion, serde_json::Value::Null)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[should_panic(expected = "can call blocking only when running on the multi-threaded runtime")]
|
||||
async fn http_sender_ontokio_current_thread_should_panic() {
|
||||
// RpcClient::new() will panic in the tokio current-thread runtime due to `tokio::task::block_in_place()` usage, and there
|
||||
// doesn't seem to be a way to detect whether the tokio runtime is multi_thread or current_thread...
|
||||
let _ = HttpSender::new("http://localhost:1234".to_string());
|
||||
async fn http_sender_on_tokio_current_thread() {
|
||||
let http_sender = HttpSender::new("http://localhost:1234".to_string());
|
||||
let _ = http_sender
|
||||
.send(RpcRequest::GetVersion, serde_json::Value::Null)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,9 @@ extern crate serde_derive;
|
||||
|
||||
pub mod blockhash_query;
|
||||
pub mod client_error;
|
||||
pub mod http_sender;
|
||||
pub mod mock_sender;
|
||||
pub(crate) mod http_sender;
|
||||
pub(crate) mod mock_sender;
|
||||
pub mod nonblocking;
|
||||
pub mod nonce_utils;
|
||||
pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
@@ -17,8 +18,15 @@ pub mod rpc_deprecated_config;
|
||||
pub mod rpc_filter;
|
||||
pub mod rpc_request;
|
||||
pub mod rpc_response;
|
||||
pub mod rpc_sender;
|
||||
pub(crate) mod rpc_sender;
|
||||
pub mod spinner;
|
||||
pub mod thin_client;
|
||||
pub mod tpu_client;
|
||||
pub mod transaction_executor;
|
||||
|
||||
pub mod mock_sender_for_cli {
|
||||
/// Magic `SIGNATURE` value used by `solana-cli` unit tests.
|
||||
/// Please don't use this constant.
|
||||
pub const SIGNATURE: &str =
|
||||
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! An [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
//! A nonblocking [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
|
||||
use {
|
||||
crate::{
|
||||
@@ -15,6 +15,7 @@ use {
|
||||
},
|
||||
rpc_sender::*,
|
||||
},
|
||||
async_trait::async_trait,
|
||||
serde_json::{json, Number, Value},
|
||||
solana_account_decoder::{UiAccount, UiAccountEncoding},
|
||||
solana_sdk::{
|
||||
@@ -40,8 +41,6 @@ use {
|
||||
};
|
||||
|
||||
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
|
||||
pub const SIGNATURE: &str =
|
||||
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
|
||||
|
||||
pub type Mocks = HashMap<RpcRequest, Value>;
|
||||
pub struct MockSender {
|
||||
@@ -75,24 +74,29 @@ pub struct MockSender {
|
||||
/// from [`RpcRequest`] to a JSON [`Value`] response, Any entries in this map
|
||||
/// override the default behavior for the given request.
|
||||
impl MockSender {
|
||||
pub fn new(url: String) -> Self {
|
||||
pub fn new<U: ToString>(url: U) -> Self {
|
||||
Self::new_with_mocks(url, Mocks::default())
|
||||
}
|
||||
|
||||
pub fn new_with_mocks(url: String, mocks: Mocks) -> Self {
|
||||
pub fn new_with_mocks<U: ToString>(url: U, mocks: Mocks) -> Self {
|
||||
Self {
|
||||
url,
|
||||
url: url.to_string(),
|
||||
mocks: RwLock::new(mocks),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RpcSender for MockSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
RpcTransportStats::default()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
async fn send(
|
||||
&self,
|
||||
request: RpcRequest,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(&request) {
|
||||
return Ok(value);
|
||||
}
|
||||
@@ -386,7 +390,7 @@ impl RpcSender for MockSender {
|
||||
"getBlocksWithLimit" => serde_json::to_value(vec![1, 2, 3])?,
|
||||
"getSignaturesForAddress" => {
|
||||
serde_json::to_value(vec![RpcConfirmedTransactionStatusWithSignature {
|
||||
signature: SIGNATURE.to_string(),
|
||||
signature: crate::mock_sender_for_cli::SIGNATURE.to_string(),
|
||||
slot: 123,
|
||||
err: None,
|
||||
memo: None,
|
||||
@@ -435,7 +439,7 @@ impl RpcSender for MockSender {
|
||||
value: vec![Value::Null, Value::Null]
|
||||
})?,
|
||||
"getProgramAccounts" => {
|
||||
let pubkey = Pubkey::from_str(&PUBKEY.to_string()).unwrap();
|
||||
let pubkey = Pubkey::from_str(PUBKEY).unwrap();
|
||||
let account = Account {
|
||||
lamports: 1_000_000,
|
||||
data: vec![],
|
||||
|
2
client/src/nonblocking/mod.rs
Normal file
2
client/src/nonblocking/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_client;
|
341
client/src/nonblocking/pubsub_client.rs
Normal file
341
client/src/nonblocking/pubsub_client.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
use {
|
||||
crate::{
|
||||
http_sender::RpcErrorObject,
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
||||
RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
|
||||
RpcTransactionLogsFilter,
|
||||
},
|
||||
rpc_response::{
|
||||
Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse,
|
||||
RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
|
||||
},
|
||||
},
|
||||
futures_util::{
|
||||
future::{ready, BoxFuture, FutureExt},
|
||||
sink::SinkExt,
|
||||
stream::{BoxStream, StreamExt},
|
||||
},
|
||||
log::*,
|
||||
serde::de::DeserializeOwned,
|
||||
serde_json::{json, Map, Value},
|
||||
solana_account_decoder::UiAccount,
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
|
||||
std::collections::BTreeMap,
|
||||
thiserror::Error,
|
||||
tokio::{
|
||||
net::TcpStream,
|
||||
sync::{mpsc, oneshot},
|
||||
task::JoinHandle,
|
||||
time::{sleep, Duration},
|
||||
},
|
||||
tokio_stream::wrappers::UnboundedReceiverStream,
|
||||
tokio_tungstenite::{
|
||||
connect_async,
|
||||
tungstenite::{
|
||||
protocol::frame::{coding::CloseCode, CloseFrame},
|
||||
Message,
|
||||
},
|
||||
MaybeTlsStream, WebSocketStream,
|
||||
},
|
||||
url::Url,
|
||||
};
|
||||
|
||||
pub type PubsubClientResult<T = ()> = Result<T, PubsubClientError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PubsubClientError {
|
||||
#[error("url parse error")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("unable to connect to server")]
|
||||
ConnectionError(tokio_tungstenite::tungstenite::Error),
|
||||
|
||||
#[error("websocket error")]
|
||||
WsError(#[from] tokio_tungstenite::tungstenite::Error),
|
||||
|
||||
#[error("connection closed (({0})")]
|
||||
ConnectionClosed(String),
|
||||
|
||||
#[error("json parse error")]
|
||||
JsonParseError(#[from] serde_json::error::Error),
|
||||
|
||||
#[error("subscribe failed: {reason}")]
|
||||
SubscribeFailed { reason: String, message: String },
|
||||
}
|
||||
|
||||
type UnsubscribeFn = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
|
||||
type SubscribeResponseMsg =
|
||||
Result<(mpsc::UnboundedReceiver<Value>, UnsubscribeFn), PubsubClientError>;
|
||||
type SubscribeRequestMsg = (String, Value, oneshot::Sender<SubscribeResponseMsg>);
|
||||
type SubscribeResult<'a, T> = PubsubClientResult<(BoxStream<'a, T>, UnsubscribeFn)>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PubsubClient {
|
||||
subscribe_tx: mpsc::UnboundedSender<SubscribeRequestMsg>,
|
||||
shutdown_tx: oneshot::Sender<()>,
|
||||
ws: JoinHandle<PubsubClientResult>,
|
||||
}
|
||||
|
||||
impl PubsubClient {
|
||||
pub async fn new(url: &str) -> PubsubClientResult<Self> {
|
||||
let url = Url::parse(url)?;
|
||||
let (ws, _response) = connect_async(url)
|
||||
.await
|
||||
.map_err(PubsubClientError::ConnectionError)?;
|
||||
|
||||
let (subscribe_tx, subscribe_rx) = mpsc::unbounded_channel();
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
|
||||
Ok(Self {
|
||||
subscribe_tx,
|
||||
shutdown_tx,
|
||||
ws: tokio::spawn(PubsubClient::run_ws(ws, subscribe_rx, shutdown_rx)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) -> PubsubClientResult {
|
||||
let _ = self.shutdown_tx.send(());
|
||||
self.ws.await.unwrap() // WS future should not be cancelled or panicked
|
||||
}
|
||||
|
||||
async fn subscribe<'a, T>(&self, operation: &str, params: Value) -> SubscribeResult<'a, T>
|
||||
where
|
||||
T: DeserializeOwned + Send + 'a,
|
||||
{
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.subscribe_tx
|
||||
.send((operation.to_string(), params, response_tx))
|
||||
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))?;
|
||||
|
||||
let (notifications, unsubscribe) = response_rx
|
||||
.await
|
||||
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))??;
|
||||
Ok((
|
||||
UnboundedReceiverStream::new(notifications)
|
||||
.filter_map(|value| ready(serde_json::from_value::<T>(value).ok()))
|
||||
.boxed(),
|
||||
unsubscribe,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn account_subscribe(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
config: Option<RpcAccountInfoConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<UiAccount>> {
|
||||
let params = json!([pubkey.to_string(), config]);
|
||||
self.subscribe("account", params).await
|
||||
}
|
||||
|
||||
pub async fn block_subscribe(
|
||||
&self,
|
||||
filter: RpcBlockSubscribeFilter,
|
||||
config: Option<RpcBlockSubscribeConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcBlockUpdate>> {
|
||||
self.subscribe("block", json!([filter, config])).await
|
||||
}
|
||||
|
||||
pub async fn logs_subscribe(
|
||||
&self,
|
||||
filter: RpcTransactionLogsFilter,
|
||||
config: RpcTransactionLogsConfig,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcLogsResponse>> {
|
||||
self.subscribe("logs", json!([filter, config])).await
|
||||
}
|
||||
|
||||
pub async fn program_subscribe(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
config: Option<RpcProgramAccountsConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcKeyedAccount>> {
|
||||
let params = json!([pubkey.to_string(), config]);
|
||||
self.subscribe("program", params).await
|
||||
}
|
||||
|
||||
pub async fn vote_subscribe(&self) -> SubscribeResult<'_, RpcVote> {
|
||||
self.subscribe("vote", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn root_subscribe(&self) -> SubscribeResult<'_, Slot> {
|
||||
self.subscribe("root", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn signature_subscribe(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
config: Option<RpcSignatureSubscribeConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcSignatureResult>> {
|
||||
let params = json!([signature.to_string(), config]);
|
||||
self.subscribe("signature", params).await
|
||||
}
|
||||
|
||||
pub async fn slot_subscribe(&self) -> SubscribeResult<'_, SlotInfo> {
|
||||
self.subscribe("slot", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn slot_updates_subscribe(&self) -> SubscribeResult<'_, SlotUpdate> {
|
||||
self.subscribe("slotsUpdates", json!([])).await
|
||||
}
|
||||
|
||||
async fn run_ws(
|
||||
mut ws: WebSocketStream<MaybeTlsStream<TcpStream>>,
|
||||
mut subscribe_rx: mpsc::UnboundedReceiver<SubscribeRequestMsg>,
|
||||
mut shutdown_rx: oneshot::Receiver<()>,
|
||||
) -> PubsubClientResult {
|
||||
let mut request_id: u64 = 0;
|
||||
|
||||
let mut requests_subscribe = BTreeMap::new();
|
||||
let mut requests_unsubscribe = BTreeMap::<u64, oneshot::Sender<()>>::new();
|
||||
let mut subscriptions = BTreeMap::new();
|
||||
let (unsubscribe_tx, mut unsubscribe_rx) = mpsc::unbounded_channel();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Send close on shutdown signal
|
||||
_ = (&mut shutdown_rx) => {
|
||||
let frame = CloseFrame { code: CloseCode::Normal, reason: "".into() };
|
||||
ws.send(Message::Close(Some(frame))).await?;
|
||||
ws.flush().await?;
|
||||
break;
|
||||
},
|
||||
// Send `Message::Ping` each 10s if no any other communication
|
||||
() = sleep(Duration::from_secs(10)) => {
|
||||
ws.send(Message::Ping(Vec::new())).await?;
|
||||
},
|
||||
// Read message for subscribe
|
||||
Some((operation, params, response_tx)) = subscribe_rx.recv() => {
|
||||
request_id += 1;
|
||||
let method = format!("{}Subscribe", operation);
|
||||
let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":params}).to_string();
|
||||
ws.send(Message::Text(text)).await?;
|
||||
requests_subscribe.insert(request_id, (operation, response_tx));
|
||||
},
|
||||
// Read message for unsubscribe
|
||||
Some((operation, sid, response_tx)) = unsubscribe_rx.recv() => {
|
||||
subscriptions.remove(&sid);
|
||||
request_id += 1;
|
||||
let method = format!("{}Unsubscribe", operation);
|
||||
let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":[sid]}).to_string();
|
||||
ws.send(Message::Text(text)).await?;
|
||||
requests_unsubscribe.insert(request_id, response_tx);
|
||||
},
|
||||
// Read incoming WebSocket message
|
||||
next_msg = ws.next() => {
|
||||
let msg = match next_msg {
|
||||
Some(msg) => msg?,
|
||||
None => break,
|
||||
};
|
||||
trace!("ws.next(): {:?}", &msg);
|
||||
|
||||
// Get text from the message
|
||||
let text = match msg {
|
||||
Message::Text(text) => text,
|
||||
Message::Binary(_data) => continue, // Ignore
|
||||
Message::Ping(data) => {
|
||||
ws.send(Message::Pong(data)).await?;
|
||||
continue
|
||||
},
|
||||
Message::Pong(_data) => continue,
|
||||
Message::Close(_frame) => break,
|
||||
Message::Frame(_frame) => continue,
|
||||
};
|
||||
|
||||
|
||||
let mut json: Map<String, Value> = serde_json::from_str(&text)?;
|
||||
|
||||
// Subscribe/Unsubscribe response, example:
|
||||
// `{"jsonrpc":"2.0","result":5308752,"id":1}`
|
||||
if let Some(id) = json.get("id") {
|
||||
let id = id.as_u64().ok_or_else(|| {
|
||||
PubsubClientError::SubscribeFailed { reason: "invalid `id` field".into(), message: text.clone() }
|
||||
})?;
|
||||
|
||||
let err = json.get("error").map(|error_object| {
|
||||
match serde_json::from_value::<RpcErrorObject>(error_object.clone()) {
|
||||
Ok(rpc_error_object) => {
|
||||
format!("{} ({})", rpc_error_object.message, rpc_error_object.code)
|
||||
}
|
||||
Err(err) => format!(
|
||||
"Failed to deserialize RPC error response: {} [{}]",
|
||||
serde_json::to_string(error_object).unwrap(),
|
||||
err
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(response_tx) = requests_unsubscribe.remove(&id) {
|
||||
let _ = response_tx.send(()); // do not care if receiver is closed
|
||||
} else if let Some((operation, response_tx)) = requests_subscribe.remove(&id) {
|
||||
match err {
|
||||
Some(reason) => {
|
||||
let _ = response_tx.send(Err(PubsubClientError::SubscribeFailed { reason, message: text.clone()}));
|
||||
},
|
||||
None => {
|
||||
// Subscribe Id
|
||||
let sid = json.get("result").and_then(Value::as_u64).ok_or_else(|| {
|
||||
PubsubClientError::SubscribeFailed { reason: "invalid `result` field".into(), message: text.clone() }
|
||||
})?;
|
||||
|
||||
// Create notifications channel and unsubscribe function
|
||||
let (notifications_tx, notifications_rx) = mpsc::unbounded_channel();
|
||||
let unsubscribe_tx = unsubscribe_tx.clone();
|
||||
let unsubscribe = Box::new(move || async move {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
// do nothing if ws already closed
|
||||
if unsubscribe_tx.send((operation, sid, response_tx)).is_ok() {
|
||||
let _ = response_rx.await; // channel can be closed only if ws is closed
|
||||
}
|
||||
}.boxed());
|
||||
|
||||
if response_tx.send(Ok((notifications_rx, unsubscribe))).is_err() {
|
||||
break;
|
||||
}
|
||||
subscriptions.insert(sid, notifications_tx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Unknown request id: {}", id);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Notification, example:
|
||||
// `{"jsonrpc":"2.0","method":"logsNotification","params":{"result":{...},"subscription":3114862}}`
|
||||
if let Some(Value::Object(params)) = json.get_mut("params") {
|
||||
if let Some(sid) = params.get("subscription").and_then(Value::as_u64) {
|
||||
let mut unsubscribe_required = false;
|
||||
|
||||
if let Some(notifications_tx) = subscriptions.get(&sid) {
|
||||
if let Some(result) = params.remove("result") {
|
||||
if notifications_tx.send(result).is_err() {
|
||||
unsubscribe_required = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsubscribe_required = true;
|
||||
}
|
||||
|
||||
if unsubscribe_required {
|
||||
if let Some(Value::String(method)) = json.remove("method") {
|
||||
if let Some(operation) = method.strip_suffix("Notification") {
|
||||
let (response_tx, _response_rx) = oneshot::channel();
|
||||
let _ = unsubscribe_tx.send((operation.to_string(), sid, response_tx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// see client-test/test/client.rs
|
||||
}
|
5064
client/src/nonblocking/rpc_client.rs
Normal file
5064
client/src/nonblocking/rpc_client.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,18 +43,16 @@ pub fn sample_txs<T>(
|
||||
total_elapsed = start_time.elapsed();
|
||||
let elapsed = now.elapsed();
|
||||
now = Instant::now();
|
||||
let mut txs;
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::processed()) {
|
||||
Err(e) => {
|
||||
// ThinClient with multiple options should pick a better one now.
|
||||
info!("Couldn't get transaction count {:?}", e);
|
||||
sleep(Duration::from_secs(sample_period));
|
||||
continue;
|
||||
}
|
||||
Ok(tx_count) => {
|
||||
txs = tx_count;
|
||||
}
|
||||
}
|
||||
let mut txs =
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::processed()) {
|
||||
Err(e) => {
|
||||
// ThinClient with multiple options should pick a better one now.
|
||||
info!("Couldn't get transaction count {:?}", e);
|
||||
sleep(Duration::from_secs(sample_period));
|
||||
continue;
|
||||
}
|
||||
Ok(tx_count) => tx_count,
|
||||
};
|
||||
|
||||
if txs < last_txs {
|
||||
info!("Expected txs({}) >= last_txs({})", txs, last_txs);
|
||||
|
@@ -615,5 +615,5 @@ impl PubsubClient {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// see core/tests/client.rs#test_slot_subscription()
|
||||
// see client-test/test/client.rs
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -290,6 +290,8 @@ pub struct RpcIdentity {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcVote {
|
||||
/// Vote account address, as base-58 encoded string
|
||||
pub vote_pubkey: String,
|
||||
pub slots: Vec<Slot>,
|
||||
pub hash: String,
|
||||
pub timestamp: Option<UnixTimestamp>,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
//! A transport for RPC calls.
|
||||
|
||||
use {
|
||||
crate::{client_error::Result, rpc_request::RpcRequest},
|
||||
async_trait::async_trait,
|
||||
std::time::Duration,
|
||||
};
|
||||
|
||||
@@ -26,10 +26,14 @@ pub struct RpcTransportStats {
|
||||
/// It is typically implemented by [`HttpSender`] in production, and
|
||||
/// [`MockSender`] in unit tests.
|
||||
///
|
||||
/// [`RpcClient`]: crate::rpc_client::RpcClient
|
||||
/// [`HttpSender`]: crate::http_sender::HttpSender
|
||||
/// [`MockSender`]: crate::mock_sender::MockSender
|
||||
pub trait RpcSender {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value>;
|
||||
#[async_trait]
|
||||
pub(crate) trait RpcSender {
|
||||
async fn send(
|
||||
&self,
|
||||
request: RpcRequest,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value>;
|
||||
fn get_transport_stats(&self) -> RpcTransportStats;
|
||||
}
|
||||
|
@@ -31,9 +31,10 @@ rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
rayon = "1.5.1"
|
||||
retain_mut = "0.1.5"
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.0" }
|
||||
solana-bloom = { path = "../bloom", version = "=1.10.0" }
|
||||
solana-accountsdb-plugin-manager = { path = "../accountsdb-plugin-manager", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-entry = { path = "../entry", version = "=1.10.0" }
|
||||
@@ -68,9 +69,9 @@ jsonrpc-core-client = { version = "18.0.0", features = ["ipc", "ws"] }
|
||||
jsonrpc-derive = "18.0.0"
|
||||
jsonrpc-pubsub = "18.0.0"
|
||||
matches = "0.1.9"
|
||||
raptorq = "1.6.4"
|
||||
reqwest = { version = "0.11.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde_json = "1.0.74"
|
||||
raptorq = "1.6.5"
|
||||
reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde_json = "1.0.78"
|
||||
serial_test = "0.5.1"
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-program-runtime = { path = "../program-runtime", version = "=1.10.0" }
|
||||
@@ -79,6 +80,9 @@ solana-version = { path = "../version", version = "=1.10.0" }
|
||||
static_assertions = "1.1.0"
|
||||
systemstat = "0.1.10"
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
sysctl = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.4"
|
||||
|
||||
|
@@ -10,7 +10,7 @@ use {
|
||||
rayon::prelude::*,
|
||||
solana_core::{
|
||||
banking_stage::{BankingStage, BankingStageStats},
|
||||
packet_deduper::PacketDeduper,
|
||||
leader_slot_banking_stage_metrics::LeaderSlotMetricsTracker,
|
||||
qos_service::QosService,
|
||||
},
|
||||
solana_entry::entry::{next_hash, Entry},
|
||||
@@ -71,7 +71,7 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, _signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
create_test_recorder(&bank, &blockstore, None, None);
|
||||
|
||||
let recorder = poh_recorder.lock().unwrap().recorder();
|
||||
|
||||
@@ -99,6 +99,7 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
&BankingStageStats::default(),
|
||||
&recorder,
|
||||
&QosService::new(Arc::new(RwLock::new(CostModel::default())), 1),
|
||||
&mut LeaderSlotMetricsTracker::new(0),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -214,7 +215,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
create_test_recorder(&bank, &blockstore, None, None);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
@@ -222,7 +223,6 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (s, _r) = unbounded();
|
||||
let packet_deduper = PacketDeduper::default();
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -232,7 +232,6 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
None,
|
||||
s,
|
||||
Arc::new(RwLock::new(CostModel::default())),
|
||||
packet_deduper.clone(),
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
@@ -267,7 +266,6 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
packet_deduper.reset();
|
||||
trace!(
|
||||
"time: {} checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
|
@@ -9,7 +9,10 @@ use {
|
||||
log::*,
|
||||
rand::{thread_rng, Rng},
|
||||
solana_core::{sigverify::TransactionSigVerifier, sigverify_stage::SigVerifyStage},
|
||||
solana_perf::{packet::to_packet_batches, test_tx::test_tx},
|
||||
solana_perf::{
|
||||
packet::{to_packet_batches, PacketBatch},
|
||||
test_tx::test_tx,
|
||||
},
|
||||
solana_sdk::{
|
||||
hash::Hash,
|
||||
signature::{Keypair, Signer},
|
||||
@@ -49,11 +52,16 @@ fn run_bench_packet_discard(num_ips: usize, bencher: &mut Bencher) {
|
||||
|
||||
bencher.iter(move || {
|
||||
SigVerifyStage::discard_excess_packets(&mut batches, 10_000);
|
||||
let mut num_packets = 0;
|
||||
for batch in batches.iter_mut() {
|
||||
for p in batch.packets.iter_mut() {
|
||||
if !p.meta.discard() {
|
||||
num_packets += 1;
|
||||
}
|
||||
p.meta.set_discard(false);
|
||||
}
|
||||
}
|
||||
assert_eq!(num_packets, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,18 +76,46 @@ fn bench_packet_discard_single_sender(bencher: &mut Bencher) {
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
let (packet_s, packet_r) = unbounded();
|
||||
let (verified_s, verified_r) = unbounded();
|
||||
let verifier = TransactionSigVerifier::default();
|
||||
let stage = SigVerifyStage::new(packet_r, verified_s, verifier);
|
||||
fn bench_packet_discard_mixed_senders(bencher: &mut Bencher) {
|
||||
const SIZE: usize = 30 * 1000;
|
||||
const CHUNK_SIZE: usize = 1024;
|
||||
fn new_rand_addr<R: Rng>(rng: &mut R) -> std::net::IpAddr {
|
||||
let mut addr = [0u16; 8];
|
||||
rng.fill(&mut addr);
|
||||
std::net::IpAddr::from(addr)
|
||||
}
|
||||
let mut rng = thread_rng();
|
||||
let mut batches = to_packet_batches(&vec![test_tx(); SIZE], CHUNK_SIZE);
|
||||
let spam_addr = new_rand_addr(&mut rng);
|
||||
for batch in batches.iter_mut() {
|
||||
for packet in batch.packets.iter_mut() {
|
||||
// One spam address, ~1000 unique addresses.
|
||||
packet.meta.addr = if rng.gen_ratio(1, 30) {
|
||||
new_rand_addr(&mut rng)
|
||||
} else {
|
||||
spam_addr
|
||||
}
|
||||
}
|
||||
}
|
||||
bencher.iter(move || {
|
||||
SigVerifyStage::discard_excess_packets(&mut batches, 10_000);
|
||||
let mut num_packets = 0;
|
||||
for batch in batches.iter_mut() {
|
||||
for packet in batch.packets.iter_mut() {
|
||||
if !packet.meta.discard() {
|
||||
num_packets += 1;
|
||||
}
|
||||
packet.meta.set_discard(false);
|
||||
}
|
||||
}
|
||||
assert_eq!(num_packets, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
fn gen_batches(use_same_tx: bool) -> Vec<PacketBatch> {
|
||||
let len = 4096;
|
||||
let use_same_tx = true;
|
||||
let chunk_size = 1024;
|
||||
let mut batches = if use_same_tx {
|
||||
if use_same_tx {
|
||||
let tx = test_tx();
|
||||
to_packet_batches(&vec![tx; len], chunk_size)
|
||||
} else {
|
||||
@@ -97,14 +133,28 @@ fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
})
|
||||
.collect();
|
||||
to_packet_batches(&txs, chunk_size)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"starting... generation took: {} ms batches: {}",
|
||||
duration_as_ms(&now.elapsed()),
|
||||
batches.len()
|
||||
);
|
||||
#[bench]
|
||||
fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
trace!("start");
|
||||
let (packet_s, packet_r) = unbounded();
|
||||
let (verified_s, verified_r) = unbounded();
|
||||
let verifier = TransactionSigVerifier::default();
|
||||
let stage = SigVerifyStage::new(packet_r, verified_s, verifier);
|
||||
|
||||
let use_same_tx = true;
|
||||
bencher.iter(move || {
|
||||
let now = Instant::now();
|
||||
let mut batches = gen_batches(use_same_tx);
|
||||
trace!(
|
||||
"starting... generation took: {} ms batches: {}",
|
||||
duration_as_ms(&now.elapsed()),
|
||||
batches.len()
|
||||
);
|
||||
|
||||
let mut sent_len = 0;
|
||||
for _ in 0..batches.len() {
|
||||
if let Some(batch) = batches.pop() {
|
||||
@@ -120,7 +170,7 @@ fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
received += v.packets.len();
|
||||
batches.push(v);
|
||||
}
|
||||
if received >= sent_len {
|
||||
if use_same_tx || received >= sent_len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -260,7 +260,7 @@ impl BroadcastRun for BroadcastDuplicatesRun {
|
||||
.map(|(node, _)| node)
|
||||
.collect();
|
||||
|
||||
// Creat cluster partition.
|
||||
// Create cluster partition.
|
||||
let cluster_partition: HashSet<Pubkey> = {
|
||||
let mut cumilative_stake = 0;
|
||||
let epoch = root_bank.get_leader_schedule_epoch(slot);
|
||||
|
@@ -25,8 +25,13 @@ use {
|
||||
rpc_subscriptions::RpcSubscriptions,
|
||||
},
|
||||
solana_runtime::{
|
||||
bank::Bank, bank_forks::BankForks, commitment::VOTE_THRESHOLD_SIZE,
|
||||
epoch_stakes::EpochStakes, vote_sender_types::ReplayVoteReceiver,
|
||||
bank::Bank,
|
||||
bank_forks::BankForks,
|
||||
commitment::VOTE_THRESHOLD_SIZE,
|
||||
epoch_stakes::EpochStakes,
|
||||
vote_parser::{self, ParsedVote},
|
||||
vote_sender_types::ReplayVoteReceiver,
|
||||
vote_transaction::VoteTransaction,
|
||||
},
|
||||
solana_sdk::{
|
||||
clock::{Slot, DEFAULT_MS_PER_SLOT, DEFAULT_TICKS_PER_SLOT},
|
||||
@@ -36,10 +41,6 @@ use {
|
||||
slot_hashes,
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_vote_program::{
|
||||
vote_state::VoteTransaction,
|
||||
vote_transaction::{self, ParsedVote},
|
||||
},
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::repeat,
|
||||
@@ -99,12 +100,6 @@ pub struct VoteTracker {
|
||||
}
|
||||
|
||||
impl VoteTracker {
|
||||
pub(crate) fn new(root_bank: &Bank) -> Self {
|
||||
let vote_tracker = VoteTracker::default();
|
||||
vote_tracker.progress_with_new_root_bank(root_bank);
|
||||
vote_tracker
|
||||
}
|
||||
|
||||
fn get_or_insert_slot_tracker(&self, slot: Slot) -> Arc<RwLock<SlotVoteTracker>> {
|
||||
if let Some(slot_vote_tracker) = self.slot_vote_trackers.read().unwrap().get(&slot) {
|
||||
return slot_vote_tracker.clone();
|
||||
@@ -296,7 +291,11 @@ impl ClusterInfoVoteListener {
|
||||
let mut packet_batches = packet::to_packet_batches(&votes, 1);
|
||||
|
||||
// Votes should already be filtered by this point.
|
||||
sigverify::ed25519_verify_cpu(&mut packet_batches, /*reject_non_vote=*/ false);
|
||||
sigverify::ed25519_verify_cpu(
|
||||
&mut packet_batches,
|
||||
/*reject_non_vote=*/ false,
|
||||
votes.len(),
|
||||
);
|
||||
let root_bank = bank_forks.read().unwrap().root_bank();
|
||||
let epoch_schedule = root_bank.epoch_schedule();
|
||||
votes
|
||||
@@ -308,7 +307,7 @@ impl ClusterInfoVoteListener {
|
||||
!packet_batch.packets[0].meta.discard()
|
||||
})
|
||||
.filter_map(|(tx, packet_batch)| {
|
||||
let (vote_account_key, vote, _) = vote_transaction::parse_vote_transaction(&tx)?;
|
||||
let (vote_account_key, vote, _) = vote_parser::parse_vote_transaction(&tx)?;
|
||||
let slot = vote.last_voted_slot()?;
|
||||
let epoch = epoch_schedule.get_epoch(slot);
|
||||
let authorized_voter = root_bank
|
||||
@@ -535,17 +534,14 @@ impl ClusterInfoVoteListener {
|
||||
let mut sel = Select::new();
|
||||
sel.recv(gossip_vote_txs_receiver);
|
||||
sel.recv(replay_votes_receiver);
|
||||
let mut remaining_wait_time = 200;
|
||||
loop {
|
||||
if remaining_wait_time == 0 {
|
||||
break;
|
||||
}
|
||||
let mut remaining_wait_time = Duration::from_millis(200);
|
||||
while remaining_wait_time > Duration::ZERO {
|
||||
let start = Instant::now();
|
||||
// Wait for one of the receivers to be ready. `ready_timeout`
|
||||
// will return if channels either have something, or are
|
||||
// disconnected. `ready_timeout` can wake up spuriously,
|
||||
// hence the loop
|
||||
let _ = sel.ready_timeout(Duration::from_millis(remaining_wait_time))?;
|
||||
let _ = sel.ready_timeout(remaining_wait_time)?;
|
||||
|
||||
// Should not early return from this point onwards until `process_votes()`
|
||||
// returns below to avoid missing any potential `optimistic_confirmed_slots`
|
||||
@@ -563,10 +559,8 @@ impl ClusterInfoVoteListener {
|
||||
bank_notification_sender,
|
||||
cluster_confirmed_slot_sender,
|
||||
));
|
||||
} else {
|
||||
remaining_wait_time = remaining_wait_time
|
||||
.saturating_sub(std::cmp::max(start.elapsed().as_millis() as u64, 1));
|
||||
}
|
||||
remaining_wait_time = remaining_wait_time.saturating_sub(start.elapsed());
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
@@ -683,7 +677,7 @@ impl ClusterInfoVoteListener {
|
||||
}
|
||||
|
||||
if is_new_vote {
|
||||
subscriptions.notify_vote(vote);
|
||||
subscriptions.notify_vote(*vote_pubkey, vote);
|
||||
let _ = verified_vote_sender.send((*vote_pubkey, vote_slots));
|
||||
}
|
||||
}
|
||||
@@ -705,7 +699,7 @@ impl ClusterInfoVoteListener {
|
||||
// Process votes from gossip and ReplayStage
|
||||
let votes = gossip_vote_txs
|
||||
.iter()
|
||||
.filter_map(vote_transaction::parse_vote_transaction)
|
||||
.filter_map(vote_parser::parse_vote_transaction)
|
||||
.zip(repeat(/*is_gossip:*/ true))
|
||||
.chain(replayed_votes.into_iter().zip(repeat(/*is_gossip:*/ false)));
|
||||
for ((vote_pubkey, vote, _), is_gossip) in votes {
|
||||
@@ -823,7 +817,7 @@ mod tests {
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
},
|
||||
solana_vote_program::vote_state::Vote,
|
||||
solana_vote_program::{vote_state::Vote, vote_transaction},
|
||||
std::{
|
||||
collections::BTreeSet,
|
||||
iter::repeat_with,
|
||||
@@ -1369,7 +1363,7 @@ mod tests {
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||
let vote_tracker = VoteTracker::new(&bank);
|
||||
let vote_tracker = VoteTracker::default();
|
||||
let optimistically_confirmed_bank =
|
||||
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
|
||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::default());
|
||||
@@ -1476,7 +1470,7 @@ mod tests {
|
||||
vec![100; validator_voting_keypairs.len()],
|
||||
);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
let vote_tracker = VoteTracker::new(&bank);
|
||||
let vote_tracker = VoteTracker::default();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||
|
@@ -2,15 +2,13 @@ use {
|
||||
crate::{broadcast_stage::BroadcastStage, retransmit_stage::RetransmitStage},
|
||||
itertools::Itertools,
|
||||
lru::LruCache,
|
||||
rand::{Rng, SeedableRng},
|
||||
rand::SeedableRng,
|
||||
rand_chacha::ChaChaRng,
|
||||
solana_gossip::{
|
||||
cluster_info::{compute_retransmit_peers, ClusterInfo},
|
||||
contact_info::ContactInfo,
|
||||
crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
|
||||
weighted_shuffle::{
|
||||
weighted_best, weighted_sample_single, weighted_shuffle, WeightedShuffle,
|
||||
},
|
||||
weighted_shuffle::{weighted_best, weighted_shuffle, WeightedShuffle},
|
||||
},
|
||||
solana_ledger::shred::Shred,
|
||||
solana_runtime::bank::Bank,
|
||||
@@ -51,13 +49,13 @@ pub struct ClusterNodes<T> {
|
||||
// All staked nodes + other known tvu-peers + the node itself;
|
||||
// sorted by (stake, pubkey) in descending order.
|
||||
nodes: Vec<Node>,
|
||||
// Cumulative stakes (excluding the node itself), used for sampling
|
||||
// broadcast peers.
|
||||
cumulative_weights: Vec<u64>,
|
||||
// Reverse index from nodes pubkey to their index in self.nodes.
|
||||
index: HashMap<Pubkey, /*index:*/ usize>,
|
||||
weighted_shuffle: WeightedShuffle</*stake:*/ u64>,
|
||||
// Weights and indices for sampling peers. weighted_{shuffle,best} expect
|
||||
// weights >= 1. For backward compatibility we use max(1, stake) for
|
||||
// weights and exclude nodes with no contact-info.
|
||||
index: Vec<(/*weight:*/ u64, /*index:*/ usize)>,
|
||||
compat_index: Vec<(/*weight:*/ u64, /*index:*/ usize)>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -90,12 +88,12 @@ impl Node {
|
||||
|
||||
impl<T> ClusterNodes<T> {
|
||||
pub(crate) fn num_peers(&self) -> usize {
|
||||
self.index.len()
|
||||
self.compat_index.len()
|
||||
}
|
||||
|
||||
// A peer is considered live if they generated their contact info recently.
|
||||
pub(crate) fn num_peers_live(&self, now: u64) -> usize {
|
||||
self.index
|
||||
self.compat_index
|
||||
.iter()
|
||||
.filter_map(|(_, index)| self.nodes[*index].contact_info())
|
||||
.filter(|node| {
|
||||
@@ -133,7 +131,7 @@ impl ClusterNodes<BroadcastStage> {
|
||||
return Vec::default();
|
||||
}
|
||||
let mut rng = ChaChaRng::from_seed(shred_seed);
|
||||
let index = match weighted_sample_single(&mut rng, &self.cumulative_weights) {
|
||||
let index = match self.weighted_shuffle.first(&mut rng) {
|
||||
None => return Vec::default(),
|
||||
Some(index) => index,
|
||||
};
|
||||
@@ -146,16 +144,16 @@ impl ClusterNodes<BroadcastStage> {
|
||||
return vec![node.tvu];
|
||||
}
|
||||
}
|
||||
let nodes: Vec<_> = self
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|node| node.pubkey() != self.pubkey)
|
||||
let mut rng = ChaChaRng::from_seed(shred_seed);
|
||||
let nodes: Vec<&Node> = self
|
||||
.weighted_shuffle
|
||||
.clone()
|
||||
.shuffle(&mut rng)
|
||||
.map(|index| &self.nodes[index])
|
||||
.collect();
|
||||
if nodes.is_empty() {
|
||||
return Vec::default();
|
||||
}
|
||||
let mut rng = ChaChaRng::from_seed(shred_seed);
|
||||
let nodes = shuffle_nodes(&mut rng, &nodes);
|
||||
let (neighbors, children) = compute_retransmit_peers(fanout, 0, &nodes);
|
||||
neighbors[..1]
|
||||
.iter()
|
||||
@@ -177,10 +175,10 @@ impl ClusterNodes<BroadcastStage> {
|
||||
/// Returns the root of turbine broadcast tree, which the leader sends the
|
||||
/// shred to.
|
||||
fn get_broadcast_peer(&self, shred_seed: [u8; 32]) -> Option<&ContactInfo> {
|
||||
if self.index.is_empty() {
|
||||
if self.compat_index.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let index = weighted_best(&self.index, shred_seed);
|
||||
let index = weighted_best(&self.compat_index, shred_seed);
|
||||
match &self.nodes[index].node {
|
||||
NodeId::ContactInfo(node) => Some(node),
|
||||
NodeId::Pubkey(_) => panic!("this should not happen!"),
|
||||
@@ -235,18 +233,18 @@ impl ClusterNodes<RetransmitStage> {
|
||||
if !enable_turbine_peers_shuffle_patch(shred.slot(), root_bank) {
|
||||
return self.get_retransmit_peers_compat(shred_seed, fanout, slot_leader);
|
||||
}
|
||||
let mut weighted_shuffle = self.weighted_shuffle.clone();
|
||||
// Exclude slot leader from list of nodes.
|
||||
let nodes: Vec<_> = if slot_leader == self.pubkey {
|
||||
if slot_leader == self.pubkey {
|
||||
error!("retransmit from slot leader: {}", slot_leader);
|
||||
self.nodes.iter().collect()
|
||||
} else {
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter(|node| node.pubkey() != slot_leader)
|
||||
.collect()
|
||||
} else if let Some(index) = self.index.get(&slot_leader) {
|
||||
weighted_shuffle.remove_index(*index);
|
||||
};
|
||||
let mut rng = ChaChaRng::from_seed(shred_seed);
|
||||
let nodes = shuffle_nodes(&mut rng, &nodes);
|
||||
let nodes: Vec<_> = weighted_shuffle
|
||||
.shuffle(&mut rng)
|
||||
.map(|index| &self.nodes[index])
|
||||
.collect();
|
||||
let self_index = nodes
|
||||
.iter()
|
||||
.position(|node| node.pubkey() == self.pubkey)
|
||||
@@ -270,9 +268,9 @@ impl ClusterNodes<RetransmitStage> {
|
||||
// Exclude leader from list of nodes.
|
||||
let (weights, index): (Vec<u64>, Vec<usize>) = if slot_leader == self.pubkey {
|
||||
error!("retransmit from slot leader: {}", slot_leader);
|
||||
self.index.iter().copied().unzip()
|
||||
self.compat_index.iter().copied().unzip()
|
||||
} else {
|
||||
self.index
|
||||
self.compat_index
|
||||
.iter()
|
||||
.filter(|(_, i)| self.nodes[*i].pubkey() != slot_leader)
|
||||
.copied()
|
||||
@@ -299,49 +297,30 @@ impl ClusterNodes<RetransmitStage> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cumulative_weights(self_pubkey: Pubkey, nodes: &[Node]) -> Vec<u64> {
|
||||
let cumulative_stakes: Vec<_> = nodes
|
||||
.iter()
|
||||
.scan(0, |acc, node| {
|
||||
if node.pubkey() != self_pubkey {
|
||||
*acc += node.stake;
|
||||
}
|
||||
Some(*acc)
|
||||
})
|
||||
.collect();
|
||||
if cumulative_stakes.last() != Some(&0) {
|
||||
return cumulative_stakes;
|
||||
}
|
||||
nodes
|
||||
.iter()
|
||||
.scan(0, |acc, node| {
|
||||
if node.pubkey() != self_pubkey {
|
||||
*acc += 1;
|
||||
}
|
||||
Some(*acc)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn new_cluster_nodes<T: 'static>(
|
||||
cluster_info: &ClusterInfo,
|
||||
stakes: &HashMap<Pubkey, u64>,
|
||||
) -> ClusterNodes<T> {
|
||||
let self_pubkey = cluster_info.id();
|
||||
let nodes = get_nodes(cluster_info, stakes);
|
||||
let index: HashMap<_, _> = nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, node)| (node.pubkey(), ix))
|
||||
.collect();
|
||||
let broadcast = TypeId::of::<T>() == TypeId::of::<BroadcastStage>();
|
||||
let cumulative_weights = if broadcast {
|
||||
build_cumulative_weights(self_pubkey, &nodes)
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
let stakes: Vec<u64> = nodes.iter().map(|node| node.stake).collect();
|
||||
let mut weighted_shuffle = WeightedShuffle::new(&stakes).unwrap();
|
||||
if broadcast {
|
||||
weighted_shuffle.remove_index(index[&self_pubkey]);
|
||||
}
|
||||
// For backward compatibility:
|
||||
// * nodes which do not have contact-info are excluded.
|
||||
// * stakes are floored at 1.
|
||||
// The sorting key here should be equivalent to
|
||||
// solana_gossip::deprecated::sorted_stakes_with_index.
|
||||
// Leader itself is excluded when sampling broadcast peers.
|
||||
let index = nodes
|
||||
let compat_index = nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, node)| node.contact_info().is_some())
|
||||
@@ -352,8 +331,9 @@ fn new_cluster_nodes<T: 'static>(
|
||||
ClusterNodes {
|
||||
pubkey: self_pubkey,
|
||||
nodes,
|
||||
cumulative_weights,
|
||||
index,
|
||||
weighted_shuffle,
|
||||
compat_index,
|
||||
_phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
@@ -406,29 +386,6 @@ fn enable_turbine_peers_shuffle_patch(shred_slot: Slot, root_bank: &Bank) -> boo
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffles nodes w.r.t their stakes.
|
||||
// Unstaked nodes will always appear at the very end.
|
||||
fn shuffle_nodes<'a, R: Rng>(rng: &mut R, nodes: &[&'a Node]) -> Vec<&'a Node> {
|
||||
// Nodes are sorted by (stake, pubkey) in descending order.
|
||||
let stakes: Vec<u64> = nodes
|
||||
.iter()
|
||||
.map(|node| node.stake)
|
||||
.take_while(|stake| *stake > 0)
|
||||
.collect();
|
||||
let num_staked = stakes.len();
|
||||
let mut out: Vec<_> = WeightedShuffle::new(rng, &stakes)
|
||||
.unwrap()
|
||||
.map(|i| nodes[i])
|
||||
.collect();
|
||||
let weights = vec![1; nodes.len() - num_staked];
|
||||
out.extend(
|
||||
WeightedShuffle::new(rng, &weights)
|
||||
.unwrap()
|
||||
.map(|i| nodes[i + num_staked]),
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
impl<T> ClusterNodesCache<T> {
|
||||
pub fn new(
|
||||
// Capacity of underlying LRU-cache in terms of number of epochs.
|
||||
@@ -505,18 +462,6 @@ impl From<Pubkey> for NodeId {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for ClusterNodes<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pubkey: Pubkey::default(),
|
||||
nodes: Vec::default(),
|
||||
cumulative_weights: Vec::default(),
|
||||
index: Vec::default(),
|
||||
_phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
@@ -608,7 +553,7 @@ mod tests {
|
||||
assert_eq!(cluster_info.tvu_peers().len(), nodes.len() - 1);
|
||||
let cluster_nodes = new_cluster_nodes::<RetransmitStage>(&cluster_info, &stakes);
|
||||
// All nodes with contact-info should be in the index.
|
||||
assert_eq!(cluster_nodes.index.len(), nodes.len());
|
||||
assert_eq!(cluster_nodes.compat_index.len(), nodes.len());
|
||||
// Staked nodes with no contact-info should be included.
|
||||
assert!(cluster_nodes.nodes.len() > nodes.len());
|
||||
// Assert that all nodes keep their contact-info.
|
||||
@@ -631,9 +576,9 @@ mod tests {
|
||||
let (peers, stakes_and_index) =
|
||||
sorted_retransmit_peers_and_stakes(&cluster_info, Some(&stakes));
|
||||
assert_eq!(stakes_and_index.len(), peers.len());
|
||||
assert_eq!(cluster_nodes.index.len(), peers.len());
|
||||
assert_eq!(cluster_nodes.compat_index.len(), peers.len());
|
||||
for (i, node) in cluster_nodes
|
||||
.index
|
||||
.compat_index
|
||||
.iter()
|
||||
.map(|(_, i)| &cluster_nodes.nodes[*i])
|
||||
.enumerate()
|
||||
@@ -689,7 +634,7 @@ mod tests {
|
||||
let cluster_nodes = ClusterNodes::<BroadcastStage>::new(&cluster_info, &stakes);
|
||||
// All nodes with contact-info should be in the index.
|
||||
// Excluding this node itself.
|
||||
assert_eq!(cluster_nodes.index.len() + 1, nodes.len());
|
||||
assert_eq!(cluster_nodes.compat_index.len() + 1, nodes.len());
|
||||
// Staked nodes with no contact-info should be included.
|
||||
assert!(cluster_nodes.nodes.len() > nodes.len());
|
||||
// Assert that all nodes keep their contact-info.
|
||||
@@ -711,9 +656,9 @@ mod tests {
|
||||
}
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(&stakes));
|
||||
assert_eq!(peers_and_stakes.len(), peers.len());
|
||||
assert_eq!(cluster_nodes.index.len(), peers.len());
|
||||
assert_eq!(cluster_nodes.compat_index.len(), peers.len());
|
||||
for (i, node) in cluster_nodes
|
||||
.index
|
||||
.compat_index
|
||||
.iter()
|
||||
.map(|(_, i)| &cluster_nodes.nodes[*i])
|
||||
.enumerate()
|
||||
|
@@ -3,7 +3,8 @@ use {
|
||||
heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice,
|
||||
latest_validator_votes_for_frozen_banks::LatestValidatorVotesForFrozenBanks,
|
||||
progress_map::{LockoutIntervals, ProgressMap},
|
||||
tower_storage::{SavedTower, TowerStorage},
|
||||
tower1_7_14::Tower1_7_14,
|
||||
tower_storage::{SavedTower, SavedTowerVersions, TowerStorage},
|
||||
},
|
||||
chrono::prelude::*,
|
||||
solana_ledger::{ancestor_iterator::AncestorIterator, blockstore::Blockstore, blockstore_db},
|
||||
@@ -13,6 +14,7 @@ use {
|
||||
},
|
||||
solana_sdk::{
|
||||
clock::{Slot, UnixTimestamp},
|
||||
feature_set,
|
||||
hash::Hash,
|
||||
instruction::Instruction,
|
||||
pubkey::Pubkey,
|
||||
@@ -21,7 +23,10 @@ use {
|
||||
},
|
||||
solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY},
|
||||
vote_state::{
|
||||
BlockTimestamp, Lockout, Vote, VoteState, VoteStateUpdate, VoteTransaction,
|
||||
MAX_LOCKOUT_HISTORY,
|
||||
},
|
||||
},
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
@@ -45,29 +50,43 @@ pub enum SwitchForkDecision {
|
||||
impl SwitchForkDecision {
|
||||
pub fn to_vote_instruction(
|
||||
&self,
|
||||
vote: Vote,
|
||||
vote: VoteTransaction,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_pubkey: &Pubkey,
|
||||
) -> Option<Instruction> {
|
||||
match self {
|
||||
SwitchForkDecision::FailedSwitchThreshold(_, total_stake) => {
|
||||
match (self, vote) {
|
||||
(SwitchForkDecision::FailedSwitchThreshold(_, total_stake), _) => {
|
||||
assert_ne!(*total_stake, 0);
|
||||
None
|
||||
}
|
||||
SwitchForkDecision::FailedSwitchDuplicateRollback(_) => None,
|
||||
SwitchForkDecision::SameFork => Some(vote_instruction::vote(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_pubkey,
|
||||
vote,
|
||||
)),
|
||||
SwitchForkDecision::SwitchProof(switch_proof_hash) => {
|
||||
(SwitchForkDecision::FailedSwitchDuplicateRollback(_), _) => None,
|
||||
(SwitchForkDecision::SameFork, VoteTransaction::Vote(v)) => Some(
|
||||
vote_instruction::vote(vote_account_pubkey, authorized_voter_pubkey, v),
|
||||
),
|
||||
(SwitchForkDecision::SameFork, VoteTransaction::VoteStateUpdate(v)) => {
|
||||
Some(vote_instruction::update_vote_state(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_pubkey,
|
||||
v,
|
||||
))
|
||||
}
|
||||
(SwitchForkDecision::SwitchProof(switch_proof_hash), VoteTransaction::Vote(v)) => {
|
||||
Some(vote_instruction::vote_switch(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_pubkey,
|
||||
vote,
|
||||
v,
|
||||
*switch_proof_hash,
|
||||
))
|
||||
}
|
||||
(
|
||||
SwitchForkDecision::SwitchProof(switch_proof_hash),
|
||||
VoteTransaction::VoteStateUpdate(v),
|
||||
) => Some(vote_instruction::update_vote_state_switch(
|
||||
vote_account_pubkey,
|
||||
authorized_voter_pubkey,
|
||||
v,
|
||||
*switch_proof_hash,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +121,47 @@ pub(crate) struct ComputedBankState {
|
||||
pub my_latest_landed_vote: Option<Slot>,
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "GMs1FxKteU7K4ZFRofMBqNhBpM4xkPVxfYod6R8DQmpT")]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub enum TowerVersions {
|
||||
V1_17_14(Tower1_7_14),
|
||||
Current(Tower),
|
||||
}
|
||||
|
||||
impl TowerVersions {
|
||||
pub fn new_current(tower: Tower) -> Self {
|
||||
Self::Current(tower)
|
||||
}
|
||||
|
||||
pub fn convert_to_current(self) -> Tower {
|
||||
match self {
|
||||
TowerVersions::V1_17_14(tower) => {
|
||||
let box_last_vote = VoteTransaction::from(tower.last_vote.clone());
|
||||
|
||||
Tower {
|
||||
node_pubkey: tower.node_pubkey,
|
||||
threshold_depth: tower.threshold_depth,
|
||||
threshold_size: tower.threshold_size,
|
||||
vote_state: tower.vote_state,
|
||||
last_vote: box_last_vote,
|
||||
last_vote_tx_blockhash: tower.last_vote_tx_blockhash,
|
||||
last_timestamp: tower.last_timestamp,
|
||||
stray_restored_slot: tower.stray_restored_slot,
|
||||
last_switch_threshold_check: tower.last_switch_threshold_check,
|
||||
}
|
||||
}
|
||||
TowerVersions::Current(tower) => tower,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[frozen_abi(digest = "BfeSJNsfQeX6JU7dmezv1s1aSvR5SoyxKRRZ4ubTh2mt")]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, AbiExample)]
|
||||
pub struct Tower {
|
||||
pub node_pubkey: Pubkey,
|
||||
threshold_depth: usize,
|
||||
threshold_size: f64,
|
||||
vote_state: VoteState,
|
||||
last_vote: Vote,
|
||||
pub(crate) vote_state: VoteState,
|
||||
last_vote: VoteTransaction,
|
||||
#[serde(skip)]
|
||||
// The blockhash used in the last vote transaction, may or may not equal the
|
||||
// blockhash of the voted block itself, depending if the vote slot was refreshed.
|
||||
@@ -136,7 +188,7 @@ impl Default for Tower {
|
||||
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
||||
threshold_size: VOTE_THRESHOLD_SIZE,
|
||||
vote_state: VoteState::default(),
|
||||
last_vote: Vote::default(),
|
||||
last_vote: VoteTransaction::from(VoteStateUpdate::default()),
|
||||
last_timestamp: BlockTimestamp::default(),
|
||||
last_vote_tx_blockhash: Hash::default(),
|
||||
stray_restored_slot: Option::default(),
|
||||
@@ -359,12 +411,18 @@ impl Tower {
|
||||
self.last_vote_tx_blockhash = new_vote_tx_blockhash;
|
||||
}
|
||||
|
||||
// Returns true if we have switched the new vote instruction that directly sets vote state
|
||||
pub(crate) fn is_direct_vote_state_update_enabled(bank: &Bank) -> bool {
|
||||
bank.feature_set
|
||||
.is_active(&feature_set::allow_votes_to_directly_update_vote_state::id())
|
||||
}
|
||||
|
||||
fn apply_vote_and_generate_vote_diff(
|
||||
local_vote_state: &mut VoteState,
|
||||
slot: Slot,
|
||||
hash: Hash,
|
||||
last_voted_slot_in_bank: Option<Slot>,
|
||||
) -> Vote {
|
||||
) -> VoteTransaction {
|
||||
let vote = Vote::new(vec![slot], hash);
|
||||
local_vote_state.process_vote_unchecked(vote);
|
||||
let slots = if let Some(last_voted_slot) = last_voted_slot_in_bank {
|
||||
@@ -377,7 +435,7 @@ impl Tower {
|
||||
} else {
|
||||
local_vote_state.votes.iter().map(|v| v.slot).collect()
|
||||
};
|
||||
Vote::new(slots, hash)
|
||||
VoteTransaction::from(Vote::new(slots, hash))
|
||||
}
|
||||
|
||||
pub fn last_voted_slot_in_bank(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
|
||||
@@ -391,7 +449,12 @@ impl Tower {
|
||||
|
||||
// Returns the new root if one is made after applying a vote for the given bank to
|
||||
// `self.vote_state`
|
||||
self.record_bank_vote_and_update_lockouts(bank.slot(), bank.hash(), last_voted_slot_in_bank)
|
||||
self.record_bank_vote_and_update_lockouts(
|
||||
bank.slot(),
|
||||
bank.hash(),
|
||||
last_voted_slot_in_bank,
|
||||
Self::is_direct_vote_state_update_enabled(bank),
|
||||
)
|
||||
}
|
||||
|
||||
fn record_bank_vote_and_update_lockouts(
|
||||
@@ -399,18 +462,29 @@ impl Tower {
|
||||
vote_slot: Slot,
|
||||
vote_hash: Hash,
|
||||
last_voted_slot_in_bank: Option<Slot>,
|
||||
is_direct_vote_state_update_enabled: bool,
|
||||
) -> Option<Slot> {
|
||||
trace!("{} record_vote for {}", self.node_pubkey, vote_slot);
|
||||
let old_root = self.root();
|
||||
let mut new_vote = Self::apply_vote_and_generate_vote_diff(
|
||||
&mut self.vote_state,
|
||||
vote_slot,
|
||||
vote_hash,
|
||||
last_voted_slot_in_bank,
|
||||
);
|
||||
|
||||
new_vote.timestamp =
|
||||
self.maybe_timestamp(self.last_vote.slots.last().copied().unwrap_or_default());
|
||||
let mut new_vote = if is_direct_vote_state_update_enabled {
|
||||
let vote = Vote::new(vec![vote_slot], vote_hash);
|
||||
self.vote_state.process_vote_unchecked(vote);
|
||||
VoteTransaction::from(VoteStateUpdate::new(
|
||||
self.vote_state.votes.clone(),
|
||||
self.vote_state.root_slot,
|
||||
vote_hash,
|
||||
))
|
||||
} else {
|
||||
Self::apply_vote_and_generate_vote_diff(
|
||||
&mut self.vote_state,
|
||||
vote_slot,
|
||||
vote_hash,
|
||||
last_voted_slot_in_bank,
|
||||
)
|
||||
};
|
||||
|
||||
new_vote.set_timestamp(self.maybe_timestamp(self.last_voted_slot().unwrap_or_default()));
|
||||
self.last_vote = new_vote;
|
||||
|
||||
let new_root = self.root();
|
||||
@@ -429,7 +503,7 @@ impl Tower {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn record_vote(&mut self, slot: Slot, hash: Hash) -> Option<Slot> {
|
||||
self.record_bank_vote_and_update_lockouts(slot, hash, self.last_voted_slot())
|
||||
self.record_bank_vote_and_update_lockouts(slot, hash, self.last_voted_slot(), true)
|
||||
}
|
||||
|
||||
/// Used for tests
|
||||
@@ -440,18 +514,22 @@ impl Tower {
|
||||
}
|
||||
|
||||
pub fn last_voted_slot(&self) -> Option<Slot> {
|
||||
self.last_vote.slots.last().copied()
|
||||
if self.last_vote.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.last_vote.slot(self.last_vote.len() - 1))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
|
||||
Some((*self.last_vote.slots.last()?, self.last_vote.hash))
|
||||
Some((self.last_voted_slot()?, self.last_vote.hash()))
|
||||
}
|
||||
|
||||
pub fn stray_restored_slot(&self) -> Option<Slot> {
|
||||
self.stray_restored_slot
|
||||
}
|
||||
|
||||
pub fn last_vote(&self) -> Vote {
|
||||
pub fn last_vote(&self) -> VoteTransaction {
|
||||
self.last_vote.clone()
|
||||
}
|
||||
|
||||
@@ -482,12 +560,17 @@ impl Tower {
|
||||
self.vote_state.root_slot.unwrap()
|
||||
}
|
||||
|
||||
// a slot is recent if it's newer than the last vote we have
|
||||
// a slot is recent if it's newer than the last vote we have. If we haven't voted yet
|
||||
// but have a root (hard forks situation) then comparre it to the root
|
||||
pub fn is_recent(&self, slot: Slot) -> bool {
|
||||
if let Some(last_voted_slot) = self.vote_state.last_voted_slot() {
|
||||
if slot <= last_voted_slot {
|
||||
return false;
|
||||
}
|
||||
} else if let Some(root) = self.vote_state.root_slot {
|
||||
if slot <= root {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -583,9 +666,9 @@ impl Tower {
|
||||
// `switch < last` is needed not to warn! this message just because of using
|
||||
// newer snapshots on validator restart
|
||||
let message = format!(
|
||||
"bank_forks doesn't have corresponding data for the stray restored \
|
||||
"bank_forks doesn't have corresponding data for the stray restored \
|
||||
last vote({}), meaning some inconsistency between saved tower and ledger.",
|
||||
last_voted_slot
|
||||
last_voted_slot
|
||||
);
|
||||
warn!("{}", message);
|
||||
datapoint_warn!("tower_warn", ("warn", message, String));
|
||||
@@ -627,42 +710,56 @@ impl Tower {
|
||||
// TODO: Handle if the last vote is on a dupe, and then we restart. The dupe won't be in
|
||||
// heaviest_subtree_fork_choice, so `heaviest_subtree_fork_choice.latest_invalid_ancestor()` will return
|
||||
// None, but the last vote will be persisted in tower.
|
||||
let switch_hash = progress.get_hash(switch_slot).expect("Slot we're trying to switch to must exist AND be frozen in progress map");
|
||||
if let Some(latest_duplicate_ancestor) = heaviest_subtree_fork_choice.latest_invalid_ancestor(&(last_voted_slot, last_voted_hash)) {
|
||||
let switch_hash = progress
|
||||
.get_hash(switch_slot)
|
||||
.expect("Slot we're trying to switch to must exist AND be frozen in progress map");
|
||||
if let Some(latest_duplicate_ancestor) = heaviest_subtree_fork_choice
|
||||
.latest_invalid_ancestor(&(last_voted_slot, last_voted_hash))
|
||||
{
|
||||
// We're rolling back because one of the ancestors of the last vote was a duplicate. In this
|
||||
// case, it's acceptable if the switch candidate is one of ancestors of the previous vote,
|
||||
// just fail the switch check because there's no point in voting on an ancestor. ReplayStage
|
||||
// should then have a special case continue building an alternate fork from this ancestor, NOT
|
||||
// the `last_voted_slot`. This is in contrast to usual SwitchFailure where ReplayStage continues to build blocks
|
||||
// on latest vote. See `ReplayStage::select_vote_and_reset_forks()` for more details.
|
||||
if heaviest_subtree_fork_choice.is_strict_ancestor(&(switch_slot, switch_hash), &(last_voted_slot, last_voted_hash)) {
|
||||
if heaviest_subtree_fork_choice.is_strict_ancestor(
|
||||
&(switch_slot, switch_hash),
|
||||
&(last_voted_slot, last_voted_hash),
|
||||
) {
|
||||
return rollback_due_to_to_to_duplicate_ancestor(latest_duplicate_ancestor);
|
||||
} else if progress.get_hash(last_voted_slot).map(|current_slot_hash| current_slot_hash != last_voted_hash).unwrap_or(true) {
|
||||
} else if progress
|
||||
.get_hash(last_voted_slot)
|
||||
.map(|current_slot_hash| current_slot_hash != last_voted_hash)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// Our last vote slot was purged because it was on a duplicate fork, don't continue below
|
||||
// where checks may panic. We allow a freebie vote here that may violate switching
|
||||
// thresholds
|
||||
// TODO: Properly handle this case
|
||||
info!("Allowing switch vote on {:?} because last vote {:?} was rolled back", (switch_slot, switch_hash), (last_voted_slot, last_voted_hash));
|
||||
info!(
|
||||
"Allowing switch vote on {:?} because last vote {:?} was rolled back",
|
||||
(switch_slot, switch_hash),
|
||||
(last_voted_slot, last_voted_hash)
|
||||
);
|
||||
return SwitchForkDecision::SwitchProof(Hash::default());
|
||||
}
|
||||
}
|
||||
|
||||
let last_vote_ancestors =
|
||||
ancestors.get(&last_voted_slot).unwrap_or_else(|| {
|
||||
if self.is_stray_last_vote() {
|
||||
// Unless last vote is stray and stale, ancestors.get(last_voted_slot) must
|
||||
// return Some(_), justifying to panic! here.
|
||||
// Also, adjust_lockouts_after_replay() correctly makes last_voted_slot None,
|
||||
// if all saved votes are ancestors of replayed_root_slot. So this code shouldn't be
|
||||
// touched in that case as well.
|
||||
// In other words, except being stray, all other slots have been voted on while
|
||||
// this validator has been running, so we must be able to fetch ancestors for
|
||||
// all of them.
|
||||
empty_ancestors_due_to_minor_unsynced_ledger()
|
||||
} else {
|
||||
panic!("no ancestors found with slot: {}", last_voted_slot);
|
||||
}
|
||||
});
|
||||
let last_vote_ancestors = ancestors.get(&last_voted_slot).unwrap_or_else(|| {
|
||||
if self.is_stray_last_vote() {
|
||||
// Unless last vote is stray and stale, ancestors.get(last_voted_slot) must
|
||||
// return Some(_), justifying to panic! here.
|
||||
// Also, adjust_lockouts_after_replay() correctly makes last_voted_slot None,
|
||||
// if all saved votes are ancestors of replayed_root_slot. So this code shouldn't be
|
||||
// touched in that case as well.
|
||||
// In other words, except being stray, all other slots have been voted on while
|
||||
// this validator has been running, so we must be able to fetch ancestors for
|
||||
// all of them.
|
||||
empty_ancestors_due_to_minor_unsynced_ledger()
|
||||
} else {
|
||||
panic!("no ancestors found with slot: {}", last_voted_slot);
|
||||
}
|
||||
});
|
||||
|
||||
let switch_slot_ancestors = ancestors.get(&switch_slot).unwrap();
|
||||
|
||||
@@ -734,22 +831,76 @@ impl Tower {
|
||||
// Find any locked out intervals for vote accounts in this bank with
|
||||
// `lockout_interval_end` >= `last_vote`, which implies they are locked out at
|
||||
// `last_vote` on another fork.
|
||||
for (_lockout_interval_end, intervals_keyed_by_end) in lockout_intervals.range((Included(last_voted_slot), Unbounded)) {
|
||||
for (lockout_interval_start, vote_account_pubkey) in intervals_keyed_by_end {
|
||||
if locked_out_vote_accounts.contains(vote_account_pubkey) {
|
||||
continue;
|
||||
}
|
||||
for (_lockout_interval_end, intervals_keyed_by_end) in
|
||||
lockout_intervals.range((Included(last_voted_slot), Unbounded))
|
||||
{
|
||||
for (lockout_interval_start, vote_account_pubkey) in intervals_keyed_by_end {
|
||||
if locked_out_vote_accounts.contains(vote_account_pubkey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only count lockouts on slots that are:
|
||||
// 1) Not ancestors of `last_vote`, meaning being on different fork
|
||||
// 2) Not from before the current root as we can't determine if
|
||||
// anything before the root was an ancestor of `last_vote` or not
|
||||
if !last_vote_ancestors.contains(lockout_interval_start)
|
||||
// Given a `lockout_interval_start` < root that appears in a
|
||||
// bank for a `candidate_slot`, it must be that `lockout_interval_start`
|
||||
// is an ancestor of the current root, because `candidate_slot` is a
|
||||
// descendant of the current root
|
||||
&& *lockout_interval_start > root
|
||||
// Only count lockouts on slots that are:
|
||||
// 1) Not ancestors of `last_vote`, meaning being on different fork
|
||||
// 2) Not from before the current root as we can't determine if
|
||||
// anything before the root was an ancestor of `last_vote` or not
|
||||
if !last_vote_ancestors.contains(lockout_interval_start)
|
||||
// Given a `lockout_interval_start` < root that appears in a
|
||||
// bank for a `candidate_slot`, it must be that `lockout_interval_start`
|
||||
// is an ancestor of the current root, because `candidate_slot` is a
|
||||
// descendant of the current root
|
||||
&& *lockout_interval_start > root
|
||||
{
|
||||
let stake = epoch_vote_accounts
|
||||
.get(vote_account_pubkey)
|
||||
.map(|(stake, _)| *stake)
|
||||
.unwrap_or(0);
|
||||
locked_out_stake += stake;
|
||||
if (locked_out_stake as f64 / total_stake as f64)
|
||||
> SWITCH_FORK_THRESHOLD
|
||||
{
|
||||
return SwitchForkDecision::SwitchProof(switch_proof);
|
||||
}
|
||||
locked_out_vote_accounts.insert(vote_account_pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the latest votes for potentially gossip votes that haven't landed yet
|
||||
for (
|
||||
vote_account_pubkey,
|
||||
(candidate_latest_frozen_vote, _candidate_latest_frozen_vote_hash),
|
||||
) in latest_validator_votes_for_frozen_banks.max_gossip_frozen_votes()
|
||||
{
|
||||
if locked_out_vote_accounts.contains(&vote_account_pubkey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if *candidate_latest_frozen_vote > last_voted_slot
|
||||
&&
|
||||
// Because `candidate_latest_frozen_vote` is the last vote made by some validator
|
||||
// in the cluster for a frozen bank `B` observed through gossip, we may have cleared
|
||||
// that frozen bank `B` because we `set_root(root)` for a `root` on a different fork,
|
||||
// like so:
|
||||
//
|
||||
// |----------X ------candidate_latest_frozen_vote (frozen)
|
||||
// old root
|
||||
// |----------new root ----last_voted_slot
|
||||
//
|
||||
// In most cases, because `last_voted_slot` must be a descendant of `root`, then
|
||||
// if `candidate_latest_frozen_vote` is not found in the ancestors/descendants map (recall these
|
||||
// directly reflect the state of BankForks), this implies that `B` was pruned from BankForks
|
||||
// because it was on a different fork than `last_voted_slot`, and thus this vote for `candidate_latest_frozen_vote`
|
||||
// should be safe to count towards the switching proof:
|
||||
//
|
||||
// However, there is also the possibility that `last_voted_slot` is a stray, in which
|
||||
// case we cannot make this conclusion as we do not know the ancestors/descendants
|
||||
// of strays. Hence we err on the side of caution here and ignore this vote. This
|
||||
// is ok because validators voting on different unrooted forks should eventually vote
|
||||
// on some descendant of the root, at which time they can be included in switching proofs.
|
||||
!Self::is_candidate_slot_descendant_of_last_vote(
|
||||
*candidate_latest_frozen_vote, last_voted_slot, ancestors)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
let stake = epoch_vote_accounts
|
||||
.get(vote_account_pubkey)
|
||||
@@ -761,58 +912,13 @@ impl Tower {
|
||||
}
|
||||
locked_out_vote_accounts.insert(vote_account_pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the latest votes for potentially gossip votes that haven't landed yet
|
||||
for (vote_account_pubkey, (candidate_latest_frozen_vote, _candidate_latest_frozen_vote_hash)) in latest_validator_votes_for_frozen_banks.max_gossip_frozen_votes() {
|
||||
if locked_out_vote_accounts.contains(&vote_account_pubkey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if *candidate_latest_frozen_vote > last_voted_slot
|
||||
&&
|
||||
// Because `candidate_latest_frozen_vote` is the last vote made by some validator
|
||||
// in the cluster for a frozen bank `B` observed through gossip, we may have cleared
|
||||
// that frozen bank `B` because we `set_root(root)` for a `root` on a different fork,
|
||||
// like so:
|
||||
//
|
||||
// |----------X ------candidate_latest_frozen_vote (frozen)
|
||||
// old root
|
||||
// |----------new root ----last_voted_slot
|
||||
//
|
||||
// In most cases, because `last_voted_slot` must be a descendant of `root`, then
|
||||
// if `candidate_latest_frozen_vote` is not found in the ancestors/descendants map (recall these
|
||||
// directly reflect the state of BankForks), this implies that `B` was pruned from BankForks
|
||||
// because it was on a different fork than `last_voted_slot`, and thus this vote for `candidate_latest_frozen_vote`
|
||||
// should be safe to count towards the switching proof:
|
||||
//
|
||||
// However, there is also the possibility that `last_voted_slot` is a stray, in which
|
||||
// case we cannot make this conclusion as we do not know the ancestors/descendants
|
||||
// of strays. Hence we err on the side of caution here and ignore this vote. This
|
||||
// is ok because validators voting on different unrooted forks should eventually vote
|
||||
// on some descendant of the root, at which time they can be included in switching proofs.
|
||||
!Self::is_candidate_slot_descendant_of_last_vote(
|
||||
*candidate_latest_frozen_vote, last_voted_slot, ancestors)
|
||||
.unwrap_or(true) {
|
||||
let stake = epoch_vote_accounts
|
||||
.get(vote_account_pubkey)
|
||||
.map(|(stake, _)| *stake)
|
||||
.unwrap_or(0);
|
||||
locked_out_stake += stake;
|
||||
if (locked_out_stake as f64 / total_stake as f64) > SWITCH_FORK_THRESHOLD {
|
||||
return SwitchForkDecision::SwitchProof(switch_proof);
|
||||
}
|
||||
locked_out_vote_accounts.insert(vote_account_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
// We have not detected sufficient lockout past the last voted slot to generate
|
||||
// a switching proof
|
||||
SwitchForkDecision::FailedSwitchThreshold(locked_out_stake, total_stake)
|
||||
})
|
||||
.unwrap_or(SwitchForkDecision::SameFork)
|
||||
.unwrap_or(SwitchForkDecision::SameFork)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -933,13 +1039,7 @@ impl Tower {
|
||||
}
|
||||
|
||||
pub fn is_stray_last_vote(&self) -> bool {
|
||||
if let Some(last_voted_slot) = self.last_voted_slot() {
|
||||
if let Some(stray_restored_slot) = self.stray_restored_slot {
|
||||
return stray_restored_slot == last_voted_slot;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
self.stray_restored_slot == self.last_voted_slot()
|
||||
}
|
||||
|
||||
// The tower root can be older/newer if the validator booted from a newer/older snapshot, so
|
||||
@@ -961,8 +1061,10 @@ impl Tower {
|
||||
assert_eq!(slot_history.check(replayed_root), Check::Found);
|
||||
|
||||
assert!(
|
||||
self.last_vote == Vote::default() && self.vote_state.votes.is_empty()
|
||||
|| self.last_vote != Vote::default() && !self.vote_state.votes.is_empty(),
|
||||
self.last_vote == VoteTransaction::from(VoteStateUpdate::default())
|
||||
&& self.vote_state.votes.is_empty()
|
||||
|| self.last_vote != VoteTransaction::from(VoteStateUpdate::default())
|
||||
&& !self.vote_state.votes.is_empty(),
|
||||
"last vote: {:?} vote_state.votes: {:?}",
|
||||
self.last_vote,
|
||||
self.vote_state.votes
|
||||
@@ -1116,7 +1218,7 @@ impl Tower {
|
||||
info!("All restored votes were behind; resetting root_slot and last_vote in tower!");
|
||||
// we might not have banks for those votes so just reset.
|
||||
// That's because the votes may well past replayed_root
|
||||
self.last_vote = Vote::default();
|
||||
self.last_vote = VoteTransaction::from(Vote::default());
|
||||
} else {
|
||||
info!(
|
||||
"{} restored votes (out of {}) were on different fork or are upcoming votes on unrooted slots: {:?}!",
|
||||
@@ -1125,11 +1227,8 @@ impl Tower {
|
||||
self.voted_slots()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
self.last_vote.slots.last().unwrap(),
|
||||
self.voted_slots().last().unwrap()
|
||||
);
|
||||
self.stray_restored_slot = Some(*self.last_vote.slots.last().unwrap());
|
||||
assert_eq!(self.last_voted_slot(), self.voted_slots().last().copied());
|
||||
self.stray_restored_slot = self.last_vote.last_voted_slot()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1176,13 +1275,12 @@ impl Tower {
|
||||
|
||||
pub fn save(&self, tower_storage: &dyn TowerStorage, node_keypair: &Keypair) -> Result<()> {
|
||||
let saved_tower = SavedTower::new(self, node_keypair)?;
|
||||
tower_storage.store(&saved_tower)?;
|
||||
tower_storage.store(&SavedTowerVersions::from(saved_tower))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore(tower_storage: &dyn TowerStorage, node_pubkey: &Pubkey) -> Result<Self> {
|
||||
let saved_tower = tower_storage.load(node_pubkey)?;
|
||||
saved_tower.try_into_tower(node_pubkey)
|
||||
tower_storage.load(node_pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,7 +1388,7 @@ pub mod test {
|
||||
},
|
||||
solana_vote_program::vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY},
|
||||
std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
fs::{remove_file, OpenOptions},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
path::PathBuf,
|
||||
@@ -1331,17 +1429,29 @@ pub mod test {
|
||||
let vote = Vote::default();
|
||||
let mut decision = SwitchForkDecision::FailedSwitchThreshold(0, 1);
|
||||
assert!(decision
|
||||
.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default())
|
||||
.to_vote_instruction(
|
||||
VoteTransaction::from(vote.clone()),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default()
|
||||
)
|
||||
.is_none());
|
||||
|
||||
decision = SwitchForkDecision::FailedSwitchDuplicateRollback(0);
|
||||
assert!(decision
|
||||
.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default())
|
||||
.to_vote_instruction(
|
||||
VoteTransaction::from(vote.clone()),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default()
|
||||
)
|
||||
.is_none());
|
||||
|
||||
decision = SwitchForkDecision::SameFork;
|
||||
assert_eq!(
|
||||
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||
decision.to_vote_instruction(
|
||||
VoteTransaction::from(vote.clone()),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default()
|
||||
),
|
||||
Some(vote_instruction::vote(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
@@ -1351,7 +1461,11 @@ pub mod test {
|
||||
|
||||
decision = SwitchForkDecision::SwitchProof(Hash::default());
|
||||
assert_eq!(
|
||||
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||
decision.to_vote_instruction(
|
||||
VoteTransaction::from(vote.clone()),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default()
|
||||
),
|
||||
Some(vote_instruction::vote_switch(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
@@ -1373,7 +1487,7 @@ pub mod test {
|
||||
|
||||
// Set the voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let votes = vec![0, 1, 2, 3, 4, 5];
|
||||
let votes = vec![1, 2, 3, 4, 5];
|
||||
cluster_votes.insert(node_pubkey, votes.clone());
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes, true);
|
||||
|
||||
@@ -1384,9 +1498,12 @@ pub mod test {
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
for i in 0..5 {
|
||||
assert_eq!(tower.vote_state.votes[i].slot as usize, i);
|
||||
assert_eq!(tower.vote_state.votes[i].confirmation_count as usize, 6 - i);
|
||||
for i in 1..5 {
|
||||
assert_eq!(tower.vote_state.votes[i - 1].slot as usize, i);
|
||||
assert_eq!(
|
||||
tower.vote_state.votes[i - 1].confirmation_count as usize,
|
||||
6 - i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1846,7 +1963,7 @@ pub mod test {
|
||||
/ (tr(44)
|
||||
// Minor fork 2
|
||||
/ (tr(45) / (tr(46))))
|
||||
/ (tr(110)))));
|
||||
/ (tr(110)))));
|
||||
|
||||
// Have two validators, each representing 20% of the stake vote on
|
||||
// minor fork 2 at slots 46 + 47
|
||||
@@ -1918,7 +2035,7 @@ pub mod test {
|
||||
let mut my_votes: Vec<Slot> = vec![];
|
||||
let next_unlocked_slot = 110;
|
||||
// Vote on the first minor fork
|
||||
my_votes.extend(0..=14);
|
||||
my_votes.extend(1..=14);
|
||||
// Come back to the main fork
|
||||
my_votes.extend(43..=44);
|
||||
// Vote on the second minor fork
|
||||
@@ -2107,8 +2224,8 @@ pub mod test {
|
||||
#[test]
|
||||
fn test_is_locked_out_empty() {
|
||||
let tower = Tower::new_for_tests(0, 0.67);
|
||||
let ancestors = HashSet::new();
|
||||
assert!(!tower.is_locked_out(0, &ancestors));
|
||||
let ancestors = HashSet::from([0]);
|
||||
assert!(!tower.is_locked_out(1, &ancestors));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2139,7 +2256,7 @@ pub mod test {
|
||||
#[test]
|
||||
fn test_check_recent_slot() {
|
||||
let mut tower = Tower::new_for_tests(0, 0.67);
|
||||
assert!(tower.is_recent(0));
|
||||
assert!(tower.is_recent(1));
|
||||
assert!(tower.is_recent(32));
|
||||
for i in 0..64 {
|
||||
tower.record_vote(i, Hash::default());
|
||||
@@ -2254,7 +2371,7 @@ pub mod test {
|
||||
let mut local = VoteState::default();
|
||||
let vote = Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), None);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
assert_eq!(vote.slots, vec![0]);
|
||||
assert_eq!(vote.slots(), vec![0]);
|
||||
assert_eq!(local.tower(), vec![0]);
|
||||
}
|
||||
|
||||
@@ -2265,7 +2382,7 @@ pub mod test {
|
||||
// another vote for slot 0 should return an empty vote as the diff.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 0, Hash::default(), Some(0));
|
||||
assert!(vote.slots.is_empty());
|
||||
assert!(vote.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2280,7 +2397,7 @@ pub mod test {
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 1, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![1]);
|
||||
assert_eq!(vote.slots(), vec![1]);
|
||||
assert_eq!(local.tower(), vec![0, 1]);
|
||||
}
|
||||
|
||||
@@ -2300,7 +2417,7 @@ pub mod test {
|
||||
// observable in any of the results.
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 3, Hash::default(), Some(0));
|
||||
assert_eq!(vote.slots, vec![3]);
|
||||
assert_eq!(vote.slots(), vec![3]);
|
||||
assert_eq!(local.tower(), vec![3]);
|
||||
}
|
||||
|
||||
@@ -2373,17 +2490,26 @@ pub mod test {
|
||||
fn vote_and_check_recent(num_votes: usize) {
|
||||
let mut tower = Tower::new_for_tests(1, 0.67);
|
||||
let slots = if num_votes > 0 {
|
||||
vec![num_votes as u64 - 1]
|
||||
{ 0..num_votes }
|
||||
.map(|i| Lockout {
|
||||
slot: i as u64,
|
||||
confirmation_count: (num_votes as u32) - (i as u32),
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let mut expected = Vote::new(slots, Hash::default());
|
||||
let mut expected = VoteStateUpdate::new(
|
||||
VecDeque::from(slots),
|
||||
if num_votes > 0 { Some(0) } else { None },
|
||||
Hash::default(),
|
||||
);
|
||||
for i in 0..num_votes {
|
||||
tower.record_vote(i as u64, Hash::default());
|
||||
}
|
||||
|
||||
expected.timestamp = tower.last_vote.timestamp;
|
||||
assert_eq!(expected, tower.last_vote)
|
||||
expected.timestamp = tower.last_vote.timestamp();
|
||||
assert_eq!(VoteTransaction::from(expected), tower.last_vote)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2683,10 +2809,12 @@ pub mod test {
|
||||
.write(true)
|
||||
.open(path)
|
||||
.unwrap();
|
||||
// 4 is the offset into SavedTowerVersions for the signature
|
||||
assert_eq!(file.seek(SeekFrom::Start(4)).unwrap(), 4);
|
||||
let mut buf = [0u8];
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 1);
|
||||
buf[0] = !buf[0];
|
||||
assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
|
||||
assert_eq!(file.seek(SeekFrom::Start(4)).unwrap(), 4);
|
||||
assert_eq!(file.write(&buf).unwrap(), 1);
|
||||
},
|
||||
);
|
||||
@@ -3025,7 +3153,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(1));
|
||||
tower.vote_state.votes.push_back(Lockout::new(0));
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(0);
|
||||
@@ -3043,7 +3171,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(1));
|
||||
tower.vote_state.votes.push_back(Lockout::new(2));
|
||||
let vote = Vote::new(vec![2], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(0);
|
||||
@@ -3068,7 +3196,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(0));
|
||||
tower.vote_state.votes.push_back(Lockout::new(1));
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(MAX_ENTRIES);
|
||||
@@ -3087,7 +3215,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(2));
|
||||
tower.vote_state.votes.push_back(Lockout::new(1));
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(0);
|
||||
@@ -3106,7 +3234,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(3));
|
||||
tower.vote_state.votes.push_back(Lockout::new(3));
|
||||
let vote = Vote::new(vec![3], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(0);
|
||||
@@ -3125,7 +3253,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(43));
|
||||
tower.vote_state.votes.push_back(Lockout::new(44));
|
||||
let vote = Vote::new(vec![44], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(42);
|
||||
@@ -3139,7 +3267,7 @@ pub mod test {
|
||||
let mut tower = Tower::new_for_tests(10, 0.9);
|
||||
tower.vote_state.votes.push_back(Lockout::new(0));
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(0);
|
||||
@@ -3153,7 +3281,7 @@ pub mod test {
|
||||
tower.vote_state.votes.push_back(Lockout::new(13));
|
||||
tower.vote_state.votes.push_back(Lockout::new(14));
|
||||
let vote = Vote::new(vec![14], Hash::default());
|
||||
tower.last_vote = vote;
|
||||
tower.last_vote = VoteTransaction::from(vote);
|
||||
tower.initialize_root(12);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
|
@@ -11,15 +11,14 @@ use {
|
||||
solana_runtime::{bank::Bank, cost_model::CostModel},
|
||||
solana_sdk::timing::timestamp,
|
||||
std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
sync::{Arc, RwLock},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
},
|
||||
};
|
||||
|
||||
// Update blockstore persistence storage when accumulated cost_table updates count exceeds the threshold
|
||||
const PERSIST_THRESHOLD: u64 = 1_000;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CostUpdateServiceTiming {
|
||||
last_print: u64,
|
||||
@@ -31,20 +30,25 @@ pub struct CostUpdateServiceTiming {
|
||||
impl CostUpdateServiceTiming {
|
||||
fn update(
|
||||
&mut self,
|
||||
update_cost_model_count: u64,
|
||||
update_cost_model_elapsed: u64,
|
||||
persist_cost_table_elapsed: u64,
|
||||
update_cost_model_count: Option<u64>,
|
||||
update_cost_model_elapsed: Option<u64>,
|
||||
persist_cost_table_elapsed: Option<u64>,
|
||||
) {
|
||||
self.update_cost_model_count += update_cost_model_count;
|
||||
self.update_cost_model_elapsed += update_cost_model_elapsed;
|
||||
self.persist_cost_table_elapsed += persist_cost_table_elapsed;
|
||||
if let Some(update_cost_model_count) = update_cost_model_count {
|
||||
self.update_cost_model_count += update_cost_model_count;
|
||||
}
|
||||
if let Some(update_cost_model_elapsed) = update_cost_model_elapsed {
|
||||
self.update_cost_model_elapsed += update_cost_model_elapsed;
|
||||
}
|
||||
if let Some(persist_cost_table_elapsed) = persist_cost_table_elapsed {
|
||||
self.persist_cost_table_elapsed += persist_cost_table_elapsed;
|
||||
}
|
||||
|
||||
let now = timestamp();
|
||||
let elapsed_ms = now - self.last_print;
|
||||
if elapsed_ms > 1000 {
|
||||
datapoint_info!(
|
||||
"cost-update-service-stats",
|
||||
("total_elapsed_us", elapsed_ms * 1000, i64),
|
||||
(
|
||||
"update_cost_model_count",
|
||||
self.update_cost_model_count as i64,
|
||||
@@ -86,7 +90,6 @@ pub struct CostUpdateService {
|
||||
impl CostUpdateService {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
exit: Arc<AtomicBool>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
cost_model: Arc<RwLock<CostModel>>,
|
||||
cost_update_receiver: CostUpdateReceiver,
|
||||
@@ -94,7 +97,7 @@ impl CostUpdateService {
|
||||
let thread_hdl = Builder::new()
|
||||
.name("solana-cost-update-service".to_string())
|
||||
.spawn(move || {
|
||||
Self::service_loop(exit, blockstore, cost_model, cost_update_receiver);
|
||||
Self::service_loop(blockstore, cost_model, cost_update_receiver);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -106,118 +109,99 @@ impl CostUpdateService {
|
||||
}
|
||||
|
||||
fn service_loop(
|
||||
exit: Arc<AtomicBool>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
cost_model: Arc<RwLock<CostModel>>,
|
||||
cost_update_receiver: CostUpdateReceiver,
|
||||
) {
|
||||
let mut cost_update_service_timing = CostUpdateServiceTiming::default();
|
||||
let mut dirty: bool;
|
||||
let mut update_count: u64;
|
||||
let wait_timer = Duration::from_millis(100);
|
||||
let mut update_count = 0_u64;
|
||||
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
for cost_update in cost_update_receiver.iter() {
|
||||
match cost_update {
|
||||
CostUpdate::FrozenBank { bank } => {
|
||||
bank.read_cost_tracker().unwrap().report_stats(bank.slot());
|
||||
}
|
||||
CostUpdate::ExecuteTiming {
|
||||
mut execute_timings,
|
||||
} => {
|
||||
let mut update_cost_model_time = Measure::start("update_cost_model_time");
|
||||
update_count += Self::update_cost_model(&cost_model, &mut execute_timings);
|
||||
update_cost_model_time.stop();
|
||||
cost_update_service_timing.update(
|
||||
Some(update_count),
|
||||
Some(update_cost_model_time.as_us()),
|
||||
None,
|
||||
);
|
||||
|
||||
dirty = false;
|
||||
update_count = 0_u64;
|
||||
let mut update_cost_model_time = Measure::start("update_cost_model_time");
|
||||
for cost_update in cost_update_receiver.try_iter() {
|
||||
match cost_update {
|
||||
CostUpdate::FrozenBank { bank } => {
|
||||
bank.read_cost_tracker().unwrap().report_stats(bank.slot());
|
||||
}
|
||||
CostUpdate::ExecuteTiming {
|
||||
mut execute_timings,
|
||||
} => {
|
||||
dirty |= Self::update_cost_model(&cost_model, &mut execute_timings);
|
||||
update_count += 1;
|
||||
if update_count > PERSIST_THRESHOLD {
|
||||
let mut persist_cost_table_time = Measure::start("persist_cost_table_time");
|
||||
Self::persist_cost_table(&blockstore, &cost_model);
|
||||
update_count = 0_u64;
|
||||
persist_cost_table_time.stop();
|
||||
cost_update_service_timing.update(
|
||||
None,
|
||||
None,
|
||||
Some(persist_cost_table_time.as_us()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
update_cost_model_time.stop();
|
||||
|
||||
let mut persist_cost_table_time = Measure::start("persist_cost_table_time");
|
||||
if dirty {
|
||||
Self::persist_cost_table(&blockstore, &cost_model);
|
||||
}
|
||||
persist_cost_table_time.stop();
|
||||
|
||||
cost_update_service_timing.update(
|
||||
update_count,
|
||||
update_cost_model_time.as_us(),
|
||||
persist_cost_table_time.as_us(),
|
||||
);
|
||||
|
||||
thread::sleep(wait_timer);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize `program_timings` with current estimated cost, update instruction_cost table
|
||||
// Returns number of updates applied
|
||||
fn update_cost_model(
|
||||
cost_model: &RwLock<CostModel>,
|
||||
execute_timings: &mut ExecuteTimings,
|
||||
) -> bool {
|
||||
let mut dirty = false;
|
||||
{
|
||||
for (program_id, program_timings) in &mut execute_timings.details.per_program_timings {
|
||||
let current_estimated_program_cost =
|
||||
cost_model.read().unwrap().find_instruction_cost(program_id);
|
||||
program_timings.coalesce_error_timings(current_estimated_program_cost);
|
||||
) -> u64 {
|
||||
let mut update_count = 0_u64;
|
||||
for (program_id, program_timings) in &mut execute_timings.details.per_program_timings {
|
||||
let current_estimated_program_cost =
|
||||
cost_model.read().unwrap().find_instruction_cost(program_id);
|
||||
program_timings.coalesce_error_timings(current_estimated_program_cost);
|
||||
|
||||
if program_timings.count < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let units = program_timings.accumulated_units / program_timings.count as u64;
|
||||
match cost_model
|
||||
.write()
|
||||
.unwrap()
|
||||
.upsert_instruction_cost(program_id, units)
|
||||
{
|
||||
Ok(c) => {
|
||||
debug!(
|
||||
"after replayed into bank, instruction {:?} has averaged cost {}",
|
||||
program_id, c
|
||||
);
|
||||
dirty = true;
|
||||
}
|
||||
Err(err) => {
|
||||
debug!(
|
||||
"after replayed into bank, instruction {:?} failed to update cost, err: {}",
|
||||
program_id, err
|
||||
);
|
||||
}
|
||||
}
|
||||
if program_timings.count < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let units = program_timings.accumulated_units / program_timings.count as u64;
|
||||
cost_model
|
||||
.write()
|
||||
.unwrap()
|
||||
.upsert_instruction_cost(program_id, units);
|
||||
update_count += 1;
|
||||
debug!(
|
||||
"After replayed into bank, updated cost for instruction {:?}, update_value {}, pre_aggregated_value {}",
|
||||
program_id, units, current_estimated_program_cost
|
||||
);
|
||||
}
|
||||
debug!(
|
||||
"after replayed into bank, updated cost model instruction cost table, current values: {:?}",
|
||||
cost_model.read().unwrap().get_instruction_cost_table()
|
||||
);
|
||||
dirty
|
||||
update_count
|
||||
}
|
||||
|
||||
// 1. Remove obsolete program entries from persisted table to limit its size
|
||||
// 2. Update persisted program cost. This involves EMA cost calculation at
|
||||
// execute_cost_table.get_cost()
|
||||
fn persist_cost_table(blockstore: &Blockstore, cost_model: &RwLock<CostModel>) {
|
||||
let cost_model_read = cost_model.read().unwrap();
|
||||
let cost_table = cost_model_read.get_instruction_cost_table();
|
||||
let db_records = blockstore.read_program_costs().expect("read programs");
|
||||
let cost_model = cost_model.read().unwrap();
|
||||
let active_program_keys = cost_model.get_program_keys();
|
||||
|
||||
// delete records from blockstore if they are no longer in cost_table
|
||||
db_records.iter().for_each(|(pubkey, _)| {
|
||||
if cost_table.get(pubkey).is_none() {
|
||||
if !active_program_keys.contains(&pubkey) {
|
||||
blockstore
|
||||
.delete_program_cost(pubkey)
|
||||
.expect("delete old program");
|
||||
}
|
||||
});
|
||||
|
||||
for (key, cost) in cost_table.iter() {
|
||||
active_program_keys.iter().for_each(|program_id| {
|
||||
let cost = cost_model.find_instruction_cost(program_id);
|
||||
blockstore
|
||||
.write_program_cost(key, cost)
|
||||
.write_program_cost(program_id, &cost)
|
||||
.expect("persist program costs to blockstore");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,15 +213,9 @@ mod tests {
|
||||
fn test_update_cost_model_with_empty_execute_timings() {
|
||||
let cost_model = Arc::new(RwLock::new(CostModel::default()));
|
||||
let mut empty_execute_timings = ExecuteTimings::default();
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut empty_execute_timings);
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.len()
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut empty_execute_timings),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,7 +233,7 @@ mod tests {
|
||||
let accumulated_units: u64 = 100;
|
||||
let total_errored_units = 0;
|
||||
let count: u32 = 10;
|
||||
expected_cost = accumulated_units / count as u64;
|
||||
expected_cost = accumulated_units / count as u64; // = 10
|
||||
|
||||
execute_timings.details.per_program_timings.insert(
|
||||
program_key_1,
|
||||
@@ -267,22 +245,15 @@ mod tests {
|
||||
total_errored_units,
|
||||
},
|
||||
);
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
let update_count =
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
assert_eq!(1, update_count);
|
||||
assert_eq!(
|
||||
1,
|
||||
expected_cost,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&expected_cost),
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.get(&program_key_1)
|
||||
.find_instruction_cost(&program_key_1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -291,8 +262,8 @@ mod tests {
|
||||
let accumulated_us: u64 = 2000;
|
||||
let accumulated_units: u64 = 200;
|
||||
let count: u32 = 10;
|
||||
// to expect new cost is Average(new_value, existing_value)
|
||||
expected_cost = ((accumulated_units / count as u64) + expected_cost) / 2;
|
||||
// to expect new cost = (mean + 2 * std) of [10, 20]
|
||||
expected_cost = 13;
|
||||
|
||||
execute_timings.details.per_program_timings.insert(
|
||||
program_key_1,
|
||||
@@ -304,22 +275,15 @@ mod tests {
|
||||
total_errored_units: 0,
|
||||
},
|
||||
);
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
let update_count =
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
assert_eq!(1, update_count);
|
||||
assert_eq!(
|
||||
1,
|
||||
expected_cost,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&expected_cost),
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.get(&program_key_1)
|
||||
.find_instruction_cost(&program_key_1)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -343,20 +307,49 @@ mod tests {
|
||||
total_errored_units: 0,
|
||||
},
|
||||
);
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
// If both the `errored_txs_compute_consumed` is empty and `count == 0`, then
|
||||
// nothing should be inserted into the cost model
|
||||
assert!(cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// set up current instruction cost to 100
|
||||
let current_program_cost = 100;
|
||||
{
|
||||
execute_timings.details.per_program_timings.insert(
|
||||
program_key_1,
|
||||
ProgramTiming {
|
||||
accumulated_us: 1000,
|
||||
accumulated_units: current_program_cost,
|
||||
count: 1,
|
||||
errored_txs_compute_consumed: vec![],
|
||||
total_errored_units: 0,
|
||||
},
|
||||
);
|
||||
let update_count =
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
assert_eq!(1, update_count);
|
||||
assert_eq!(
|
||||
current_program_cost,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.find_instruction_cost(&program_key_1)
|
||||
);
|
||||
}
|
||||
|
||||
// Test updating cost model with only erroring compute costs where the `cost_per_error` is
|
||||
// greater than the current instruction cost for the program. Should update with the
|
||||
// new erroring compute costs
|
||||
let cost_per_error = 1000;
|
||||
// expected_cost = (mean + 2*std) of data points:
|
||||
// [
|
||||
// 100, // original program_cost
|
||||
// 1000, // cost_per_error
|
||||
// ]
|
||||
let expected_cost = 289u64;
|
||||
{
|
||||
let errored_txs_compute_consumed = vec![cost_per_error; 3];
|
||||
let total_errored_units = errored_txs_compute_consumed.iter().sum();
|
||||
@@ -370,29 +363,23 @@ mod tests {
|
||||
total_errored_units,
|
||||
},
|
||||
);
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
let update_count =
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
|
||||
assert_eq!(1, update_count);
|
||||
assert_eq!(
|
||||
1,
|
||||
expected_cost,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&cost_per_error),
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.get(&program_key_1)
|
||||
.find_instruction_cost(&program_key_1)
|
||||
);
|
||||
}
|
||||
|
||||
// Test updating cost model with only erroring compute costs where the error cost is
|
||||
// `smaller_cost_per_error`, less than the current instruction cost for the program.
|
||||
// The cost should not decrease for these new lesser errors
|
||||
let smaller_cost_per_error = cost_per_error - 10;
|
||||
let smaller_cost_per_error = expected_cost - 10;
|
||||
{
|
||||
let errored_txs_compute_consumed = vec![smaller_cost_per_error; 3];
|
||||
let total_errored_units = errored_txs_compute_consumed.iter().sum();
|
||||
@@ -406,22 +393,23 @@ mod tests {
|
||||
total_errored_units,
|
||||
},
|
||||
);
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
let update_count =
|
||||
CostUpdateService::update_cost_model(&cost_model, &mut execute_timings);
|
||||
|
||||
// expected_cost = (mean = 2*std) of data points:
|
||||
// [
|
||||
// 100, // original program cost,
|
||||
// 1000, // cost_per_error from above test
|
||||
// 289, // the smaller_cost_per_error will be coalesced to prev cost
|
||||
// ]
|
||||
let expected_cost = 293u64;
|
||||
assert_eq!(1, update_count);
|
||||
assert_eq!(
|
||||
1,
|
||||
expected_cost,
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.len()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(&cost_per_error),
|
||||
cost_model
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_instruction_cost_table()
|
||||
.get(&program_key_1)
|
||||
.find_instruction_cost(&program_key_1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -2715,7 +2715,8 @@ mod test {
|
||||
stake
|
||||
);
|
||||
}
|
||||
for slot in &[17] {
|
||||
{
|
||||
let slot = &17;
|
||||
assert_eq!(
|
||||
tree1
|
||||
.stake_voted_subtree(&(*slot, Hash::default()))
|
||||
|
871
core/src/leader_slot_banking_stage_metrics.rs
Normal file
871
core/src/leader_slot_banking_stage_metrics.rs
Normal file
@@ -0,0 +1,871 @@
|
||||
use {
|
||||
crate::leader_slot_banking_stage_timing_metrics::*,
|
||||
solana_poh::poh_recorder::BankStart,
|
||||
solana_sdk::{clock::Slot, saturating_add_assign},
|
||||
std::time::Instant,
|
||||
};
|
||||
|
||||
/// A summary of what happened to transactions passed to the execution pipeline.
|
||||
/// Transactions can
|
||||
/// 1) Did not even make it to execution due to being filtered out by things like AccountInUse
|
||||
/// lock conflicts or CostModel compute limits. These types of errors are retryable and
|
||||
/// counted in `Self::retryable_transaction_indexes`.
|
||||
/// 2) Did not execute due to some fatal error like too old, or duplicate signature. These
|
||||
/// will be dropped from the transactions queue and not counted in `Self::retryable_transaction_indexes`
|
||||
/// 3) Were executed and committed, captured by `committed_transactions_count` below.
|
||||
/// 4) Were executed and failed commit, captured by `failed_commit_count` below.
|
||||
pub(crate) struct ProcessTransactionsSummary {
|
||||
// Returns true if we hit the end of the block/max PoH height for the block before
|
||||
// processing all the transactions in the batch.
|
||||
pub reached_max_poh_height: bool,
|
||||
|
||||
// Total number of transactions that were passed as candidates for execution. See description
|
||||
// of struct above for possible outcomes for these transactions
|
||||
pub transactions_attempted_execution_count: usize,
|
||||
|
||||
// Total number of transactions that made it into the block
|
||||
pub committed_transactions_count: usize,
|
||||
|
||||
// Total number of transactions that made it into the block where the transactions
|
||||
// output from execution was success/no error.
|
||||
pub committed_transactions_with_successful_result_count: usize,
|
||||
|
||||
// All transactions that were executed but then failed record because the
|
||||
// slot ended
|
||||
pub failed_commit_count: usize,
|
||||
|
||||
// Indexes of transactions in the transactions slice that were not committed but are retryable
|
||||
pub retryable_transaction_indexes: Vec<usize>,
|
||||
|
||||
// The number of transactions filtered out by the cost model
|
||||
pub cost_model_throttled_transactions_count: usize,
|
||||
|
||||
// Total amount of time spent running the cost model
|
||||
pub cost_model_us: u64,
|
||||
|
||||
// Breakdown of time spent executing and comitting transactions
|
||||
pub execute_and_commit_timings: LeaderExecuteAndCommitTimings,
|
||||
}
|
||||
|
||||
// Metrics describing packets ingested/processed in various parts of BankingStage during this
|
||||
// validator's leader slot
|
||||
#[derive(Debug, Default)]
|
||||
struct LeaderSlotPacketCountMetrics {
|
||||
// total number of live packets TPU received from verified receiver for processing.
|
||||
total_new_valid_packets: u64,
|
||||
|
||||
// total number of packets TPU received from sigverify that failed signature verification.
|
||||
newly_failed_sigverify_count: u64,
|
||||
|
||||
// total number of dropped packet due to the thread's buffered packets capacity being reached.
|
||||
exceeded_buffer_limit_dropped_packets_count: u64,
|
||||
|
||||
// total number of packets that got added to the pending buffer after arriving to BankingStage
|
||||
newly_buffered_packets_count: u64,
|
||||
|
||||
// total number of transactions in the buffer that were filtered out due to things like age and
|
||||
// duplicate signature checks
|
||||
retryable_packets_filtered_count: u64,
|
||||
|
||||
// total number of transactions that attempted execution in this slot. Should equal the sum
|
||||
// of `committed_transactions_count`, `retryable_errored_transaction_count`, and
|
||||
// `nonretryable_errored_transactions_count`.
|
||||
transactions_attempted_execution_count: u64,
|
||||
|
||||
// total number of transactions that were executed and committed into the block
|
||||
// on this thread
|
||||
committed_transactions_count: u64,
|
||||
|
||||
// total number of transactions that were executed, got a successful execution output/no error,
|
||||
// and were then committed into the block
|
||||
committed_transactions_with_successful_result_count: u64,
|
||||
|
||||
// total number of transactions that were not executed or failed commit, BUT were added back to the buffered
|
||||
// queue becaus they were retryable errors
|
||||
retryable_errored_transaction_count: u64,
|
||||
|
||||
// total number of transactions that attempted execution due to some fatal error (too old, duplicate signature, etc.)
|
||||
// AND were dropped from the buffered queue
|
||||
nonretryable_errored_transactions_count: u64,
|
||||
|
||||
// total number of transactions that were executed, but failed to be committed into the Poh stream because
|
||||
// the block ended. Some of these may be already counted in `nonretryable_errored_transactions_count` if they
|
||||
// then hit the age limit after failing to be comitted.
|
||||
executed_transactions_failed_commit_count: u64,
|
||||
|
||||
// total number of transactions that were excluded from the block because they were too expensive
|
||||
// according to the cost model. These transactions are added back to the buffered queue and are
|
||||
// already counted in `self.retrayble_errored_transaction_count`.
|
||||
cost_model_throttled_transactions_count: u64,
|
||||
|
||||
// total number of forwardsable packets that failed forwarding
|
||||
failed_forwarded_packets_count: u64,
|
||||
|
||||
// total number of forwardsable packets that were successfully forwarded
|
||||
successful_forwarded_packets_count: u64,
|
||||
|
||||
// total number of attempted forwards that failed. Note this is not a count of the number of packets
|
||||
// that failed, just the total number of batches of packets that failed forwarding
|
||||
packet_batch_forward_failure_count: u64,
|
||||
|
||||
// total number of valid unprocessed packets in the buffer that were removed after being forwarded
|
||||
cleared_from_buffer_after_forward_count: u64,
|
||||
|
||||
// total number of packets removed at the end of the slot due to being too old, duplicate, etc.
|
||||
end_of_slot_filtered_invalid_count: u64,
|
||||
}
|
||||
|
||||
impl LeaderSlotPacketCountMetrics {
|
||||
fn new() -> Self {
|
||||
Self { ..Self::default() }
|
||||
}
|
||||
|
||||
fn report(&self, id: u32, slot: Slot) {
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_packet_counts",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"total_new_valid_packets",
|
||||
self.total_new_valid_packets as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"newly_failed_sigverify_count",
|
||||
self.newly_failed_sigverify_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"exceeded_buffer_limit_dropped_packets_count",
|
||||
self.exceeded_buffer_limit_dropped_packets_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"newly_buffered_packets_count",
|
||||
self.newly_buffered_packets_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"retryable_packets_filtered_count",
|
||||
self.retryable_packets_filtered_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"transactions_attempted_execution_count",
|
||||
self.transactions_attempted_execution_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"committed_transactions_count",
|
||||
self.committed_transactions_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"committed_transactions_with_successful_result_count",
|
||||
self.committed_transactions_with_successful_result_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"retryable_errored_transaction_count",
|
||||
self.retryable_errored_transaction_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"nonretryable_errored_transactions_count",
|
||||
self.nonretryable_errored_transactions_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"executed_transactions_failed_commit_count",
|
||||
self.executed_transactions_failed_commit_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"cost_model_throttled_transactions_count",
|
||||
self.cost_model_throttled_transactions_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"failed_forwarded_packets_count",
|
||||
self.failed_forwarded_packets_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"successful_forwarded_packets_count",
|
||||
self.successful_forwarded_packets_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"packet_batch_forward_failure_count",
|
||||
self.packet_batch_forward_failure_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"cleared_from_buffer_after_forward_count",
|
||||
self.cleared_from_buffer_after_forward_count as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"end_of_slot_filtered_invalid_count",
|
||||
self.end_of_slot_filtered_invalid_count as i64,
|
||||
i64
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LeaderSlotMetrics {
|
||||
// banking_stage creates one QosService instance per working threads, that is uniquely
|
||||
// identified by id. This field allows to categorize metrics for gossip votes, TPU votes
|
||||
// and other transactions.
|
||||
id: u32,
|
||||
|
||||
// aggregate metrics per slot
|
||||
slot: Slot,
|
||||
|
||||
packet_count_metrics: LeaderSlotPacketCountMetrics,
|
||||
|
||||
timing_metrics: LeaderSlotTimingMetrics,
|
||||
|
||||
// Used by tests to check if the `self.report()` method was called
|
||||
is_reported: bool,
|
||||
}
|
||||
|
||||
impl LeaderSlotMetrics {
|
||||
pub(crate) fn new(id: u32, slot: Slot, bank_creation_time: &Instant) -> Self {
|
||||
Self {
|
||||
id,
|
||||
slot,
|
||||
packet_count_metrics: LeaderSlotPacketCountMetrics::new(),
|
||||
timing_metrics: LeaderSlotTimingMetrics::new(bank_creation_time),
|
||||
is_reported: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report(&mut self) {
|
||||
self.is_reported = true;
|
||||
|
||||
self.timing_metrics.report(self.id, self.slot);
|
||||
self.packet_count_metrics.report(self.id, self.slot);
|
||||
}
|
||||
|
||||
/// Returns `Some(self.slot)` if the metrics have been reported, otherwise returns None
|
||||
fn reported_slot(&self) -> Option<Slot> {
|
||||
if self.is_reported {
|
||||
Some(self.slot)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LeaderSlotMetricsTracker {
|
||||
// Only `Some` if BankingStage detects it's time to construct our leader slot,
|
||||
// otherwise `None`
|
||||
leader_slot_metrics: Option<LeaderSlotMetrics>,
|
||||
id: u32,
|
||||
}
|
||||
|
||||
impl LeaderSlotMetricsTracker {
|
||||
pub fn new(id: u32) -> Self {
|
||||
Self {
|
||||
leader_slot_metrics: None,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns reported slot if metrics were reported
|
||||
pub(crate) fn update_on_leader_slot_boundary(
|
||||
&mut self,
|
||||
bank_start: &Option<BankStart>,
|
||||
) -> Option<Slot> {
|
||||
match (self.leader_slot_metrics.as_mut(), bank_start) {
|
||||
(None, None) => None,
|
||||
|
||||
(Some(leader_slot_metrics), None) => {
|
||||
leader_slot_metrics.report();
|
||||
// Ensure tests catch that `report()` method was called
|
||||
let reported_slot = leader_slot_metrics.reported_slot();
|
||||
// Slot has ended, time to report metrics
|
||||
self.leader_slot_metrics = None;
|
||||
reported_slot
|
||||
}
|
||||
|
||||
(None, Some(bank_start)) => {
|
||||
// Our leader slot has begain, time to create a new slot tracker
|
||||
self.leader_slot_metrics = Some(LeaderSlotMetrics::new(
|
||||
self.id,
|
||||
bank_start.working_bank.slot(),
|
||||
&bank_start.bank_creation_time,
|
||||
));
|
||||
self.leader_slot_metrics.as_ref().unwrap().reported_slot()
|
||||
}
|
||||
|
||||
(Some(leader_slot_metrics), Some(bank_start)) => {
|
||||
if leader_slot_metrics.slot != bank_start.working_bank.slot() {
|
||||
// Last slot has ended, new slot has began
|
||||
leader_slot_metrics.report();
|
||||
// Ensure tests catch that `report()` method was called
|
||||
let reported_slot = leader_slot_metrics.reported_slot();
|
||||
self.leader_slot_metrics = Some(LeaderSlotMetrics::new(
|
||||
self.id,
|
||||
bank_start.working_bank.slot(),
|
||||
&bank_start.bank_creation_time,
|
||||
));
|
||||
reported_slot
|
||||
} else {
|
||||
leader_slot_metrics.reported_slot()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn accumulate_process_transactions_summary(
|
||||
&mut self,
|
||||
process_transactions_summary: &ProcessTransactionsSummary,
|
||||
) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
let ProcessTransactionsSummary {
|
||||
transactions_attempted_execution_count,
|
||||
committed_transactions_count,
|
||||
committed_transactions_with_successful_result_count,
|
||||
failed_commit_count,
|
||||
ref retryable_transaction_indexes,
|
||||
cost_model_throttled_transactions_count,
|
||||
cost_model_us,
|
||||
ref execute_and_commit_timings,
|
||||
..
|
||||
} = process_transactions_summary;
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.transactions_attempted_execution_count,
|
||||
*transactions_attempted_execution_count as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.committed_transactions_count,
|
||||
*committed_transactions_count as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.committed_transactions_with_successful_result_count,
|
||||
*committed_transactions_with_successful_result_count as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.executed_transactions_failed_commit_count,
|
||||
*failed_commit_count as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.retryable_errored_transaction_count,
|
||||
retryable_transaction_indexes.len() as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.nonretryable_errored_transactions_count,
|
||||
transactions_attempted_execution_count
|
||||
.saturating_sub(*committed_transactions_count)
|
||||
.saturating_sub(retryable_transaction_indexes.len()) as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.cost_model_throttled_transactions_count,
|
||||
*cost_model_throttled_transactions_count as u64
|
||||
);
|
||||
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_packets_timings
|
||||
.cost_model_us,
|
||||
*cost_model_us as u64
|
||||
);
|
||||
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.execute_and_commit_timings
|
||||
.accumulate(execute_and_commit_timings);
|
||||
}
|
||||
}
|
||||
|
||||
// Packet inflow/outflow/processing metrics
|
||||
pub(crate) fn increment_total_new_valid_packets(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.total_new_valid_packets,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_newly_failed_sigverify_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.newly_failed_sigverify_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_exceeded_buffer_limit_dropped_packets_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.exceeded_buffer_limit_dropped_packets_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_newly_buffered_packets_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.newly_buffered_packets_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_retryable_packets_filtered_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.retryable_packets_filtered_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_failed_forwarded_packets_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.failed_forwarded_packets_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_successful_forwarded_packets_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.successful_forwarded_packets_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_packet_batch_forward_failure_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.packet_batch_forward_failure_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_cleared_from_buffer_after_forward_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.cleared_from_buffer_after_forward_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_end_of_slot_filtered_invalid_count(&mut self, count: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.packet_count_metrics
|
||||
.end_of_slot_filtered_invalid_count,
|
||||
count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Outermost banking thread's loop timing metrics
|
||||
pub(crate) fn increment_process_buffered_packets_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.outer_loop_timings
|
||||
.process_buffered_packets_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_slot_metrics_check_slot_boundary_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.outer_loop_timings
|
||||
.slot_metrics_check_slot_boundary_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_receive_and_buffer_packets_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.outer_loop_timings
|
||||
.receive_and_buffer_packets_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing buffer timing metrics
|
||||
pub(crate) fn increment_make_decision_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_buffered_packets_timings
|
||||
.make_decision_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_consume_buffered_packets_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_buffered_packets_timings
|
||||
.consume_buffered_packets_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_forward_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_buffered_packets_timings
|
||||
.forward_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_forward_and_hold_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_buffered_packets_timings
|
||||
.forward_and_hold_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Consuming buffered packets timing metrics
|
||||
pub(crate) fn increment_end_of_slot_filtering_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.consume_buffered_packets_timings
|
||||
.end_of_slot_filtering_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_consume_buffered_packets_poh_recorder_lock_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.consume_buffered_packets_timings
|
||||
.poh_recorder_lock_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_process_packets_transactions_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.consume_buffered_packets_timings
|
||||
.process_packets_transactions_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing packets timing metrics
|
||||
pub(crate) fn increment_transactions_from_packets_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_packets_timings
|
||||
.transactions_from_packets_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_process_transactions_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_packets_timings
|
||||
.process_transactions_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increment_filter_retryable_packets_us(&mut self, us: u64) {
|
||||
if let Some(leader_slot_metrics) = &mut self.leader_slot_metrics {
|
||||
saturating_add_assign!(
|
||||
leader_slot_metrics
|
||||
.timing_metrics
|
||||
.process_packets_timings
|
||||
.filter_retryable_packets_us,
|
||||
us
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::*,
|
||||
solana_runtime::{bank::Bank, genesis_utils::create_genesis_config},
|
||||
solana_sdk::pubkey::Pubkey,
|
||||
std::sync::Arc,
|
||||
};
|
||||
|
||||
struct TestSlotBoundaryComponents {
|
||||
first_bank: Arc<Bank>,
|
||||
first_poh_recorder_bank: BankStart,
|
||||
next_bank: Arc<Bank>,
|
||||
next_poh_recorder_bank: BankStart,
|
||||
leader_slot_metrics_tracker: LeaderSlotMetricsTracker,
|
||||
}
|
||||
|
||||
fn setup_test_slot_boundary_banks() -> TestSlotBoundaryComponents {
|
||||
let genesis = create_genesis_config(10);
|
||||
let first_bank = Arc::new(Bank::new_for_tests(&genesis.genesis_config));
|
||||
let first_poh_recorder_bank = BankStart {
|
||||
working_bank: first_bank.clone(),
|
||||
bank_creation_time: Arc::new(Instant::now()),
|
||||
};
|
||||
|
||||
// Create a child descended from the first bank
|
||||
let next_bank = Arc::new(Bank::new_from_parent(
|
||||
&first_bank,
|
||||
&Pubkey::new_unique(),
|
||||
first_bank.slot() + 1,
|
||||
));
|
||||
let next_poh_recorder_bank = BankStart {
|
||||
working_bank: next_bank.clone(),
|
||||
bank_creation_time: Arc::new(Instant::now()),
|
||||
};
|
||||
|
||||
let banking_stage_thread_id = 0;
|
||||
let leader_slot_metrics_tracker = LeaderSlotMetricsTracker::new(banking_stage_thread_id);
|
||||
|
||||
TestSlotBoundaryComponents {
|
||||
first_bank,
|
||||
first_poh_recorder_bank,
|
||||
next_bank,
|
||||
next_poh_recorder_bank,
|
||||
leader_slot_metrics_tracker,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_not_leader_to_not_leader() {
|
||||
let TestSlotBoundaryComponents {
|
||||
mut leader_slot_metrics_tracker,
|
||||
..
|
||||
} = setup_test_slot_boundary_banks();
|
||||
// Test that with no bank being tracked, and no new bank being tracked, nothing is reported
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.is_none());
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_not_leader_to_leader() {
|
||||
let TestSlotBoundaryComponents {
|
||||
first_poh_recorder_bank,
|
||||
mut leader_slot_metrics_tracker,
|
||||
..
|
||||
} = setup_test_slot_boundary_banks();
|
||||
|
||||
// Test case where the thread has not detected a leader bank, and now sees a leader bank.
|
||||
// Metrics should not be reported because leader slot has not ended
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank))
|
||||
.is_none());
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_leader_to_not_leader() {
|
||||
let TestSlotBoundaryComponents {
|
||||
first_bank,
|
||||
first_poh_recorder_bank,
|
||||
mut leader_slot_metrics_tracker,
|
||||
..
|
||||
} = setup_test_slot_boundary_banks();
|
||||
|
||||
// Test case where the thread has a leader bank, and now detects there's no more leader bank,
|
||||
// implying the slot has ended. Metrics should be reported for `first_bank.slot()`,
|
||||
// because that leader slot has just ended.
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank))
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.unwrap(),
|
||||
first_bank.slot()
|
||||
);
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_leader_to_leader_same_slot() {
|
||||
let TestSlotBoundaryComponents {
|
||||
first_bank,
|
||||
first_poh_recorder_bank,
|
||||
mut leader_slot_metrics_tracker,
|
||||
..
|
||||
} = setup_test_slot_boundary_banks();
|
||||
|
||||
// Test case where the thread has a leader bank, and now detects the same leader bank,
|
||||
// implying the slot is still running. Metrics should not be reported
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank.clone()))
|
||||
.is_none());
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank))
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.unwrap(),
|
||||
first_bank.slot()
|
||||
);
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_leader_to_leader_bigger_slot() {
|
||||
let TestSlotBoundaryComponents {
|
||||
first_bank,
|
||||
first_poh_recorder_bank,
|
||||
next_bank,
|
||||
next_poh_recorder_bank,
|
||||
mut leader_slot_metrics_tracker,
|
||||
} = setup_test_slot_boundary_banks();
|
||||
|
||||
// Test case where the thread has a leader bank, and now detects there's a new leader bank
|
||||
// for a bigger slot, implying the slot has ended. Metrics should be reported for the
|
||||
// smaller slot
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank))
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(next_poh_recorder_bank))
|
||||
.unwrap(),
|
||||
first_bank.slot()
|
||||
);
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.unwrap(),
|
||||
next_bank.slot()
|
||||
);
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_update_on_leader_slot_boundary_leader_to_leader_smaller_slot() {
|
||||
let TestSlotBoundaryComponents {
|
||||
first_bank,
|
||||
first_poh_recorder_bank,
|
||||
next_bank,
|
||||
next_poh_recorder_bank,
|
||||
mut leader_slot_metrics_tracker,
|
||||
} = setup_test_slot_boundary_banks();
|
||||
// Test case where the thread has a leader bank, and now detects there's a new leader bank
|
||||
// for a samller slot, implying the slot has ended. Metrics should be reported for the
|
||||
// bigger slot
|
||||
assert!(leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(next_poh_recorder_bank))
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&Some(first_poh_recorder_bank))
|
||||
.unwrap(),
|
||||
next_bank.slot()
|
||||
);
|
||||
assert_eq!(
|
||||
leader_slot_metrics_tracker
|
||||
.update_on_leader_slot_boundary(&None)
|
||||
.unwrap(),
|
||||
first_bank.slot()
|
||||
);
|
||||
assert!(leader_slot_metrics_tracker.leader_slot_metrics.is_none());
|
||||
}
|
||||
}
|
286
core/src/leader_slot_banking_stage_timing_metrics.rs
Normal file
286
core/src/leader_slot_banking_stage_timing_metrics.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
use {
|
||||
solana_program_runtime::timings::ExecuteTimings,
|
||||
solana_sdk::{clock::Slot, saturating_add_assign},
|
||||
std::time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LeaderExecuteAndCommitTimings {
|
||||
pub collect_balances_us: u64,
|
||||
pub load_execute_us: u64,
|
||||
pub freeze_lock_us: u64,
|
||||
pub record_us: u64,
|
||||
pub commit_us: u64,
|
||||
pub find_and_send_votes_us: u64,
|
||||
pub record_transactions_timings: RecordTransactionsTimings,
|
||||
pub execute_timings: ExecuteTimings,
|
||||
}
|
||||
|
||||
impl LeaderExecuteAndCommitTimings {
|
||||
pub fn accumulate(&mut self, other: &LeaderExecuteAndCommitTimings) {
|
||||
saturating_add_assign!(self.collect_balances_us, other.collect_balances_us);
|
||||
saturating_add_assign!(self.load_execute_us, other.load_execute_us);
|
||||
saturating_add_assign!(self.freeze_lock_us, other.freeze_lock_us);
|
||||
saturating_add_assign!(self.record_us, other.record_us);
|
||||
saturating_add_assign!(self.commit_us, other.commit_us);
|
||||
saturating_add_assign!(self.find_and_send_votes_us, other.find_and_send_votes_us);
|
||||
saturating_add_assign!(self.commit_us, other.commit_us);
|
||||
self.record_transactions_timings
|
||||
.accumulate(&other.record_transactions_timings);
|
||||
self.execute_timings.accumulate(&other.execute_timings);
|
||||
}
|
||||
|
||||
pub fn report(&self, id: u32, slot: Slot) {
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_execute_and_commit_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
("collect_balances_us", self.collect_balances_us as i64, i64),
|
||||
("load_execute_us", self.load_execute_us as i64, i64),
|
||||
("freeze_lock_us", self.freeze_lock_us as i64, i64),
|
||||
("record_us", self.record_us as i64, i64),
|
||||
("commit_us", self.commit_us as i64, i64),
|
||||
(
|
||||
"find_and_send_votes_us",
|
||||
self.find_and_send_votes_us as i64,
|
||||
i64
|
||||
),
|
||||
);
|
||||
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_record_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"execution_results_to_transactions_us",
|
||||
self.record_transactions_timings
|
||||
.execution_results_to_transactions_us as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"hash_us",
|
||||
self.record_transactions_timings.hash_us as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"poh_record_us",
|
||||
self.record_transactions_timings.poh_record_us as i64,
|
||||
i64
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RecordTransactionsTimings {
|
||||
pub execution_results_to_transactions_us: u64,
|
||||
pub hash_us: u64,
|
||||
pub poh_record_us: u64,
|
||||
}
|
||||
|
||||
impl RecordTransactionsTimings {
|
||||
pub fn accumulate(&mut self, other: &RecordTransactionsTimings) {
|
||||
saturating_add_assign!(
|
||||
self.execution_results_to_transactions_us,
|
||||
other.execution_results_to_transactions_us
|
||||
);
|
||||
saturating_add_assign!(self.hash_us, other.hash_us);
|
||||
saturating_add_assign!(self.poh_record_us, other.poh_record_us);
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics capturing wallclock time spent in various parts of BankingStage during this
|
||||
// validator's leader slot
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LeaderSlotTimingMetrics {
|
||||
pub outer_loop_timings: OuterLoopTimings,
|
||||
pub process_buffered_packets_timings: ProcessBufferedPacketsTimings,
|
||||
pub consume_buffered_packets_timings: ConsumeBufferedPacketsTimings,
|
||||
pub process_packets_timings: ProcessPacketsTimings,
|
||||
pub execute_and_commit_timings: LeaderExecuteAndCommitTimings,
|
||||
}
|
||||
|
||||
impl LeaderSlotTimingMetrics {
|
||||
pub(crate) fn new(bank_creation_time: &Instant) -> Self {
|
||||
Self {
|
||||
outer_loop_timings: OuterLoopTimings::new(bank_creation_time),
|
||||
process_buffered_packets_timings: ProcessBufferedPacketsTimings::default(),
|
||||
consume_buffered_packets_timings: ConsumeBufferedPacketsTimings::default(),
|
||||
process_packets_timings: ProcessPacketsTimings::default(),
|
||||
execute_and_commit_timings: LeaderExecuteAndCommitTimings::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self, id: u32, slot: Slot) {
|
||||
self.outer_loop_timings.report(id, slot);
|
||||
self.process_buffered_packets_timings.report(id, slot);
|
||||
self.consume_buffered_packets_timings.report(id, slot);
|
||||
self.process_packets_timings.report(id, slot);
|
||||
self.execute_and_commit_timings.report(id, slot);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OuterLoopTimings {
|
||||
pub bank_detected_time: Instant,
|
||||
|
||||
// Delay from when the bank was created to when this thread detected it
|
||||
pub bank_detected_delay_us: u64,
|
||||
|
||||
// Time spent processing buffered packets
|
||||
pub process_buffered_packets_us: u64,
|
||||
|
||||
// Time spent checking for slot boundary and reporting leader slot metrics
|
||||
pub slot_metrics_check_slot_boundary_us: u64,
|
||||
|
||||
// Time spent processing new incoming packets to the banking thread
|
||||
pub receive_and_buffer_packets_us: u64,
|
||||
}
|
||||
|
||||
impl OuterLoopTimings {
|
||||
fn new(bank_creation_time: &Instant) -> Self {
|
||||
Self {
|
||||
bank_detected_time: Instant::now(),
|
||||
bank_detected_delay_us: bank_creation_time.elapsed().as_micros() as u64,
|
||||
process_buffered_packets_us: 0,
|
||||
slot_metrics_check_slot_boundary_us: 0,
|
||||
receive_and_buffer_packets_us: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn report(&self, id: u32, slot: Slot) {
|
||||
let bank_detected_to_now_us = self.bank_detected_time.elapsed().as_micros() as u64;
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_loop_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"bank_detected_to_slot_end_detected_us",
|
||||
bank_detected_to_now_us,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"bank_creation_to_slot_end_detected_us",
|
||||
bank_detected_to_now_us + self.bank_detected_delay_us,
|
||||
i64
|
||||
),
|
||||
("bank_detected_delay_us", self.bank_detected_delay_us, i64),
|
||||
(
|
||||
"process_buffered_packets_us",
|
||||
self.process_buffered_packets_us,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"slot_metrics_check_slot_boundary_us",
|
||||
self.slot_metrics_check_slot_boundary_us,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"receive_and_buffer_packets_us",
|
||||
self.receive_and_buffer_packets_us,
|
||||
i64
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ProcessBufferedPacketsTimings {
|
||||
pub make_decision_us: u64,
|
||||
pub consume_buffered_packets_us: u64,
|
||||
pub forward_us: u64,
|
||||
pub forward_and_hold_us: u64,
|
||||
}
|
||||
impl ProcessBufferedPacketsTimings {
|
||||
fn report(&self, id: u32, slot: Slot) {
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_process_buffered_packets_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
("make_decision_us", self.make_decision_us as i64, i64),
|
||||
(
|
||||
"consume_buffered_packets_us",
|
||||
self.consume_buffered_packets_us as i64,
|
||||
i64
|
||||
),
|
||||
("forward_us", self.forward_us as i64, i64),
|
||||
("forward_and_hold_us", self.forward_and_hold_us as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ConsumeBufferedPacketsTimings {
|
||||
// Time spent grabbing poh recorder lock
|
||||
pub poh_recorder_lock_us: u64,
|
||||
|
||||
// Time spent filtering invalid packets after leader slot has ended
|
||||
pub end_of_slot_filtering_us: u64,
|
||||
|
||||
// Time spent processing transactions
|
||||
pub process_packets_transactions_us: u64,
|
||||
}
|
||||
|
||||
impl ConsumeBufferedPacketsTimings {
|
||||
fn report(&self, id: u32, slot: Slot) {
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_consume_buffered_packets_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"poh_recorder_lock_us",
|
||||
self.poh_recorder_lock_us as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"end_of_slot_filtering_us",
|
||||
self.end_of_slot_filtering_us as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"process_packets_transactions_us",
|
||||
self.process_packets_transactions_us as i64,
|
||||
i64
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ProcessPacketsTimings {
|
||||
// Time spent converting packets to transactions
|
||||
pub transactions_from_packets_us: u64,
|
||||
|
||||
// Time spent processing transactions
|
||||
pub process_transactions_us: u64,
|
||||
|
||||
// Time spent filtering retryable packets that were returned after transaction
|
||||
// processing
|
||||
pub filter_retryable_packets_us: u64,
|
||||
|
||||
// Time spent running the cost model in processing transactions before executing
|
||||
// transactions
|
||||
pub cost_model_us: u64,
|
||||
}
|
||||
|
||||
impl ProcessPacketsTimings {
|
||||
fn report(&self, id: u32, slot: Slot) {
|
||||
datapoint_info!(
|
||||
"banking_stage-leader_slot_process_packets_timings",
|
||||
("id", id as i64, i64),
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"transactions_from_packets_us",
|
||||
self.transactions_from_packets_us,
|
||||
i64
|
||||
),
|
||||
("process_transactions_us", self.process_transactions_us, i64),
|
||||
(
|
||||
"filter_retryable_packets_us",
|
||||
self.filter_retryable_packets_us,
|
||||
i64
|
||||
),
|
||||
("cost_model_us", self.cost_model_us, i64),
|
||||
);
|
||||
}
|
||||
}
|
@@ -117,6 +117,19 @@ impl LedgerCleanupService {
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper function to `cleanup_ledger` which returns a tuple of the
|
||||
/// following four elements suggesting whether to clean up the ledger:
|
||||
///
|
||||
/// Return value (bool, Slot, Slot, u64):
|
||||
/// - `slots_to_clean` (bool): a boolean value indicating whether there
|
||||
/// are any slots to clean. If true, then `cleanup_ledger` function
|
||||
/// will then proceed with the ledger cleanup.
|
||||
/// - `first_slot_to_purge` (Slot): the first slot to purge.
|
||||
/// - `lowest_slot_to_puerge` (Slot): the lowest slot to purge. Together
|
||||
/// with `first_slot_to_purge`, the two Slot values represent the
|
||||
/// range of the clean up.
|
||||
/// - `total_shreds` (u64): the total estimated number of shreds before the
|
||||
/// `root`.
|
||||
fn find_slots_to_clean(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
root: Slot,
|
||||
@@ -169,6 +182,30 @@ impl LedgerCleanupService {
|
||||
Ok(new_root_receiver.try_iter().last().unwrap_or(root))
|
||||
}
|
||||
|
||||
/// Checks for new roots and initiates a cleanup if the last cleanup was at
|
||||
/// least `purge_interval` slots ago. A cleanup will no-op if the ledger
|
||||
/// already has fewer than `max_ledger_shreds`; otherwise, the cleanup will
|
||||
/// purge enough slots to get the ledger size below `max_ledger_shreds`.
|
||||
///
|
||||
/// [`new_root_receiver`]: signal receiver which contains the information
|
||||
/// about what `Slot` is the current root.
|
||||
/// [`max_ledger_shreds`]: the number of shreds to keep since the new root.
|
||||
/// [`last_purge_slot`]: an both an input and output parameter indicating
|
||||
/// the id of the last purged slot. As an input parameter, it works
|
||||
/// together with `purge_interval` on whether it is too early to perform
|
||||
/// ledger cleanup. As an output parameter, it will be updated if this
|
||||
/// function actually performs the ledger cleanup.
|
||||
/// [`purge_interval`]: the minimum slot interval between two ledger
|
||||
/// cleanup. When the root derived from `new_root_receiver` minus
|
||||
/// `last_purge_slot` is fewer than `purge_interval`, the function will
|
||||
/// simply return `Ok` without actually running the ledger cleanup.
|
||||
/// In this case, `purge_interval` will remain unchanged.
|
||||
/// [`last_compact_slot`]: an output value which indicates the most recent
|
||||
/// slot which has been cleaned up after this call. If this parameter is
|
||||
/// updated after this function call, it means the ledger cleanup has
|
||||
/// been performed.
|
||||
///
|
||||
/// Also see `blockstore::purge_slot`.
|
||||
pub fn cleanup_ledger(
|
||||
new_root_receiver: &Receiver<Slot>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
|
@@ -28,10 +28,11 @@ pub mod fork_choice;
|
||||
pub mod gen_keys;
|
||||
pub mod heaviest_subtree_fork_choice;
|
||||
pub mod latest_validator_votes_for_frozen_banks;
|
||||
pub mod leader_slot_banking_stage_metrics;
|
||||
pub mod leader_slot_banking_stage_timing_metrics;
|
||||
pub mod ledger_cleanup_service;
|
||||
pub mod optimistic_confirmation_verifier;
|
||||
pub mod outstanding_requests;
|
||||
pub mod packet_deduper;
|
||||
pub mod packet_hasher;
|
||||
pub mod progress_map;
|
||||
pub mod qos_service;
|
||||
@@ -55,6 +56,7 @@ pub mod sigverify_stage;
|
||||
pub mod snapshot_packager_service;
|
||||
pub mod stats_reporter_service;
|
||||
pub mod system_monitor_service;
|
||||
mod tower1_7_14;
|
||||
pub mod tower_storage;
|
||||
pub mod tpu;
|
||||
pub mod tree_diff;
|
||||
|
@@ -1,63 +0,0 @@
|
||||
use {
|
||||
crate::{banking_stage::BankingStageStats, packet_hasher::PacketHasher},
|
||||
lru::LruCache,
|
||||
solana_measure::measure::Measure,
|
||||
solana_perf::packet::PacketBatch,
|
||||
std::{
|
||||
ops::DerefMut,
|
||||
sync::{atomic::Ordering, Arc, Mutex},
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_LRU_SIZE: usize = 200_000;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketDeduper(Arc<Mutex<(LruCache<u64, ()>, PacketHasher)>>);
|
||||
|
||||
impl Default for PacketDeduper {
|
||||
fn default() -> Self {
|
||||
Self(Arc::new(Mutex::new((
|
||||
LruCache::new(DEFAULT_LRU_SIZE),
|
||||
PacketHasher::default(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketDeduper {
|
||||
pub fn dedupe_packets(
|
||||
&self,
|
||||
packet_batch: &PacketBatch,
|
||||
packet_indexes: &mut Vec<usize>,
|
||||
banking_stage_stats: &BankingStageStats,
|
||||
) {
|
||||
let original_packets_count = packet_indexes.len();
|
||||
let mut packet_duplicate_check_time = Measure::start("packet_duplicate_check");
|
||||
let mut duplicates = self.0.lock().unwrap();
|
||||
let (cache, hasher) = duplicates.deref_mut();
|
||||
packet_indexes.retain(|i| {
|
||||
let packet_hash = hasher.hash_packet(&packet_batch.packets[*i]);
|
||||
match cache.get_mut(&packet_hash) {
|
||||
Some(_hash) => false,
|
||||
None => {
|
||||
cache.put(packet_hash, ());
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
packet_duplicate_check_time.stop();
|
||||
banking_stage_stats
|
||||
.packet_duplicate_check_elapsed
|
||||
.fetch_add(packet_duplicate_check_time.as_us(), Ordering::Relaxed);
|
||||
banking_stage_stats
|
||||
.dropped_duplicated_packets_count
|
||||
.fetch_add(
|
||||
original_packets_count.saturating_sub(packet_indexes.len()),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
let mut duplicates = self.0.lock().unwrap();
|
||||
duplicates.0.clear();
|
||||
}
|
||||
}
|
@@ -6,10 +6,12 @@ use {
|
||||
replay_stage::SUPERMINORITY_THRESHOLD,
|
||||
},
|
||||
solana_ledger::blockstore_processor::{ConfirmationProgress, ConfirmationTiming},
|
||||
solana_program_runtime::timings::ExecuteTimingType,
|
||||
solana_runtime::{bank::Bank, bank_forks::BankForks, vote_account::VoteAccount},
|
||||
solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey},
|
||||
std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
ops::Index,
|
||||
sync::{Arc, RwLock},
|
||||
time::Instant,
|
||||
},
|
||||
@@ -62,23 +64,60 @@ impl ReplaySlotStats {
|
||||
),
|
||||
("total_entries", num_entries as i64, i64),
|
||||
("total_shreds", num_shreds as i64, i64),
|
||||
("check_us", self.execute_timings.check_us, i64),
|
||||
("load_us", self.execute_timings.load_us, i64),
|
||||
("execute_us", self.execute_timings.execute_us, i64),
|
||||
("store_us", self.execute_timings.store_us, i64),
|
||||
(
|
||||
"check_us",
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::CheckUs),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"load_us",
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::LoadUs),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"execute_us",
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::ExecuteUs),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"store_us",
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::StoreUs),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"update_stakes_cache_us",
|
||||
self.execute_timings.update_stakes_cache_us,
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::UpdateStakesCacheUs),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"total_batches_len",
|
||||
self.execute_timings.total_batches_len,
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::TotalBatchesLen),
|
||||
i64
|
||||
),
|
||||
(
|
||||
"num_execute_batches",
|
||||
self.execute_timings.num_execute_batches,
|
||||
*self
|
||||
.execute_timings
|
||||
.metrics
|
||||
.index(ExecuteTimingType::NumExecuteBatches),
|
||||
i64
|
||||
),
|
||||
(
|
||||
|
@@ -117,22 +117,25 @@ impl QosService {
|
||||
txs_costs
|
||||
}
|
||||
|
||||
// Given a list of transactions and their costs, this function returns a corresponding
|
||||
// list of Results that indicate if a transaction is selected to be included in the current block,
|
||||
/// Given a list of transactions and their costs, this function returns a corresponding
|
||||
/// list of Results that indicate if a transaction is selected to be included in the current block,
|
||||
/// and a count of the number of transactions that would fit in the block
|
||||
pub fn select_transactions_per_cost<'a>(
|
||||
&self,
|
||||
transactions: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
transactions_costs: impl Iterator<Item = &'a TransactionCost>,
|
||||
bank: &Arc<Bank>,
|
||||
) -> Vec<transaction::Result<()>> {
|
||||
) -> (Vec<transaction::Result<()>>, usize) {
|
||||
let mut cost_tracking_time = Measure::start("cost_tracking_time");
|
||||
let mut cost_tracker = bank.write_cost_tracker().unwrap();
|
||||
let mut num_included = 0;
|
||||
let select_results = transactions
|
||||
.zip(transactions_costs)
|
||||
.map(|(tx, cost)| match cost_tracker.try_add(tx, cost) {
|
||||
Ok(current_block_cost) => {
|
||||
debug!("slot {:?}, transaction {:?}, cost {:?}, fit into current block, current block cost {}", bank.slot(), tx, cost, current_block_cost);
|
||||
self.metrics.selected_txs_count.fetch_add(1, Ordering::Relaxed);
|
||||
num_included += 1;
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
@@ -162,7 +165,7 @@ impl QosService {
|
||||
self.metrics
|
||||
.cost_tracking_time
|
||||
.fetch_add(cost_tracking_time.as_us(), Ordering::Relaxed);
|
||||
select_results
|
||||
(select_results, num_included)
|
||||
}
|
||||
|
||||
// metrics are reported by bank slot
|
||||
@@ -172,37 +175,6 @@ impl QosService {
|
||||
.unwrap_or_else(|err| warn!("qos service report metrics failed: {:?}", err));
|
||||
}
|
||||
|
||||
// metrics accumulating apis
|
||||
pub fn accumulate_tpu_ingested_packets_count(&self, count: u64) {
|
||||
self.metrics
|
||||
.tpu_ingested_packets_count
|
||||
.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn accumulate_tpu_buffered_packets_count(&self, count: u64) {
|
||||
self.metrics
|
||||
.tpu_buffered_packets_count
|
||||
.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn accumulated_verified_txs_count(&self, count: u64) {
|
||||
self.metrics
|
||||
.verified_txs_count
|
||||
.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn accumulated_processed_txs_count(&self, count: u64) {
|
||||
self.metrics
|
||||
.processed_txs_count
|
||||
.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn accumulated_retryable_txs_count(&self, count: u64) {
|
||||
self.metrics
|
||||
.retryable_txs_count
|
||||
.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn accumulate_estimated_transaction_costs(
|
||||
&self,
|
||||
cost_details: &BatchedTransactionCostDetails,
|
||||
@@ -261,24 +233,6 @@ struct QosServiceMetrics {
|
||||
// aggregate metrics per slot
|
||||
slot: AtomicU64,
|
||||
|
||||
// accumulated number of live packets TPU received from verified receiver for processing.
|
||||
tpu_ingested_packets_count: AtomicU64,
|
||||
|
||||
// accumulated number of live packets TPU put into buffer due to no active bank.
|
||||
tpu_buffered_packets_count: AtomicU64,
|
||||
|
||||
// accumulated number of verified txs, which excludes unsanitized transactions and
|
||||
// non-vote transactions when in vote-only mode from ingested packets
|
||||
verified_txs_count: AtomicU64,
|
||||
|
||||
// accumulated number of transactions been processed, includes those landed and those to be
|
||||
// returned (due to AccountInUse, and other QoS related reasons)
|
||||
processed_txs_count: AtomicU64,
|
||||
|
||||
// accumulated number of transactions buffered for retry, often due to AccountInUse and QoS
|
||||
// reasons, includes retried_txs_per_block_limit_count and retried_txs_per_account_limit_count
|
||||
retryable_txs_count: AtomicU64,
|
||||
|
||||
// accumulated time in micro-sec spent in computing transaction cost. It is the main performance
|
||||
// overhead introduced by cost_model
|
||||
compute_cost_time: AtomicU64,
|
||||
@@ -340,31 +294,6 @@ impl QosServiceMetrics {
|
||||
"qos-service-stats",
|
||||
("id", self.id as i64, i64),
|
||||
("bank_slot", bank_slot as i64, i64),
|
||||
(
|
||||
"tpu_ingested_packets_count",
|
||||
self.tpu_ingested_packets_count.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"tpu_buffered_packets_count",
|
||||
self.tpu_buffered_packets_count.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"verified_txs_count",
|
||||
self.verified_txs_count.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"processed_txs_count",
|
||||
self.processed_txs_count.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"retryable_txs_count",
|
||||
self.retryable_txs_count.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"compute_cost_time",
|
||||
self.compute_cost_time.swap(0, Ordering::Relaxed) as i64,
|
||||
@@ -542,7 +471,9 @@ mod tests {
|
||||
bank.write_cost_tracker()
|
||||
.unwrap()
|
||||
.set_limits(cost_limit, cost_limit, cost_limit);
|
||||
let results = qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||
let (results, num_selected) =
|
||||
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||
assert_eq!(num_selected, 2);
|
||||
|
||||
// verify that first transfer tx and first vote are allowed
|
||||
assert_eq!(results.len(), txs.len());
|
||||
|
@@ -642,7 +642,7 @@ impl RepairWeight {
|
||||
}
|
||||
|
||||
// Heavier, smaller slots come first
|
||||
fn sort_by_stake_weight_slot(slot_stake_voted: &mut Vec<(Slot, u64)>) {
|
||||
fn sort_by_stake_weight_slot(slot_stake_voted: &mut [(Slot, u64)]) {
|
||||
slot_stake_voted.sort_by(|(slot, stake_voted), (slot_, stake_voted_)| {
|
||||
if stake_voted == stake_voted_ {
|
||||
slot.cmp(slot_)
|
||||
|
@@ -21,7 +21,7 @@ use {
|
||||
progress_map::{ForkProgress, ProgressMap, PropagatedStats},
|
||||
repair_service::DuplicateSlotsResetReceiver,
|
||||
rewards_recorder_service::RewardsRecorderSender,
|
||||
tower_storage::{SavedTower, TowerStorage},
|
||||
tower_storage::{SavedTower, SavedTowerVersions, TowerStorage},
|
||||
unfrozen_gossip_verified_vote_hashes::UnfrozenGossipVerifiedVoteHashes,
|
||||
voting_service::VoteOp,
|
||||
window_service::DuplicateSlotReceiver,
|
||||
@@ -40,7 +40,7 @@ use {
|
||||
},
|
||||
solana_measure::measure::Measure,
|
||||
solana_metrics::inc_new_counter_info,
|
||||
solana_poh::poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
|
||||
solana_poh::poh_recorder::{PohLeaderStatus, PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
|
||||
solana_program_runtime::timings::ExecuteTimings,
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::{BankNotification, BankNotificationSender},
|
||||
@@ -64,7 +64,7 @@ use {
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_vote_program::vote_state::Vote,
|
||||
solana_vote_program::vote_state::VoteTransaction,
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
result,
|
||||
@@ -139,6 +139,9 @@ pub struct ReplayStageConfig {
|
||||
pub wait_for_vote_to_start_leader: bool,
|
||||
pub ancestor_hashes_replay_update_sender: AncestorHashesReplayUpdateSender,
|
||||
pub tower_storage: Arc<dyn TowerStorage>,
|
||||
// Stops voting until this slot has been reached. Should be used to avoid
|
||||
// duplicate voting which can lead to slashing.
|
||||
pub wait_to_vote_slot: Option<Slot>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -382,6 +385,7 @@ impl ReplayStage {
|
||||
wait_for_vote_to_start_leader,
|
||||
ancestor_hashes_replay_update_sender,
|
||||
tower_storage,
|
||||
wait_to_vote_slot,
|
||||
} = config;
|
||||
|
||||
trace!("replay stage");
|
||||
@@ -425,6 +429,7 @@ impl ReplayStage {
|
||||
last_refresh_time: Instant::now(),
|
||||
last_print_time: Instant::now(),
|
||||
};
|
||||
|
||||
loop {
|
||||
// Stop getting entries if we get exit signal
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
@@ -561,7 +566,7 @@ impl ReplayStage {
|
||||
&vote_account,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&vote_tracker,
|
||||
&cluster_slots,
|
||||
@@ -603,6 +608,7 @@ impl ReplayStage {
|
||||
has_new_vote_been_rooted, &mut
|
||||
last_vote_refresh_time,
|
||||
&voting_sender,
|
||||
wait_to_vote_slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -686,6 +692,7 @@ impl ReplayStage {
|
||||
&voting_sender,
|
||||
&mut epoch_slots_frozen_slots,
|
||||
&drop_bank_sender,
|
||||
wait_to_vote_slot,
|
||||
);
|
||||
};
|
||||
voting_time.stop();
|
||||
@@ -1479,13 +1486,17 @@ impl ReplayStage {
|
||||
|
||||
assert!(!poh_recorder.lock().unwrap().has_bank());
|
||||
|
||||
let (reached_leader_slot, _grace_ticks, poh_slot, parent_slot) =
|
||||
poh_recorder.lock().unwrap().reached_leader_slot();
|
||||
let (poh_slot, parent_slot) = match poh_recorder.lock().unwrap().reached_leader_slot() {
|
||||
PohLeaderStatus::Reached {
|
||||
poh_slot,
|
||||
parent_slot,
|
||||
} => (poh_slot, parent_slot),
|
||||
PohLeaderStatus::NotReached => {
|
||||
trace!("{} poh_recorder hasn't reached_leader_slot", my_pubkey);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !reached_leader_slot {
|
||||
trace!("{} poh_recorder hasn't reached_leader_slot", my_pubkey);
|
||||
return;
|
||||
}
|
||||
trace!("{} reached_leader_slot", my_pubkey);
|
||||
|
||||
let parent = bank_forks
|
||||
@@ -1602,7 +1613,10 @@ impl ReplayStage {
|
||||
verify_recyclers: &VerifyRecyclers,
|
||||
) -> result::Result<usize, BlockstoreProcessorError> {
|
||||
let tx_count_before = bank_progress.replay_progress.num_txs;
|
||||
let confirm_result = blockstore_processor::confirm_slot(
|
||||
// All errors must lead to marking the slot as dead, otherwise,
|
||||
// the `check_slot_agrees_with_cluster()` called by `replay_active_banks()`
|
||||
// will break!
|
||||
blockstore_processor::confirm_slot(
|
||||
blockstore,
|
||||
bank,
|
||||
&mut bank_progress.replay_stats,
|
||||
@@ -1614,16 +1628,9 @@ impl ReplayStage {
|
||||
None,
|
||||
verify_recyclers,
|
||||
false,
|
||||
);
|
||||
)?;
|
||||
let tx_count_after = bank_progress.replay_progress.num_txs;
|
||||
let tx_count = tx_count_after - tx_count_before;
|
||||
confirm_result.map_err(|err| {
|
||||
// All errors must lead to marking the slot as dead, otherwise,
|
||||
// the `check_slot_agrees_with_cluster()` called by `replay_active_banks()`
|
||||
// will break!
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(tx_count)
|
||||
}
|
||||
|
||||
@@ -1723,6 +1730,7 @@ impl ReplayStage {
|
||||
voting_sender: &Sender<VoteOp>,
|
||||
epoch_slots_frozen_slots: &mut EpochSlotsFrozenSlots,
|
||||
bank_drop_sender: &Sender<Vec<Arc<Bank>>>,
|
||||
wait_to_vote_slot: Option<Slot>,
|
||||
) {
|
||||
if bank.is_empty() {
|
||||
inc_new_counter_info!("replay_stage-voted_empty_bank", 1);
|
||||
@@ -1811,6 +1819,7 @@ impl ReplayStage {
|
||||
*has_new_vote_been_rooted,
|
||||
replay_timing,
|
||||
voting_sender,
|
||||
wait_to_vote_slot,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1819,14 +1828,20 @@ impl ReplayStage {
|
||||
bank: &Bank,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
authorized_voter_keypairs: &[Arc<Keypair>],
|
||||
vote: Vote,
|
||||
vote: VoteTransaction,
|
||||
switch_fork_decision: &SwitchForkDecision,
|
||||
vote_signatures: &mut Vec<Signature>,
|
||||
has_new_vote_been_rooted: bool,
|
||||
wait_to_vote_slot: Option<Slot>,
|
||||
) -> Option<Transaction> {
|
||||
if authorized_voter_keypairs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Some(slot) = wait_to_vote_slot {
|
||||
if bank.slot() < slot {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let vote_account = match bank.get_vote_account(vote_account_pubkey) {
|
||||
None => {
|
||||
warn!(
|
||||
@@ -1921,6 +1936,7 @@ impl ReplayStage {
|
||||
has_new_vote_been_rooted: bool,
|
||||
last_vote_refresh_time: &mut LastVoteRefreshTime,
|
||||
voting_sender: &Sender<VoteOp>,
|
||||
wait_to_vote_slot: Option<Slot>,
|
||||
) {
|
||||
let last_voted_slot = tower.last_voted_slot();
|
||||
if last_voted_slot.is_none() {
|
||||
@@ -1963,6 +1979,7 @@ impl ReplayStage {
|
||||
&SwitchForkDecision::SameFork,
|
||||
vote_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
wait_to_vote_slot,
|
||||
);
|
||||
|
||||
if let Some(vote_tx) = vote_tx {
|
||||
@@ -2000,6 +2017,7 @@ impl ReplayStage {
|
||||
has_new_vote_been_rooted: bool,
|
||||
replay_timing: &mut ReplayTiming,
|
||||
voting_sender: &Sender<VoteOp>,
|
||||
wait_to_vote_slot: Option<Slot>,
|
||||
) {
|
||||
let mut generate_time = Measure::start("generate_vote");
|
||||
let vote_tx = Self::generate_vote_tx(
|
||||
@@ -2011,6 +2029,7 @@ impl ReplayStage {
|
||||
switch_fork_decision,
|
||||
vote_signatures,
|
||||
has_new_vote_been_rooted,
|
||||
wait_to_vote_slot,
|
||||
);
|
||||
generate_time.stop();
|
||||
replay_timing.generate_vote_us += generate_time.as_us();
|
||||
@@ -2022,7 +2041,7 @@ impl ReplayStage {
|
||||
.send(VoteOp::PushVote {
|
||||
tx: vote_tx,
|
||||
tower_slots,
|
||||
saved_tower,
|
||||
saved_tower: SavedTowerVersions::from(saved_tower),
|
||||
})
|
||||
.unwrap_or_else(|err| warn!("Error: {:?}", err));
|
||||
}
|
||||
@@ -2295,7 +2314,7 @@ impl ReplayStage {
|
||||
my_vote_pubkey: &Pubkey,
|
||||
ancestors: &HashMap<u64, HashSet<u64>>,
|
||||
frozen_banks: &mut Vec<Arc<Bank>>,
|
||||
tower: &Tower,
|
||||
tower: &mut Tower,
|
||||
progress: &mut ProgressMap,
|
||||
vote_tracker: &VoteTracker,
|
||||
cluster_slots: &ClusterSlots,
|
||||
@@ -2316,6 +2335,70 @@ impl ReplayStage {
|
||||
.expect("All frozen banks must exist in the Progress map")
|
||||
.computed;
|
||||
if !is_computed {
|
||||
// Check if our tower is behind, if so (and the feature migration flag is in use)
|
||||
// overwrite with the newer bank.
|
||||
if let (true, Some((_, vote_account))) = (
|
||||
Tower::is_direct_vote_state_update_enabled(bank),
|
||||
bank.get_vote_account(my_vote_pubkey),
|
||||
) {
|
||||
if let Some(mut bank_vote_state) =
|
||||
vote_account.vote_state().as_ref().ok().cloned()
|
||||
{
|
||||
if bank_vote_state.last_voted_slot()
|
||||
> tower.vote_state.last_voted_slot()
|
||||
{
|
||||
info!(
|
||||
"Frozen bank vote state slot {:?}
|
||||
is newer than our local vote state slot {:?},
|
||||
adopting the bank vote state as our own.
|
||||
Bank votes: {:?}, root: {:?},
|
||||
Local votes: {:?}, root: {:?}",
|
||||
bank_vote_state.last_voted_slot(),
|
||||
tower.vote_state.last_voted_slot(),
|
||||
bank_vote_state.votes,
|
||||
bank_vote_state.root_slot,
|
||||
tower.vote_state.votes,
|
||||
tower.vote_state.root_slot
|
||||
);
|
||||
|
||||
if let Some(local_root) = tower.vote_state.root_slot {
|
||||
if bank_vote_state
|
||||
.root_slot
|
||||
.map(|bank_root| local_root > bank_root)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// If the local root is larger than this on chain vote state
|
||||
// root (possible due to supermajority roots being set on
|
||||
// startup), then we need to adjust the tower
|
||||
bank_vote_state.root_slot = Some(local_root);
|
||||
bank_vote_state
|
||||
.votes
|
||||
.retain(|lockout| lockout.slot > local_root);
|
||||
info!(
|
||||
"Local root is larger than on chain root,
|
||||
overwrote bank root {:?} and updated votes {:?}",
|
||||
bank_vote_state.root_slot, bank_vote_state.votes
|
||||
);
|
||||
|
||||
if let Some(first_vote) = bank_vote_state.votes.front() {
|
||||
assert!(ancestors
|
||||
.get(&first_vote.slot)
|
||||
.expect(
|
||||
"Ancestors map must contain an
|
||||
entry for all slots on this fork
|
||||
greater than `local_root` and less
|
||||
than `bank_slot`"
|
||||
)
|
||||
.contains(&local_root));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tower.vote_state.root_slot = bank_vote_state.root_slot;
|
||||
tower.vote_state.votes = bank_vote_state.votes;
|
||||
}
|
||||
}
|
||||
}
|
||||
let computed_bank_state = Tower::collect_vote_lockouts(
|
||||
my_vote_pubkey,
|
||||
bank_slot,
|
||||
@@ -3915,36 +3998,35 @@ pub mod tests {
|
||||
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
|
||||
let slot = bank1.slot();
|
||||
|
||||
let signatures = create_test_transactions_and_populate_blockstore(
|
||||
let mut test_signatures_iter = create_test_transactions_and_populate_blockstore(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
bank0.slot(),
|
||||
bank1,
|
||||
blockstore.clone(),
|
||||
Arc::new(AtomicU64::default()),
|
||||
);
|
||||
)
|
||||
.into_iter();
|
||||
|
||||
let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for VersionedTransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if transaction.signatures[0] == signatures[0] {
|
||||
let meta = meta.unwrap();
|
||||
assert_eq!(meta.status, Ok(()));
|
||||
} else if transaction.signatures[0] == signatures[1] {
|
||||
let meta = meta.unwrap();
|
||||
assert_eq!(
|
||||
meta.status,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(1)
|
||||
))
|
||||
);
|
||||
} else {
|
||||
assert_eq!(meta, None);
|
||||
}
|
||||
}
|
||||
let actual_tx_results: Vec<_> = confirmed_block
|
||||
.transactions
|
||||
.into_iter()
|
||||
.map(|VersionedTransactionWithStatusMeta { transaction, meta }| {
|
||||
(transaction.signatures[0], meta.status)
|
||||
})
|
||||
.collect();
|
||||
let expected_tx_results = vec![
|
||||
(test_signatures_iter.next().unwrap(), Ok(())),
|
||||
(
|
||||
test_signatures_iter.next().unwrap(),
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(1),
|
||||
)),
|
||||
),
|
||||
];
|
||||
assert_eq!(actual_tx_results, expected_tx_results);
|
||||
assert!(test_signatures_iter.next().is_none());
|
||||
}
|
||||
Blockstore::destroy(&ledger_path).unwrap();
|
||||
}
|
||||
@@ -3986,12 +4068,12 @@ pub mod tests {
|
||||
.values()
|
||||
.cloned()
|
||||
.collect();
|
||||
let tower = Tower::new_for_tests(0, 0.67);
|
||||
let mut tower = Tower::new_for_tests(0, 0.67);
|
||||
let newly_computed = ReplayStage::compute_bank_stats(
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -4035,7 +4117,7 @@ pub mod tests {
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -4071,7 +4153,7 @@ pub mod tests {
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -4087,7 +4169,7 @@ pub mod tests {
|
||||
fn test_same_weight_select_lower_slot() {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(1);
|
||||
let tower = Tower::default();
|
||||
let mut tower = Tower::default();
|
||||
|
||||
// Create the tree of banks in a BankForks object
|
||||
let forks = tr(0) / (tr(1)) / (tr(2));
|
||||
@@ -4110,7 +4192,7 @@ pub mod tests {
|
||||
&my_vote_pubkey,
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut vote_simulator.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -4166,7 +4248,7 @@ pub mod tests {
|
||||
|
||||
// Set the voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let votes = vec![0, 2];
|
||||
let votes = vec![2];
|
||||
cluster_votes.insert(my_node_pubkey, votes.clone());
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes, true);
|
||||
|
||||
@@ -4191,7 +4273,7 @@ pub mod tests {
|
||||
&my_vote_pubkey,
|
||||
&vote_simulator.bank_forks.read().unwrap().ancestors(),
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut vote_simulator.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -4327,9 +4409,9 @@ pub mod tests {
|
||||
for i in 0..std::cmp::max(new_vote_pubkeys.len(), new_node_pubkeys.len()) {
|
||||
propagated_stats.is_propagated = false;
|
||||
let len = std::cmp::min(i, new_vote_pubkeys.len());
|
||||
let mut voted_pubkeys = new_vote_pubkeys[..len].iter().copied().collect();
|
||||
let mut voted_pubkeys = new_vote_pubkeys[..len].to_vec();
|
||||
let len = std::cmp::min(i, new_node_pubkeys.len());
|
||||
let mut node_pubkeys = new_node_pubkeys[..len].iter().copied().collect();
|
||||
let mut node_pubkeys = new_node_pubkeys[..len].to_vec();
|
||||
let did_newly_reach_threshold =
|
||||
ReplayStage::update_slot_propagated_threshold_from_votes(
|
||||
&mut voted_pubkeys,
|
||||
@@ -4474,7 +4556,7 @@ pub mod tests {
|
||||
// runs in `update_propagation_status`
|
||||
assert!(!progress_map.get_leader_propagation_slot_must_exist(10).0);
|
||||
|
||||
let vote_tracker = VoteTracker::new(&bank_forks.root_bank());
|
||||
let vote_tracker = VoteTracker::default();
|
||||
vote_tracker.insert_vote(10, vote_pubkey);
|
||||
ReplayStage::update_propagation_status(
|
||||
&mut progress_map,
|
||||
@@ -4559,7 +4641,7 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
let vote_tracker = VoteTracker::new(&bank_forks.root_bank());
|
||||
let vote_tracker = VoteTracker::default();
|
||||
for vote_pubkey in &vote_pubkeys {
|
||||
// Insert a vote for the last bank for each voter
|
||||
vote_tracker.insert_vote(10, *vote_pubkey);
|
||||
@@ -4646,7 +4728,7 @@ pub mod tests {
|
||||
progress_map.insert(i, fork_progress);
|
||||
}
|
||||
|
||||
let vote_tracker = VoteTracker::new(&bank_forks.root_bank());
|
||||
let vote_tracker = VoteTracker::default();
|
||||
// Insert a new vote
|
||||
vote_tracker.insert_vote(10, vote_pubkeys[2]);
|
||||
|
||||
@@ -5106,12 +5188,12 @@ pub mod tests {
|
||||
);
|
||||
|
||||
// Update propagation status
|
||||
let tower = Tower::new_for_tests(0, 0.67);
|
||||
let mut tower = Tower::new_for_tests(0, 0.67);
|
||||
ReplayStage::compute_bank_stats(
|
||||
&validator_node_to_vote_keys[&my_pubkey],
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&vote_tracker,
|
||||
&ClusterSlots::default(),
|
||||
@@ -5494,7 +5576,7 @@ pub mod tests {
|
||||
&Pubkey::new_unique(),
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -5621,7 +5703,7 @@ pub mod tests {
|
||||
&Pubkey::new_unique(),
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
&tower,
|
||||
&mut tower,
|
||||
&mut progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
@@ -5797,6 +5879,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut ReplayTiming::default(),
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
let vote_info = voting_receiver
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
@@ -5836,6 +5919,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
|
||||
// No new votes have been submitted to gossip
|
||||
@@ -5861,6 +5945,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut ReplayTiming::default(),
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
let vote_info = voting_receiver
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
@@ -5892,6 +5977,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
|
||||
// No new votes have been submitted to gossip
|
||||
@@ -5929,6 +6015,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
let vote_info = voting_receiver
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
@@ -5996,6 +6083,7 @@ pub mod tests {
|
||||
has_new_vote_been_rooted,
|
||||
&mut last_vote_refresh_time,
|
||||
&voting_sender,
|
||||
None,
|
||||
);
|
||||
|
||||
let votes = cluster_info.get_votes(&mut cursor);
|
||||
|
@@ -40,12 +40,17 @@ impl Default for TransactionSigVerifier {
|
||||
}
|
||||
|
||||
impl SigVerifier for TransactionSigVerifier {
|
||||
fn verify_batches(&self, mut batches: Vec<PacketBatch>) -> Vec<PacketBatch> {
|
||||
fn verify_batches(
|
||||
&self,
|
||||
mut batches: Vec<PacketBatch>,
|
||||
valid_packets: usize,
|
||||
) -> Vec<PacketBatch> {
|
||||
sigverify::ed25519_verify(
|
||||
&mut batches,
|
||||
&self.recycler,
|
||||
&self.recycler_out,
|
||||
self.reject_non_vote,
|
||||
valid_packets,
|
||||
);
|
||||
batches
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user