Compare commits

..

8 Commits

1058 changed files with 65887 additions and 144538 deletions

42
.appveyor.yml Normal file
View File

@ -0,0 +1,42 @@
version: '{build}'
branches:
only:
- master
- /^v[0-9.]+\.[0-9.]+/
cache:
- '%USERPROFILE%\.cargo'
- '%APPVEYOR_BUILD_FOLDER%\target'
clone_folder: d:\projects\solana
build_script:
- bash ci/publish-tarball.sh
notifications:
- provider: Slack
incoming_webhook:
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
channel: ci-status
on_build_success: false
on_build_failure: true
on_build_status_changed: true
deploy:
- provider: S3
access_key_id:
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
secret_access_key:
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
bucket: release.solana.com
region: us-west-1
set_public: true
- provider: GitHub
auth_token:
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
draft: false
prerelease: false
on:
appveyor_repo_tag: true

View File

@ -1,12 +1,15 @@
{
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
"environment": {
"CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]",
"CRATES_IO_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:GGRTYDjMXksevzR6kq4Jx+FaIQZz50RU:xkbwDxcgoCyU+aT2tiI9mymigrEl6YiOr3axe3aX70ELIBKbCdPGilXP/wixvKi94g2u]",
"GEOLOCATION_API_KEY": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:U2PZLi5MU3Ru/zK1SilianEeizcMvxml:AJKf2OAtDHmJh0KyXrBnNnistItZvVVP3cZ7ZLtrVupjmWN/PzmKwSsXeCNObWS+]",
"GITHUB_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:0NJNlpD/O19mvOakCGBYDhIDfySxWFSC:Dz4NXv9x6ncRQ1u9sVoWOcqmkg0sI09qmefghB0GXZgPcFGgn6T0mw7ynNnbUvjyH8dLruKHauk=]",
"INFLUX_DATABASE": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:SzwHIeOVpmbTcGQOGngoFgYumsLZJUGq:t7Rpk49njsWvoM+ztv5Uwuiz]",
"INFLUX_PASSWORD": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:/MUs+q7pdGrUjzwcq+6pgIFxur4hxdqu:am22z2E2dtmw1f1J1Mq5JLcUHZsrEjQAJ0pp21M4AZeJbNO6bVb44d9zSkHj7xdN6U+GNlCk+wU=]",
"INFLUX_USERNAME": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:XjghH20xGVWro9B+epGlJaJcW8Wze0Bi:ZIdOtXudTY5TqKseDU7gVvQXfmXV99Xh]"
"CODECOV_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:JnxhrIxh09AvqdJgrVSYmb7PxSrh19aE:07WzVExCHEd1lJ1m8QizRRthGri+WBNeZRKjjEvsy5eo4gv3HD7zVEm42tVTGkqITKkBNQ==]",
"CRATES_IO_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:d0jJqC32/axwzq/N7kMRmpxKhnRrhtpt:zvcPHwkOzGnjhNkAQSejwdy1Jkr9wR1qXFFCnfIjyt/XQYubzB1tLkoly/qdmeb5]",
"GEOLOCATION_API_KEY": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R4gfB6Ey4i50HyfLt4UZDLBqg3qHEUye:UfZCOgt8XI6Y2g+ivCRVoS1fjFycFs7/GSevvCqh1B50mG0+hzpEyzXQLuKG5OeI]",
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Egc2dMrHDU0NcZ71LwGv/V66shUhwYUE:04VoIb8CKy7KYhQ5W4cEW9SDKZltxWBL5Hob106lMBbUOD/yUvKYcG3Ep8JfTMwO3K8zowW5HpU/IdGoilX0XWLiJJ6t+p05WWK0TA16nOEtwrEG+UK8wm3sN+xCO20i4jDhpNpgg3FYFHT5rKTHW8+zaBTNUX/SFxkN67Lm+92IM28CXYE43SU1WV6H99hGFFVpTK5JVM3JuYU1ex/dHRE+xCzTr4MYUB/F+nGoNFW8HUDV/y0e1jxT9to3x0SmnytEEuk+5RUzFuEt9cKNFeNml3fOCi4qL+sfj/Y5pjH9xDiUxsvH/8NL35jbLP244aFHgWcp]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:NeOxSoWCvXB9AL4H6OK26l/7bmsKd/oz:Ijfoxtvk2CHlN1ZXHup3Gg/914kbbAkEGWJfvozA8UIe+aUzUObMyTrKkVOeNAH8Q8YH9tNzk7RRnrTcpnzeCCBLlWcVEeruMxHox3mPRzmSeDLxtbzCl9VePlRO3T7jg90K5hW+ZAkd5J/WJNzpAcmr93ts/of3MbvGHSujId/efCTzJEcP6JInnBb8Vrj7TlgKbzUlnqpq1+NjYPSXN3maKa9pKeo2JWxZlGBMoy6QWUUY5GbYEylw9smwh1LJcHZjlaZNMuOl4gNKtaSr38IXQkAXaRUJDPAmPras00YObKzXU8RkTrP4EoP/jx5LPR7f]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:7t+56twjW+jR7fpFNNeRFLPd7E4lbmyN:JuviDpkQrfVcNUGRGsa2e/UhvH6tTYyk1s4cHHE5xZH1NByL7Kpqx36VG/+o1AUGEeSQdsBnKgzYdMoFYbO8o50DoRPc86QIEVXCupD6J9avxLFtQgOWgJp+/mCdUVXlqXiFs/vQgS/L4psrcKdF6WHd77BeUr6ll8DjH+9m5FC9Rcai2pXno6VbPpunHQ0oUdYzhFR64+LiRacBaefQ9igZ+nSEWDLqbaZSyfm9viWkijoVFTq8gAgdXXEh7g0QdxVE5T6bPristJhT6jWBhWunPUCDNFFErWIsbRGctepl4pbCWqh2hNTw9btSgVfeY6uGCOsdy9E=]"
}
}

View File

@ -3,19 +3,16 @@
#
# Save target/ for the next CI build on this machine
#
if [[ -z $CARGO_TARGET_CACHE ]]; then
echo "+++ CARGO_TARGET_CACHE not defined" # pre-command should have defined it
else
(
set -x
mkdir -p "$CARGO_TARGET_CACHE"
set -x
rsync -a --delete --link-dest="$PWD" target "$CARGO_TARGET_CACHE"
du -hs "$CARGO_TARGET_CACHE"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
)
fi
(
set -x
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
mkdir -p "$d"
set -x
rsync -a --delete --link-dest="$PWD" target "$d"
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
)
#
# Add job_stats data point

View File

@ -11,24 +11,23 @@ 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"
(
set -x
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
MAX_CACHE_SIZE=18 # gigabytes
if [[ -d $CARGO_TARGET_CACHE ]]; then
du -hs "$CARGO_TARGET_CACHE"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
if [[ -d $d ]]; then
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
echo "--- $CARGO_TARGET_CACHE is too large, removing it"
rm -rf "$CARGO_TARGET_CACHE"
echo "--- $d is too large, removing it"
rm -rf "$d"
fi
else
echo "--- $CARGO_TARGET_CACHE not present"
echo "--- $d not present"
fi
mkdir -p "$CARGO_TARGET_CACHE"/target
rsync -a --delete --link-dest="$CARGO_TARGET_CACHE" "$CARGO_TARGET_CACHE"/target .
mkdir -p "$d"/target
rsync -a --delete --link-dest="$d" "$d"/target .
)

5
.gitbook.yaml Normal file
View File

@ -0,0 +1,5 @@
root: ./book/src
structure:
readme: introduction.md
summary: SUMMARY.md

11
.gitignore vendored
View File

@ -1,7 +1,6 @@
/docs/html/
/docs/src/tests.ok
/docs/src/cli/usage.md
/docs/src/.gitbook/assets/*.svg
/book/html/
/book/src/tests.ok
/book/src/.gitbook/assets/*.svg
/farf/
/solana-release/
/solana-release.tar.bz2
@ -23,7 +22,3 @@ log-*/
/.idea/
/solana.iml
/.vscode/
# fetch-spl.sh artifacts
/spl-genesis-args.sh
/spl_*.so

View File

@ -1,40 +1,9 @@
# Validate your changes with:
#
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate/
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
#
# https://doc.mergify.io/
pull_request_rules:
- name: automatic merge (squash) on CI success
conditions:
- status-success=buildkite/solana
#- status-success=Travis CI - Pull Request
- status-success=ci-gate
- label=automerge
- author≠@dont-squash-my-commits
actions:
merge:
method: squash
# Join the dont-squash-my-commits group if you won't like your commits squashed
- name: automatic merge (rebase) on CI success
conditions:
- status-success=buildkite/solana
#- status-success=Travis CI - Pull Request
- status-success=ci-gate
- label=automerge
- author=@dont-squash-my-commits
actions:
merge:
method: rebase
- name: remove automerge label on CI failure
conditions:
- label=automerge
- "#status-failure!=0"
actions:
label:
remove:
- automerge
comment:
message: automerge label removed due to a CI failure
- name: remove outdated reviews
conditions:
- base=master
@ -50,27 +19,27 @@ pull_request_rules:
label:
add:
- automerge
- name: v1.0 backport
- name: v0.21 backport
conditions:
- label=v1.0
- base=master
- label=v0.21
actions:
backport:
ignore_conflicts: true
branches:
- v1.0
- name: v1.1 backport
- v0.21
- name: v0.22 backport
conditions:
- label=v1.1
- base=master
- label=v0.22
actions:
backport:
ignore_conflicts: true
branches:
- v1.1
- name: v1.2 backport
- v0.22
- name: v0.23 backport
conditions:
- label=v1.2
- base=master
- label=v0.23
actions:
backport:
ignore_conflicts: true
branches:
- v1.2
- v0.23

View File

@ -1,3 +1,17 @@
os:
- osx
language: rust
rust:
- stable
install:
- source ci/rust-version.sh
script:
- source ci/env.sh
- ci/publish-tarball.sh
branches:
only:
- master
@ -8,66 +22,21 @@ notifications:
on_success: change
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
os: linux
dist: bionic
language: minimal
jobs:
include:
- &release-artifacts
if: type = push
name: "macOS release artifacts"
os: osx
language: rust
rust:
- stable
install:
- source ci/rust-version.sh
script:
- source ci/env.sh
- ci/publish-tarball.sh
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
bucket: release.solana.com
region: us-west-1
skip_cleanup: true
acl: public_read
local_dir: travis-s3-upload
on:
all_branches: true
- provider: releases
token: $GITHUB_TOKEN
skip_cleanup: true
file_glob: true
file: travis-release-upload/*
on:
tags: true
- <<: *release-artifacts
name: "Windows release artifacts"
os: windows
# docs pull request or commit
- name: "docs"
if: type IN (push, pull_request) OR tag IS present
language: node_js
node_js:
- "node"
services:
- docker
cache:
directories:
- ~/.npm
before_install:
- source ci/env.sh
- .travis/channel_restriction.sh edge beta || travis_terminate 0
- .travis/affects.sh docs/ .travis || travis_terminate 0
- cd docs/
- source .travis/before_install.sh
script:
- source .travis/script.sh
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
bucket: release.solana.com
region: us-west-1
skip_cleanup: true
acl: public_read
local_dir: travis-s3-upload
on:
all_branches: true
- provider: releases
api_key: $GITHUB_TOKEN
skip_cleanup: true
file_glob: true
file: travis-release-upload/*
on:
tags: true

View File

@ -1,25 +0,0 @@
#!/usr/bin/env bash
#
# Check if files in the commit range match one or more prefixes
#
# Always run the job if we are on a tagged release
if [[ -n "$TRAVIS_TAG" ]]; then
exit 0
fi
(
set -x
git diff --name-only "$TRAVIS_COMMIT_RANGE"
)
for file in $(git diff --name-only "$TRAVIS_COMMIT_RANGE"); do
for prefix in "$@"; do
if [[ $file =~ ^"$prefix" ]]; then
exit 0
fi
done
done
echo "No modifications to $*"
exit 1

View File

@ -1,17 +0,0 @@
#!/usr/bin/env bash
#
# Only proceed if we are on one of the channels passed in when calling this file
#
set -ex
eval "$(ci/channel-info.sh)"
for acceptable_channel in "$@"; do
if [[ "$CHANNEL" == "$acceptable_channel" ]]; then
exit 0
fi
done
echo "Not running from one of the following channels: $*"
exit 1

View File

@ -224,20 +224,21 @@ Inventing new terms is allowed, but should only be done when the term is widely
used and understood. Avoid introducing new 3-letter terms, which can be
confused with 3-letter acronyms.
[Terms currently in use](docs/src/terminology.md)
[Terms currently in use](book/src/terminology.md)
## Design Proposals
Solana's architecture is described by docs generated from markdown files in
the `docs/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to include it in the
[Accepted Design Proposals](https://docs.solana.com/proposals)
section of the Solana docs. Here's the full process:
Solana's architecture is described by a book generated from markdown files in
the `book/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to at least propose a change the content
under the [Accepted Design
Proposals](https://docs.solana.com/book/v/master/proposals) chapter. Here's
the full process:
1. Propose a design by creating a PR that adds a markdown document to the
`docs/src/proposals` directory and references it from the [table of
contents](docs/src/SUMMARY.md). Add any relevant *maintainers* to the PR
directory `book/src/` and references it from the [table of
contents](book/src/SUMMARY.md). Add any relevant *maintainers* to the PR
review.
2. The PR being merged indicates your proposed change was accepted and that the
maintainers support your plan of attack.

7662
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,10 @@ members = [
"bench-exchange",
"bench-streamer",
"bench-tps",
"accounts-bench",
"banking-bench",
"cli-config",
"cli-output",
"chacha-sys",
"client",
"core",
"dos",
"download-utils",
"faucet",
"perf",
"validator",
@ -25,13 +21,9 @@ members = [
"logger",
"log-analyzer",
"merkle-tree",
"stake-o-matic",
"storage-bigtable",
"streamer",
"measure",
"metrics",
"net-shaper",
"notifier",
"programs/bpf_loader",
"programs/budget",
"programs/btc_spv",
@ -42,22 +34,18 @@ members = [
"programs/noop",
"programs/ownable",
"programs/stake",
"programs/storage",
"programs/vest",
"programs/vote",
"remote-wallet",
"ramp-tps",
"archiver",
"runtime",
"sdk",
"sdk-c",
"scripts",
"stake-accounts",
"stake-monitor",
"sys-tuner",
"tokens",
"transaction-status",
"account-decoder",
"upload-perf",
"net-utils",
"version",
"fixed-buf",
"vote-signer",
"cli",
"rayon-threadlimit",
@ -66,4 +54,6 @@ members = [
exclude = [
"programs/bpf",
"programs/move_loader",
"programs/librapay",
]

187
README.md
View File

@ -1,17 +1,76 @@
<p align="center">
<a href="https://solana.com">
<img alt="Solana" src="https://i.imgur.com/OMnvVEz.png" width="250" />
</a>
</p>
[![Solana crate](https://img.shields.io/crates/v/solana-core.svg)](https://crates.io/crates/solana-core)
[![Solana documentation](https://docs.rs/solana-core/badge.svg)](https://docs.rs/solana-core)
[![Build status](https://badge.buildkite.com/8cc350de251d61483db98bdfc895b9ea0ac8ffa4a32ee850ed.svg?branch=master)](https://buildkite.com/solana-labs/solana/builds?branch=master)
[![codecov](https://codecov.io/gh/solana-labs/solana/branch/master/graph/badge.svg)](https://codecov.io/gh/solana-labs/solana)
# Building
Blockchain Rebuilt for Scale
===
## **1. Install rustc, cargo and rustfmt.**
Solana&trade; is a new blockchain architecture built from the ground up for scale. The architecture supports
up to 710 thousand transactions per second on a gigabit network.
Disclaimer
===
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
Introduction
===
It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [\[H.T.Kung, J.T.Robinson (1981)\]](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.65.4735). At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain!
> Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [\[L.Lamport (1984)\]](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.1078)
Furthermore, and much to our surprise, it can be implemented using a mechanism that has existed in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.
Architecture
===
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
Release Binaries
===
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
Additionally we provide pre-release binaries for the latest code on the edge and
beta channels. Note that these pre-release binaries may be less stable than an
official release.
### Edge channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
### Beta channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
Developing
===
Building
---
Install rustc, cargo and rustfmt:
```bash
$ curl https://sh.rustup.rs -sSf | sh
@ -28,43 +87,118 @@ $ rustup update
On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc. On Ubuntu:
```bash
$ sudo apt-get update
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev llvm clang
```
## **2. Download the source code.**
Download the source code:
```bash
$ git clone https://github.com/solana-labs/solana.git
$ cd solana
```
## **3. Build.**
Build
```bash
$ cargo build
```
## **4. Run a minimal local cluster.**
Then to run a minimal local cluster
```bash
$ ./run.sh
```
# Testing
Testing
---
**Run the test suite:**
Run the test suite:
```bash
$ cargo test
```
### Starting a local testnet
Start your own testnet locally, instructions are in the [online docs](https://docs.solana.com/bench-tps).
Local Testnet
---
### Accessing the remote testnet
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
# Benchmarking
Remote Testnets
---
We maintain several testnets:
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
## Deploy process
They are deployed with the `ci/testnet-manager.sh` script through a list of [scheduled
buildkite jobs](https://buildkite.com/solana-labs/testnet-management/settings/schedules).
Each testnet can be manually manipulated from buildkite as well.
## How do I reset the testnet?
Manually trigger the [testnet-management](https://buildkite.com/solana-labs/testnet-management) pipeline
and when prompted select the desired testnet
## How can I scale the tx generation rate?
Increase the TX rate by increasing the number of cores on the client machine which is running
`bench-tps` or run multiple clients. Decrease by lowering cores or using the rayon env
variable `RAYON_NUM_THREADS=<xx>`
## How can I test a change on the testnet?
Currently, a merged PR is the only way to test a change on the testnet. But you
can run your own testnet using the scripts in the `net/` directory.
## Adjusting the number of clients or validators on the testnet
Edit `ci/testnet-manager.sh`
## Metrics Server Maintenance
Sometimes the dashboard becomes unresponsive. This happens due to glitch in the metrics server.
The current solution is to reset the metrics server. Use the following steps.
1. The server is hosted in a GCP VM instance. Check if the VM instance is down by trying to SSH
into it from the GCP console. The name of the VM is ```metrics-solana-com```.
2. If the VM is inaccessible, reset it from the GCP console.
3. Once VM is up (or, was already up), the metrics services can be restarted from build automation.
1. Navigate to https://buildkite.com/solana-labs/metrics-dot-solana-dot-com in your web browser
2. Click on ```New Build```
3. This will show a pop up dialog. Click on ```options``` drop down.
4. Type in ```FORCE_START=true``` in ```Environment Variables``` text box.
5. Click ```Create Build```
6. This will restart the metrics services, and the dashboards should be accessible afterwards.
## Debugging Testnet
Testnet may exhibit different symptoms of failures. Primary statistics to check are
1. Rise in Confirmation Time
2. Nodes are not voting
3. Panics, and OOM notifications
Check the following if there are any signs of failure.
1. Did testnet deployment fail?
1. View buildkite logs for the last deployment: https://buildkite.com/solana-labs/testnet-management
2. Use the relevant branch
3. If the deployment failed, look at the build logs. The build artifacts for each remote node is uploaded.
It's a good first step to triage from these logs.
2. You may have to log into remote node if the deployment succeeded, but something failed during runtime.
1. Get the private key for the testnet deployment from ```metrics-solana-com``` GCP instance.
2. SSH into ```metrics-solana-com``` using GCP console and do the following.
```bash
sudo bash
cd ~buildkite-agent/.ssh
ls
```
3. Copy the relevant private key to your local machine
4. Find the public IP address of the AWS instance for the remote node using AWS console
5. ```ssh -i <private key file> ubuntu@<ip address of remote node>```
6. The logs are in ```~solana\solana``` folder
Benchmarking
---
First install the nightly build of rustc. `cargo bench` requires use of the
unstable features only available in the nightly build.
@ -79,11 +213,13 @@ Run the benchmarks:
$ cargo +nightly bench
```
# Release Process
Release Process
---
The release process for this project is described [here](RELEASE.md).
# Code coverage
Code coverage
---
To generate code coverage statistics:
@ -92,6 +228,7 @@ $ scripts/coverage.sh
$ open target/cov/lcov-local/index.html
```
Why coverage? While most see coverage as a code quality metric, we see it primarily as a developer
productivity metric. When a developer makes a change to the codebase, presumably it's a *solution* to
some problem. Our unit-test suite is how we encode the set of *problems* the codebase solves. Running
@ -103,7 +240,3 @@ problem is solved by this code?" On the other hand, if a test does fail and you
better way to solve the same problem, a Pull Request with your solution would most certainly be
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
send us that patch!
# Disclaimer
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.

View File

@ -116,8 +116,7 @@ There are three release channels that map to branches as follows:
1. After the new release has been tagged, update the Cargo.toml files on **release branch** to the next semantic version (e.g. 0.9.0 -> 0.9.1) with:
```
$ scripts/increment-cargo-version.sh patch
$ ./scripts/cargo-for-all-lock-files.sh tree
scripts/increment-cargo-version.sh patch
```
1. Rebuild to get an updated version of `Cargo.lock`:
```
@ -139,11 +138,30 @@ There are three release channels that map to branches as follows:
### Update documentation
TODO: Documentation update procedure is WIP as we move to gitbook
Document the new recommended version by updating `docs/src/running-archiver.md` and `docs/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
Document the new recommended version by updating `book/src/running-archiver.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
### Update software on devnet.solana.com
#### Publish updated Book
We maintain three copies of the "book" as official documentation:
The testnet running on devnet.solana.com is set to use a fixed release tag
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
https://solana-labs.github.io/book/
2) "Book-edge" tracks the tip of the master branch and updates automatically.
https://solana-labs.github.io/book-edge/
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
https://solana-labs.github.io/book-beta/
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
```bash
PUBLISH_BOOK_TAG=v0.16.6
```
https://buildkite.com/solana-labs/manual-update-book
### Update software on testnet.solana.com
The testnet running on testnet.solana.com is set to use a fixed release tag
which is set in the Buildkite testnet-management pipeline.
This tag needs to be updated and the testnet restarted after a new release
tag is created.
@ -183,4 +201,4 @@ TESTNET_OP=create-and-start
### Alert the community
Notify Discord users on #validator-support that a new release for
devnet.solana.com is available
testnet.solana.com is available

View File

@ -1,29 +0,0 @@
[package]
name = "solana-account-decoder"
version = "1.2.33"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
license = "Apache-2.0"
edition = "2018"
[dependencies]
bincode = "1.3.1"
base64 = "0.12.3"
bs58 = "0.3.1"
bv = "0.11.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
solana-config-program = { path = "../programs/config", version = "1.2.33" }
solana-sdk = { path = "../sdk", version = "1.2.33" }
solana-stake-program = { path = "../programs/stake", version = "1.2.33" }
solana-vote-program = { path = "../programs/vote", version = "1.2.33" }
spl-token-v2-0 = { package = "spl-token", version = "2.0.6", features = ["skip-no-mangle"] }
thiserror = "1.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,182 +0,0 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
pub mod parse_account_data;
pub mod parse_config;
pub mod parse_nonce;
pub mod parse_stake;
pub mod parse_sysvar;
pub mod parse_token;
pub mod parse_vote;
pub mod validator_info;
use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount};
use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey};
use std::str::FromStr;
pub type StringAmount = String;
/// A duplicate representation of an Account for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UiAccount {
pub lamports: u64,
pub data: UiAccountData,
pub owner: String,
pub executable: bool,
pub rent_epoch: Epoch,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiAccountData {
LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
Json(ParsedAccount),
Binary(String, UiAccountEncoding),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiAccountEncoding {
Binary, // Legacy. Retained for RPC backwards compatibility
Base58,
Base64,
JsonParsed,
}
impl UiAccount {
pub fn encode(
pubkey: &Pubkey,
account: Account,
encoding: UiAccountEncoding,
additional_data: Option<AccountAdditionalData>,
data_slice_config: Option<UiDataSliceConfig>,
) -> Self {
let data = match encoding {
UiAccountEncoding::Binary => UiAccountData::LegacyBinary(
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
),
UiAccountEncoding::Base58 => UiAccountData::Binary(
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
encoding,
),
UiAccountEncoding::Base64 => UiAccountData::Binary(
base64::encode(slice_data(&account.data, data_slice_config)),
encoding,
),
UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) =
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
{
UiAccountData::Json(parsed_data)
} else {
UiAccountData::Binary(base64::encode(&account.data), UiAccountEncoding::Base64)
}
}
};
UiAccount {
lamports: account.lamports,
data,
owner: account.owner.to_string(),
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
pub fn decode(&self) -> Option<Account> {
let data = match &self.data {
UiAccountData::Json(_) => None,
UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
UiAccountData::Binary(blob, encoding) => match encoding {
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
},
}?;
Some(Account {
lamports: self.lamports,
data,
owner: Pubkey::from_str(&self.owner).ok()?,
executable: self.executable,
rent_epoch: self.rent_epoch,
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiFeeCalculator {
pub lamports_per_signature: StringAmount,
}
impl From<FeeCalculator> for UiFeeCalculator {
fn from(fee_calculator: FeeCalculator) -> Self {
Self {
lamports_per_signature: fee_calculator.lamports_per_signature.to_string(),
}
}
}
impl Default for UiFeeCalculator {
fn default() -> Self {
Self {
lamports_per_signature: "0".to_string(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UiDataSliceConfig {
pub offset: usize,
pub length: usize,
}
fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
if offset >= data.len() {
&[]
} else if length > data.len() - offset {
&data[offset..]
} else {
&data[offset..offset + length]
}
} else {
data
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_slice_data() {
let data = vec![1, 2, 3, 4, 5];
let slice_config = Some(UiDataSliceConfig {
offset: 0,
length: 5,
});
assert_eq!(slice_data(&data, slice_config), &data[..]);
let slice_config = Some(UiDataSliceConfig {
offset: 0,
length: 10,
});
assert_eq!(slice_data(&data, slice_config), &data[..]);
let slice_config = Some(UiDataSliceConfig {
offset: 1,
length: 2,
});
assert_eq!(slice_data(&data, slice_config), &data[1..3]);
let slice_config = Some(UiDataSliceConfig {
offset: 10,
length: 2,
});
assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
}
}

View File

@ -1,145 +0,0 @@
use crate::{
parse_config::parse_config,
parse_nonce::parse_nonce,
parse_stake::parse_stake,
parse_sysvar::parse_sysvar,
parse_token::{parse_token, spl_token_id_v2_0},
parse_vote::parse_vote,
};
use inflector::Inflector;
use serde_json::Value;
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
use std::collections::HashMap;
use thiserror::Error;
lazy_static! {
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
static ref STAKE_PROGRAM_ID: Pubkey = solana_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_v2_0();
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
}
#[derive(Error, Debug)]
pub enum ParseAccountError {
#[error("{0:?} account not parsable")]
AccountNotParsable(ParsableAccount),
#[error("Program not parsable")]
ProgramNotParsable,
#[error("Additional data required to parse: {0}")]
AdditionalDataMissing(String),
#[error("Instruction error")]
InstructionError(#[from] InstructionError),
#[error("Serde json error")]
SerdeJsonError(#[from] serde_json::error::Error),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ParsedAccount {
pub program: String,
pub parsed: Value,
pub space: u64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Config,
Nonce,
SplToken,
Stake,
Sysvar,
Vote,
}
#[derive(Default)]
pub struct AccountAdditionalData {
pub spl_token_decimals: Option<u8>,
}
pub fn parse_account_data(
pubkey: &Pubkey,
program_id: &Pubkey,
data: &[u8],
additional_data: Option<AccountAdditionalData>,
) -> Result<ParsedAccount, ParseAccountError> {
let program_name = PARSABLE_PROGRAM_IDS
.get(program_id)
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let additional_data = additional_data.unwrap_or_default();
let parsed_json = match program_name {
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::SplToken => {
serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
}
ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(ParsedAccount {
program: format!("{:?}", program_name).to_kebab_case(),
parsed: parsed_json,
space: data.len() as u64,
})
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::nonce::{
state::{Data, Versions},
State,
};
use solana_vote_program::vote_state::{VoteState, VoteStateVersions};
#[test]
fn test_parse_account_data() {
let account_pubkey = Pubkey::new_rand();
let other_program = Pubkey::new_rand();
let data = vec![0; 4];
assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let parsed = parse_account_data(
&account_pubkey,
&solana_vote_program::id(),
&vote_account_data,
None,
)
.unwrap();
assert_eq!(parsed.program, "vote".to_string());
assert_eq!(parsed.space, VoteState::size_of() as u64);
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
let parsed = parse_account_data(
&account_pubkey,
&system_program::id(),
&nonce_account_data,
None,
)
.unwrap();
assert_eq!(parsed.program, "nonce".to_string());
assert_eq!(parsed.space, State::size() as u64);
}
}

View File

@ -1,146 +0,0 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
validator_info,
};
use bincode::deserialize;
use serde_json::Value;
use solana_config_program::{get_config_data, ConfigKeys};
use solana_sdk::pubkey::Pubkey;
use solana_stake_program::config::Config as StakeConfig;
pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
let parsed_account = if pubkey == &solana_stake_program::config::id() {
get_config_data(data)
.ok()
.and_then(|data| deserialize::<StakeConfig>(data).ok())
.map(|config| ConfigAccountType::StakeConfig(config.into()))
} else {
deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
Some(ConfigAccountType::ValidatorInfo(UiConfig {
keys: validator_info.keys,
config_data: serde_json::from_str(&validator_info.config_data).ok()?,
}))
})
} else {
None
}
})
};
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
ParsableAccount::Config,
))
}
fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
where
T: serde::de::DeserializeOwned,
{
let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?;
let keys = keys
.iter()
.map(|key| UiConfigKey {
pubkey: key.0.to_string(),
signer: key.1,
})
.collect();
Some(UiConfig { keys, config_data })
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum ConfigAccountType {
StakeConfig(UiStakeConfig),
ValidatorInfo(UiConfig<Value>),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiConfigKey {
pub pubkey: String,
pub signer: bool,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStakeConfig {
pub warmup_cooldown_rate: f64,
pub slash_penalty: u8,
}
impl From<StakeConfig> for UiStakeConfig {
fn from(config: StakeConfig) -> Self {
Self {
warmup_cooldown_rate: config.warmup_cooldown_rate,
slash_penalty: config.slash_penalty,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiConfig<T> {
pub keys: Vec<UiConfigKey>,
pub config_data: T,
}
#[cfg(test)]
mod test {
use super::*;
use crate::validator_info::ValidatorInfo;
use serde_json::json;
use solana_config_program::create_config_account;
#[test]
fn test_parse_config() {
let stake_config = StakeConfig {
warmup_cooldown_rate: 0.25,
slash_penalty: 50,
};
let stake_config_account = create_config_account(vec![], &stake_config, 10);
assert_eq!(
parse_config(
&stake_config_account.data,
&solana_stake_program::config::id()
)
.unwrap(),
ConfigAccountType::StakeConfig(UiStakeConfig {
warmup_cooldown_rate: 0.25,
slash_penalty: 50,
}),
);
let validator_info = ValidatorInfo {
info: serde_json::to_string(&json!({
"name": "Solana",
}))
.unwrap(),
};
let info_pubkey = Pubkey::new_rand();
let validator_info_config_account = create_config_account(
vec![(validator_info::id(), false), (info_pubkey, true)],
&validator_info,
10,
);
assert_eq!(
parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
ConfigAccountType::ValidatorInfo(UiConfig {
keys: vec![
UiConfigKey {
pubkey: validator_info::id().to_string(),
signer: false,
},
UiConfigKey {
pubkey: info_pubkey.to_string(),
signer: true,
}
],
config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
}),
);
let bad_data = vec![0; 4];
assert!(parse_config(&bad_data, &info_pubkey).is_err());
}
}

View File

@ -1,67 +0,0 @@
use crate::{parse_account_data::ParseAccountError, UiFeeCalculator};
use solana_sdk::{
instruction::InstructionError,
nonce::{state::Versions, State},
};
pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
let nonce_state: Versions = bincode::deserialize(data)
.map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
let nonce_state = nonce_state.convert_to_current();
match nonce_state {
State::Uninitialized => Ok(UiNonceState::Uninitialized),
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(),
fee_calculator: data.fee_calculator.into(),
})),
}
}
/// A duplicate representation of NonceState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum UiNonceState {
Uninitialized,
Initialized(UiNonceData),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiNonceData {
pub authority: String,
pub blockhash: String,
pub fee_calculator: UiFeeCalculator,
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{
hash::Hash,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
};
#[test]
fn test_parse_nonce() {
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
assert_eq!(
parse_nonce(&nonce_account_data).unwrap(),
UiNonceState::Initialized(UiNonceData {
authority: Pubkey::default().to_string(),
blockhash: Hash::default().to_string(),
fee_calculator: UiFeeCalculator {
lamports_per_signature: 0.to_string(),
},
}),
);
let bad_data = vec![0; 4];
assert!(parse_nonce(&bad_data).is_err());
}
}

View File

@ -1,236 +0,0 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
StringAmount,
};
use bincode::deserialize;
use solana_sdk::clock::{Epoch, UnixTimestamp};
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
let stake_state: StakeState = deserialize(data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
let parsed_account = match stake_state {
StakeState::Uninitialized => StakeAccountType::Uninitialized,
StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
meta: meta.into(),
stake: None,
}),
StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
meta: meta.into(),
stake: Some(stake.into()),
}),
StakeState::RewardsPool => StakeAccountType::RewardsPool,
};
Ok(parsed_account)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
#[allow(clippy::large_enum_variant)]
pub enum StakeAccountType {
Uninitialized,
Initialized(UiStakeAccount),
Delegated(UiStakeAccount),
RewardsPool,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStakeAccount {
pub meta: UiMeta,
pub stake: Option<UiStake>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiMeta {
pub rent_exempt_reserve: StringAmount,
pub authorized: UiAuthorized,
pub lockup: UiLockup,
}
impl From<Meta> for UiMeta {
fn from(meta: Meta) -> Self {
Self {
rent_exempt_reserve: meta.rent_exempt_reserve.to_string(),
authorized: meta.authorized.into(),
lockup: meta.lockup.into(),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiLockup {
pub unix_timestamp: UnixTimestamp,
pub epoch: Epoch,
pub custodian: String,
}
impl From<Lockup> for UiLockup {
fn from(lockup: Lockup) -> Self {
Self {
unix_timestamp: lockup.unix_timestamp,
epoch: lockup.epoch,
custodian: lockup.custodian.to_string(),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiAuthorized {
pub staker: String,
pub withdrawer: String,
}
impl From<Authorized> for UiAuthorized {
fn from(authorized: Authorized) -> Self {
Self {
staker: authorized.staker.to_string(),
withdrawer: authorized.withdrawer.to_string(),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStake {
pub delegation: UiDelegation,
pub credits_observed: u64,
}
impl From<Stake> for UiStake {
fn from(stake: Stake) -> Self {
Self {
delegation: stake.delegation.into(),
credits_observed: stake.credits_observed,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiDelegation {
pub voter: String,
pub stake: StringAmount,
pub activation_epoch: StringAmount,
pub deactivation_epoch: StringAmount,
pub warmup_cooldown_rate: f64,
}
impl From<Delegation> for UiDelegation {
fn from(delegation: Delegation) -> Self {
Self {
voter: delegation.voter_pubkey.to_string(),
stake: delegation.stake.to_string(),
activation_epoch: delegation.activation_epoch.to_string(),
deactivation_epoch: delegation.deactivation_epoch.to_string(),
warmup_cooldown_rate: delegation.warmup_cooldown_rate,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use bincode::serialize;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_parse_stake() {
let stake_state = StakeState::Uninitialized;
let stake_data = serialize(&stake_state).unwrap();
assert_eq!(
parse_stake(&stake_data).unwrap(),
StakeAccountType::Uninitialized
);
let pubkey = Pubkey::new_rand();
let custodian = Pubkey::new_rand();
let authorized = Authorized::auto(&pubkey);
let lockup = Lockup {
unix_timestamp: 0,
epoch: 1,
custodian,
};
let meta = Meta {
rent_exempt_reserve: 42,
authorized,
lockup,
};
let stake_state = StakeState::Initialized(meta);
let stake_data = serialize(&stake_state).unwrap();
assert_eq!(
parse_stake(&stake_data).unwrap(),
StakeAccountType::Initialized(UiStakeAccount {
meta: UiMeta {
rent_exempt_reserve: 42.to_string(),
authorized: UiAuthorized {
staker: pubkey.to_string(),
withdrawer: pubkey.to_string(),
},
lockup: UiLockup {
unix_timestamp: 0,
epoch: 1,
custodian: custodian.to_string(),
}
},
stake: None,
})
);
let voter_pubkey = Pubkey::new_rand();
let stake = Stake {
delegation: Delegation {
voter_pubkey,
stake: 20,
activation_epoch: 2,
deactivation_epoch: std::u64::MAX,
warmup_cooldown_rate: 0.25,
},
credits_observed: 10,
};
let stake_state = StakeState::Stake(meta, stake);
let stake_data = serialize(&stake_state).unwrap();
assert_eq!(
parse_stake(&stake_data).unwrap(),
StakeAccountType::Delegated(UiStakeAccount {
meta: UiMeta {
rent_exempt_reserve: 42.to_string(),
authorized: UiAuthorized {
staker: pubkey.to_string(),
withdrawer: pubkey.to_string(),
},
lockup: UiLockup {
unix_timestamp: 0,
epoch: 1,
custodian: custodian.to_string(),
}
},
stake: Some(UiStake {
delegation: UiDelegation {
voter: voter_pubkey.to_string(),
stake: 20.to_string(),
activation_epoch: 2.to_string(),
deactivation_epoch: std::u64::MAX.to_string(),
warmup_cooldown_rate: 0.25,
},
credits_observed: 10,
})
})
);
let stake_state = StakeState::RewardsPool;
let stake_data = serialize(&stake_state).unwrap();
assert_eq!(
parse_stake(&stake_data).unwrap(),
StakeAccountType::RewardsPool
);
let bad_data = vec![1, 2, 3, 4];
assert!(parse_stake(&bad_data).is_err());
}
}

View File

@ -1,328 +0,0 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
StringAmount, UiFeeCalculator,
};
use bincode::deserialize;
use bv::BitVec;
use solana_sdk::{
clock::{Clock, Epoch, Slot, UnixTimestamp},
epoch_schedule::EpochSchedule,
pubkey::Pubkey,
rent::Rent,
slot_hashes::SlotHashes,
slot_history::{self, SlotHistory},
stake_history::{StakeHistory, StakeHistoryEntry},
sysvar::{self, fees::Fees, recent_blockhashes::RecentBlockhashes, rewards::Rewards},
};
pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
let parsed_account = {
if pubkey == &sysvar::clock::id() {
deserialize::<Clock>(data)
.ok()
.map(|clock| SysvarAccountType::Clock(clock.into()))
} else if pubkey == &sysvar::epoch_schedule::id() {
deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
} else if pubkey == &sysvar::fees::id() {
deserialize::<Fees>(data)
.ok()
.map(|fees| SysvarAccountType::Fees(fees.into()))
} else if pubkey == &sysvar::recent_blockhashes::id() {
deserialize::<RecentBlockhashes>(data)
.ok()
.map(|recent_blockhashes| {
let recent_blockhashes = recent_blockhashes
.iter()
.map(|entry| UiRecentBlockhashesEntry {
blockhash: entry.blockhash.to_string(),
fee_calculator: entry.fee_calculator.clone().into(),
})
.collect();
SysvarAccountType::RecentBlockhashes(recent_blockhashes)
})
} else if pubkey == &sysvar::rent::id() {
deserialize::<Rent>(data)
.ok()
.map(|rent| SysvarAccountType::Rent(rent.into()))
} else if pubkey == &sysvar::rewards::id() {
deserialize::<Rewards>(data)
.ok()
.map(|rewards| SysvarAccountType::Rewards(rewards.into()))
} else if pubkey == &sysvar::slot_hashes::id() {
deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
let slot_hashes = slot_hashes
.iter()
.map(|slot_hash| UiSlotHashEntry {
slot: slot_hash.0,
hash: slot_hash.1.to_string(),
})
.collect();
SysvarAccountType::SlotHashes(slot_hashes)
})
} else if pubkey == &sysvar::slot_history::id() {
deserialize::<SlotHistory>(data).ok().map(|slot_history| {
SysvarAccountType::SlotHistory(UiSlotHistory {
next_slot: slot_history.next_slot,
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
})
})
} else if pubkey == &sysvar::stake_history::id() {
deserialize::<StakeHistory>(data).ok().map(|stake_history| {
let stake_history = stake_history
.iter()
.map(|entry| UiStakeHistoryEntry {
epoch: entry.0,
stake_history: entry.1.clone(),
})
.collect();
SysvarAccountType::StakeHistory(stake_history)
})
} else {
None
}
};
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
ParsableAccount::Sysvar,
))
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum SysvarAccountType {
Clock(UiClock),
EpochSchedule(EpochSchedule),
Fees(UiFees),
RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
Rent(UiRent),
Rewards(UiRewards),
SlotHashes(Vec<UiSlotHashEntry>),
SlotHistory(UiSlotHistory),
StakeHistory(Vec<UiStakeHistoryEntry>),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct UiClock {
pub slot: Slot,
pub epoch: Epoch,
pub leader_schedule_epoch: Epoch,
pub unix_timestamp: UnixTimestamp,
}
impl From<Clock> for UiClock {
fn from(clock: Clock) -> Self {
Self {
slot: clock.slot,
epoch: clock.epoch,
leader_schedule_epoch: clock.leader_schedule_epoch,
unix_timestamp: clock.unix_timestamp,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct UiFees {
pub fee_calculator: UiFeeCalculator,
}
impl From<Fees> for UiFees {
fn from(fees: Fees) -> Self {
Self {
fee_calculator: fees.fee_calculator.into(),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct UiRent {
pub lamports_per_byte_year: StringAmount,
pub exemption_threshold: f64,
pub burn_percent: u8,
}
impl From<Rent> for UiRent {
fn from(rent: Rent) -> Self {
Self {
lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
exemption_threshold: rent.exemption_threshold,
burn_percent: rent.burn_percent,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct UiRewards {
pub validator_point_value: f64,
}
impl From<Rewards> for UiRewards {
fn from(rewards: Rewards) -> Self {
Self {
validator_point_value: rewards.validator_point_value,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiRecentBlockhashesEntry {
pub blockhash: String,
pub fee_calculator: UiFeeCalculator,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiSlotHashEntry {
pub slot: Slot,
pub hash: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiSlotHistory {
pub next_slot: Slot,
pub bits: String,
}
struct SlotHistoryBits(BitVec<u64>);
impl std::fmt::Debug for SlotHistoryBits {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for i in 0..slot_history::MAX_ENTRIES {
if self.0.get(i) {
write!(f, "1")?;
} else {
write!(f, "0")?;
}
}
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStakeHistoryEntry {
pub epoch: Epoch,
pub stake_history: StakeHistoryEntry,
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{
fee_calculator::FeeCalculator,
hash::Hash,
sysvar::{recent_blockhashes::IterItem, Sysvar},
};
use std::iter::FromIterator;
#[test]
fn test_parse_sysvars() {
let clock_sysvar = Clock::default().create_account(1);
assert_eq!(
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
SysvarAccountType::Clock(UiClock::default()),
);
let epoch_schedule = EpochSchedule {
slots_per_epoch: 12,
leader_schedule_slot_offset: 0,
warmup: false,
first_normal_epoch: 1,
first_normal_slot: 12,
};
let epoch_schedule_sysvar = epoch_schedule.create_account(1);
assert_eq!(
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
SysvarAccountType::EpochSchedule(epoch_schedule),
);
let fees_sysvar = Fees::default().create_account(1);
assert_eq!(
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
SysvarAccountType::Fees(UiFees::default()),
);
let hash = Hash::new(&[1; 32]);
let fee_calculator = FeeCalculator {
lamports_per_signature: 10,
};
let recent_blockhashes =
RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter());
let recent_blockhashes_sysvar = recent_blockhashes.create_account(1);
assert_eq!(
parse_sysvar(
&recent_blockhashes_sysvar.data,
&sysvar::recent_blockhashes::id()
)
.unwrap(),
SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
blockhash: hash.to_string(),
fee_calculator: fee_calculator.into(),
}]),
);
let rent = Rent {
lamports_per_byte_year: 10,
exemption_threshold: 2.0,
burn_percent: 5,
};
let rent_sysvar = rent.create_account(1);
assert_eq!(
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
SysvarAccountType::Rent(rent.into()),
);
let rewards_sysvar = Rewards::default().create_account(1);
assert_eq!(
parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
SysvarAccountType::Rewards(UiRewards::default()),
);
let mut slot_hashes = SlotHashes::default();
slot_hashes.add(1, hash);
let slot_hashes_sysvar = slot_hashes.create_account(1);
assert_eq!(
parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
slot: 1,
hash: hash.to_string(),
}]),
);
let mut slot_history = SlotHistory::default();
slot_history.add(42);
let slot_history_sysvar = slot_history.create_account(1);
assert_eq!(
parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
SysvarAccountType::SlotHistory(UiSlotHistory {
next_slot: slot_history.next_slot,
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
}),
);
let mut stake_history = StakeHistory::default();
let stake_history_entry = StakeHistoryEntry {
effective: 10,
activating: 2,
deactivating: 3,
};
stake_history.add(1, stake_history_entry.clone());
let stake_history_sysvar = stake_history.create_account(1);
assert_eq!(
parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
epoch: 1,
stake_history: stake_history_entry,
}]),
);
let bad_pubkey = Pubkey::new_rand();
assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
let bad_data = vec![0; 4];
assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
}
}

View File

@ -1,299 +0,0 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
StringAmount,
};
use solana_sdk::pubkey::Pubkey;
use spl_token_v2_0::{
solana_sdk::{program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey},
state::{Account, AccountState, Mint, Multisig},
};
use std::str::FromStr;
// A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey
pub fn spl_token_id_v2_0() -> Pubkey {
Pubkey::from_str(&spl_token_v2_0::id().to_string()).unwrap()
}
// A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey
pub fn spl_token_v2_0_native_mint() -> Pubkey {
Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap()
}
pub fn parse_token(
data: &[u8],
mint_decimals: Option<u8>,
) -> Result<TokenAccountType, ParseAccountError> {
if data.len() == Account::get_packed_len() {
let account = Account::unpack(data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
let decimals = mint_decimals.ok_or_else(|| {
ParseAccountError::AdditionalDataMissing(
"no mint_decimals provided to parse spl-token account".to_string(),
)
})?;
Ok(TokenAccountType::Account(UiTokenAccount {
mint: account.mint.to_string(),
owner: account.owner.to_string(),
token_amount: token_amount_to_ui_amount(account.amount, decimals),
delegate: match account.delegate {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
state: account.state.into(),
is_native: account.is_native(),
rent_exempt_reserve: match account.is_native {
COption::Some(reserve) => Some(token_amount_to_ui_amount(reserve, decimals)),
COption::None => None,
},
delegated_amount: if account.delegate.is_none() {
None
} else {
Some(token_amount_to_ui_amount(
account.delegated_amount,
decimals,
))
},
close_authority: match account.close_authority {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
}))
} else if data.len() == Mint::get_packed_len() {
let mint = Mint::unpack(data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Mint(UiMint {
mint_authority: match mint.mint_authority {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
supply: mint.supply.to_string(),
decimals: mint.decimals,
is_initialized: mint.is_initialized,
freeze_authority: match mint.freeze_authority {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
}))
} else if data.len() == Multisig::get_packed_len() {
let multisig = Multisig::unpack(data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Multisig(UiMultisig {
num_required_signers: multisig.m,
num_valid_signers: multisig.n,
is_initialized: multisig.is_initialized,
signers: multisig
.signers
.iter()
.filter_map(|pubkey| {
if pubkey != &SplTokenPubkey::default() {
Some(pubkey.to_string())
} else {
None
}
})
.collect(),
}))
} else {
Err(ParseAccountError::AccountNotParsable(
ParsableAccount::SplToken,
))
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum TokenAccountType {
Account(UiTokenAccount),
Mint(UiMint),
Multisig(UiMultisig),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiTokenAccount {
pub mint: String,
pub owner: String,
pub token_amount: UiTokenAmount,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegate: Option<String>,
pub state: UiAccountState,
pub is_native: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub rent_exempt_reserve: Option<UiTokenAmount>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegated_amount: Option<UiTokenAmount>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_authority: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiAccountState {
Uninitialized,
Initialized,
Frozen,
}
impl From<AccountState> for UiAccountState {
fn from(state: AccountState) -> Self {
match state {
AccountState::Uninitialized => UiAccountState::Uninitialized,
AccountState::Initialized => UiAccountState::Initialized,
AccountState::Frozen => UiAccountState::Frozen,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiTokenAmount {
pub ui_amount: f64,
pub decimals: u8,
pub amount: StringAmount,
}
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
UiTokenAmount {
ui_amount: amount_decimals,
decimals,
amount: amount.to_string(),
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiMint {
pub mint_authority: Option<String>,
pub supply: StringAmount,
pub decimals: u8,
pub is_initialized: bool,
pub freeze_authority: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiMultisig {
pub num_required_signers: u8,
pub num_valid_signers: u8,
pub is_initialized: bool,
pub signers: Vec<String>,
}
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
if data.len() == Account::get_packed_len() {
Some(Pubkey::new(&data[0..32]))
} else {
None
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_token() {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
let mut account_data = vec![0; Account::get_packed_len()];
let mut account = Account::unpack_unchecked(&account_data).unwrap();
account.mint = mint_pubkey;
account.owner = owner_pubkey;
account.amount = 42;
account.state = AccountState::Initialized;
account.is_native = COption::None;
account.close_authority = COption::Some(owner_pubkey);
Account::pack(account, &mut account_data).unwrap();
assert!(parse_token(&account_data, None).is_err());
assert_eq!(
parse_token(&account_data, Some(2)).unwrap(),
TokenAccountType::Account(UiTokenAccount {
mint: mint_pubkey.to_string(),
owner: owner_pubkey.to_string(),
token_amount: UiTokenAmount {
ui_amount: 0.42,
decimals: 2,
amount: "42".to_string()
},
delegate: None,
state: UiAccountState::Initialized,
is_native: false,
rent_exempt_reserve: None,
delegated_amount: None,
close_authority: Some(owner_pubkey.to_string()),
}),
);
let mut mint_data = vec![0; Mint::get_packed_len()];
let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
mint.mint_authority = COption::Some(owner_pubkey);
mint.supply = 42;
mint.decimals = 3;
mint.is_initialized = true;
mint.freeze_authority = COption::Some(owner_pubkey);
Mint::pack(mint, &mut mint_data).unwrap();
assert_eq!(
parse_token(&mint_data, None).unwrap(),
TokenAccountType::Mint(UiMint {
mint_authority: Some(owner_pubkey.to_string()),
supply: 42.to_string(),
decimals: 3,
is_initialized: true,
freeze_authority: Some(owner_pubkey.to_string()),
}),
);
let signer1 = SplTokenPubkey::new(&[1; 32]);
let signer2 = SplTokenPubkey::new(&[2; 32]);
let signer3 = SplTokenPubkey::new(&[3; 32]);
let mut multisig_data = vec![0; Multisig::get_packed_len()];
let mut signers = [SplTokenPubkey::default(); 11];
signers[0] = signer1;
signers[1] = signer2;
signers[2] = signer3;
let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
multisig.m = 2;
multisig.n = 3;
multisig.is_initialized = true;
multisig.signers = signers;
Multisig::pack(multisig, &mut multisig_data).unwrap();
assert_eq!(
parse_token(&multisig_data, None).unwrap(),
TokenAccountType::Multisig(UiMultisig {
num_required_signers: 2,
num_valid_signers: 3,
is_initialized: true,
signers: vec![
signer1.to_string(),
signer2.to_string(),
signer3.to_string()
],
}),
);
let bad_data = vec![0; 4];
assert!(parse_token(&bad_data, None).is_err());
}
#[test]
fn test_get_token_account_mint() {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let mut account_data = vec![0; Account::get_packed_len()];
let mut account = Account::unpack_unchecked(&account_data).unwrap();
account.mint = mint_pubkey;
Account::pack(account, &mut account_data).unwrap();
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
assert_eq!(
get_token_account_mint(&account_data),
Some(expected_mint_pubkey)
);
}
}

View File

@ -1,144 +0,0 @@
use crate::{parse_account_data::ParseAccountError, StringAmount};
use solana_sdk::{
clock::{Epoch, Slot},
pubkey::Pubkey,
};
use solana_vote_program::vote_state::{BlockTimestamp, Lockout, VoteState};
pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, ParseAccountError> {
let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?;
let epoch_credits = vote_state
.epoch_credits()
.iter()
.map(|(epoch, credits, previous_credits)| UiEpochCredits {
epoch: *epoch,
credits: credits.to_string(),
previous_credits: previous_credits.to_string(),
})
.collect();
let votes = vote_state
.votes
.iter()
.map(|lockout| UiLockout {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
})
.collect();
let authorized_voters = vote_state
.authorized_voters()
.iter()
.map(|(epoch, authorized_voter)| UiAuthorizedVoters {
epoch: *epoch,
authorized_voter: authorized_voter.to_string(),
})
.collect();
let prior_voters = vote_state
.prior_voters()
.buf()
.iter()
.filter(|(pubkey, _, _)| pubkey != &Pubkey::default())
.map(
|(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters {
authorized_pubkey: authorized_pubkey.to_string(),
epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch,
target_epoch: *target_epoch,
},
)
.collect();
Ok(VoteAccountType::Vote(UiVoteState {
node_pubkey: vote_state.node_pubkey.to_string(),
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
commission: vote_state.commission,
votes,
root_slot: vote_state.root_slot,
authorized_voters,
prior_voters,
epoch_credits,
last_timestamp: vote_state.last_timestamp,
}))
}
/// A wrapper enum for consistency across programs
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum VoteAccountType {
Vote(UiVoteState),
}
/// A duplicate representation of VoteState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiVoteState {
node_pubkey: String,
authorized_withdrawer: String,
commission: u8,
votes: Vec<UiLockout>,
root_slot: Option<Slot>,
authorized_voters: Vec<UiAuthorizedVoters>,
prior_voters: Vec<UiPriorVoters>,
epoch_credits: Vec<UiEpochCredits>,
last_timestamp: BlockTimestamp,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiLockout {
slot: Slot,
confirmation_count: u32,
}
impl From<&Lockout> for UiLockout {
fn from(lockout: &Lockout) -> Self {
Self {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiAuthorizedVoters {
epoch: Epoch,
authorized_voter: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiPriorVoters {
authorized_pubkey: String,
epoch_of_last_authorized_switch: Epoch,
target_epoch: Epoch,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiEpochCredits {
epoch: Epoch,
credits: StringAmount,
previous_credits: StringAmount,
}
#[cfg(test)]
mod test {
use super::*;
use solana_vote_program::vote_state::VoteStateVersions;
#[test]
fn test_parse_vote() {
let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let mut expected_vote_state = UiVoteState::default();
expected_vote_state.node_pubkey = Pubkey::default().to_string();
expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
assert_eq!(
parse_vote(&vote_account_data).unwrap(),
VoteAccountType::Vote(expected_vote_state)
);
let bad_data = vec![0; 4];
assert!(parse_vote(&bad_data).is_err());
}
}

View File

@ -1,18 +0,0 @@
use solana_config_program::ConfigState;
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
pub const MAX_VALIDATOR_INFO: u64 = 576;
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
pub struct ValidatorInfo {
pub info: String,
}
impl ConfigState for ValidatorInfo {
fn max_space() -> u64 {
MAX_VALIDATOR_INFO
}
}

View File

@ -1,22 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-accounts-bench"
version = "1.2.33"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
log = "0.4.6"
rayon = "1.4.0"
solana-logger = { path = "../logger", version = "1.2.33" }
solana-runtime = { path = "../runtime", version = "1.2.33" }
solana-measure = { path = "../measure", version = "1.2.33" }
solana-sdk = { path = "../sdk", version = "1.2.33" }
rand = "0.7.0"
clap = "2.33.1"
crossbeam-channel = "0.4"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,105 +0,0 @@
use clap::{value_t, App, Arg};
use rayon::prelude::*;
use solana_measure::measure::Measure;
use solana_runtime::{
accounts::{create_test_accounts, update_accounts, Accounts},
accounts_index::Ancestors,
};
use solana_sdk::{genesis_config::OperatingMode, pubkey::Pubkey};
use std::fs;
use std::path::PathBuf;
fn main() {
solana_logger::setup();
let matches = App::new("crate")
.about("about")
.version("version")
.arg(
Arg::with_name("num_slots")
.long("num_slots")
.takes_value(true)
.value_name("SLOTS")
.help("Number of slots to store to."),
)
.arg(
Arg::with_name("num_accounts")
.long("num_accounts")
.takes_value(true)
.value_name("NUM_ACCOUNTS")
.help("Total number of accounts"),
)
.arg(
Arg::with_name("iterations")
.long("iterations")
.takes_value(true)
.value_name("ITERATIONS")
.help("Number of bench iterations"),
)
.arg(
Arg::with_name("clean")
.long("clean")
.takes_value(false)
.help("Run clean"),
)
.get_matches();
let num_slots = value_t!(matches, "num_slots", usize).unwrap_or(4);
let num_accounts = value_t!(matches, "num_accounts", usize).unwrap_or(10_000);
let iterations = value_t!(matches, "iterations", usize).unwrap_or(20);
let clean = matches.is_present("clean");
println!("clean: {:?}", clean);
let path = PathBuf::from("farf/accounts-bench");
if fs::remove_dir_all(path.clone()).is_err() {
println!("Warning: Couldn't remove {:?}", path);
}
let accounts = Accounts::new(vec![path], OperatingMode::Preview);
println!("Creating {} accounts", num_accounts);
let mut create_time = Measure::start("create accounts");
let pubkeys: Vec<_> = (0..num_slots)
.into_par_iter()
.map(|slot| {
let mut pubkeys: Vec<Pubkey> = vec![];
create_test_accounts(
&accounts,
&mut pubkeys,
num_accounts / num_slots,
slot as u64,
);
pubkeys
})
.collect();
let pubkeys: Vec<_> = pubkeys.into_iter().flatten().collect();
create_time.stop();
println!(
"created {} accounts in {} slots {}",
(num_accounts / num_slots) * num_slots,
num_slots,
create_time
);
let mut ancestors: Ancestors = vec![(0, 0)].into_iter().collect();
for i in 1..num_slots {
ancestors.insert(i as u64, i - 1);
accounts.add_root(i as u64);
}
for x in 0..iterations {
if clean {
let mut time = Measure::start("clean");
accounts.accounts_db.clean_accounts();
time.stop();
println!("{}", time);
for slot in 0..num_slots {
update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
accounts.add_root((x * num_slots + slot) as u64);
}
} else {
let mut pubkeys: Vec<Pubkey> = vec![];
let mut time = Measure::start("hash");
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
time.stop();
println!("hash: {} {}", hash, time);
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
}
}
}

19
archiver/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-archiver"
version = "0.22.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
console = "0.9.1"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-metrics = { path = "../metrics", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }

147
archiver/src/main.rs Normal file
View File

@ -0,0 +1,147 @@
use clap::{crate_description, crate_name, App, Arg};
use console::style;
use solana_clap_utils::{
input_validators::is_keypair,
keypair::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
},
};
use solana_core::{
archiver::Archiver,
cluster_info::{Node, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
};
use solana_sdk::{commitment_config::CommitmentConfig, signature::KeypairUtil};
use std::{net::SocketAddr, path::PathBuf, process::exit, sync::Arc};
fn main() {
solana_logger::setup();
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_clap_utils::version!())
.arg(
Arg::with_name("identity_keypair")
.short("i")
.long("identity-keypair")
.value_name("PATH")
.takes_value(true)
.validator(is_keypair)
.help("File containing an identity (keypair)"),
)
.arg(
Arg::with_name("entrypoint")
.short("n")
.long("entrypoint")
.value_name("HOST:PORT")
.takes_value(true)
.required(true)
.validator(solana_net_utils::is_host_port)
.help("Rendezvous with the cluster at this entry point"),
)
.arg(
Arg::with_name("ledger")
.short("l")
.long("ledger")
.value_name("DIR")
.takes_value(true)
.required(true)
.help("use DIR as persistent ledger location"),
)
.arg(
Arg::with_name("storage_keypair")
.short("s")
.long("storage-keypair")
.value_name("PATH")
.takes_value(true)
.validator(is_keypair)
.help("File containing the storage account keypair"),
)
.arg(
Arg::with_name(ASK_SEED_PHRASE_ARG.name)
.long(ASK_SEED_PHRASE_ARG.long)
.value_name("KEYPAIR NAME")
.multiple(true)
.takes_value(true)
.possible_values(&["identity-keypair", "storage-keypair"])
.help(ASK_SEED_PHRASE_ARG.help),
)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
.requires(ASK_SEED_PHRASE_ARG.name)
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.get_matches();
let ledger_path = PathBuf::from(matches.value_of("ledger").unwrap());
let identity_keypair = keypair_input(&matches, "identity_keypair")
.unwrap_or_else(|err| {
eprintln!("Identity keypair input failed: {}", err);
exit(1);
})
.keypair;
let KeypairWithSource {
keypair: storage_keypair,
source: storage_keypair_source,
} = keypair_input(&matches, "storage_keypair").unwrap_or_else(|err| {
eprintln!("Storage keypair input failed: {}", err);
exit(1);
});
if storage_keypair_source == keypair::Source::Generated {
clap::Error::with_description(
"The `storage-keypair` argument was not found",
clap::ErrorKind::ArgumentNotFound,
)
.exit();
}
let entrypoint_addr = matches
.value_of("entrypoint")
.map(|entrypoint| {
solana_net_utils::parse_host_port(entrypoint)
.expect("failed to parse entrypoint address")
})
.unwrap();
let gossip_addr = {
let ip = solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap();
let mut addr = SocketAddr::new(ip, 0);
addr.set_ip(solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap());
addr
};
let node = Node::new_archiver_with_external_ip(
&identity_keypair.pubkey(),
&gossip_addr,
VALIDATOR_PORT_RANGE,
);
println!(
"{} version {} (branch={}, commit={})",
style(crate_name!()).bold(),
solana_clap_utils::version!(),
option_env!("CI_BRANCH").unwrap_or("unknown"),
option_env!("CI_COMMIT").unwrap_or("unknown")
);
solana_metrics::set_host_id(identity_keypair.pubkey().to_string());
println!(
"replicating the data with identity_keypair={:?} gossip_addr={:?}",
identity_keypair.pubkey(),
gossip_addr
);
let entrypoint_info = ContactInfo::new_gossip_entry_point(&entrypoint_addr);
let archiver = Archiver::new(
&ledger_path,
node,
entrypoint_info,
Arc::new(identity_keypair),
Arc::new(storage_keypair),
CommitmentConfig::recent(),
)
.unwrap();
archiver.join();
}

View File

@ -2,27 +2,19 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-banking-bench"
version = "1.2.33"
version = "0.22.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.1"
crossbeam-channel = "0.4"
log = "0.4.6"
rand = "0.7.0"
rayon = "1.4.0"
solana-core = { path = "../core", version = "1.2.33" }
solana-clap-utils = { path = "../clap-utils", version = "1.2.33" }
solana-streamer = { path = "../streamer", version = "1.2.33" }
solana-perf = { path = "../perf", version = "1.2.33" }
solana-ledger = { path = "../ledger", version = "1.2.33" }
solana-logger = { path = "../logger", version = "1.2.33" }
solana-runtime = { path = "../runtime", version = "1.2.33" }
solana-measure = { path = "../measure", version = "1.2.33" }
solana-sdk = { path = "../sdk", version = "1.2.33" }
solana-version = { path = "../version", version = "1.2.33" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rayon = "1.2.0"
solana-core = { path = "../core", version = "0.22.0" }
solana-ledger = { path = "../ledger", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-measure = { path = "../measure", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
rand = "0.6.5"
crossbeam-channel = "0.3"

View File

@ -1,38 +1,30 @@
use clap::{crate_description, crate_name, value_t, App, Arg};
use crossbeam_channel::unbounded;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana_core::{
banking_stage::{create_test_recorder, BankingStage},
cluster_info::ClusterInfo,
cluster_info::Node,
poh_recorder::PohRecorder,
poh_recorder::WorkingBankEntry,
};
use solana_ledger::{
bank_forks::BankForks,
blockstore::Blockstore,
genesis_utils::{create_genesis_config, GenesisConfigInfo},
get_tmp_ledger_path,
};
use solana_core::banking_stage::{create_test_recorder, BankingStage};
use solana_core::cluster_info::ClusterInfo;
use solana_core::cluster_info::Node;
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::PohRecorder;
use solana_core::poh_recorder::WorkingBankEntry;
use solana_ledger::bank_forks::BankForks;
use solana_ledger::{blocktree::Blocktree, get_tmp_ledger_path};
use solana_measure::measure::Measure;
use solana_perf::packet::to_packets_chunked;
use solana_runtime::bank::Bank;
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::Keypair,
signature::Signature,
system_transaction,
timing::{duration_as_us, timestamp},
transaction::Transaction,
};
use std::{
sync::{atomic::Ordering, mpsc::Receiver, Arc, Mutex},
thread::sleep,
time::{Duration, Instant},
};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::Signature;
use solana_sdk::system_transaction;
use solana_sdk::timing::{duration_as_us, timestamp};
use solana_sdk::transaction::Transaction;
use std::sync::atomic::Ordering;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex, RwLock};
use std::thread::sleep;
use std::time::{Duration, Instant};
fn check_txs(
receiver: &Arc<Receiver<WorkingBankEntry>>,
@ -65,22 +57,15 @@ fn check_txs(
no_bank
}
fn make_accounts_txs(
total_num_transactions: usize,
hash: Hash,
same_payer: bool,
) -> Vec<Transaction> {
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
let to_pubkey = Pubkey::new_rand();
let payer_key = Keypair::new();
let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash);
(0..total_num_transactions)
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
(0..txes)
.into_par_iter()
.map(|_| {
let mut new = dummy.clone();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
if !same_payer {
new.message.account_keys[0] = Pubkey::new_rand();
}
new.message.account_keys[0] = Pubkey::new_rand();
new.message.account_keys[1] = Pubkey::new_rand();
new.signatures = vec![Signature::new(&sig[0..64])];
new
@ -104,61 +89,13 @@ fn bytes_as_usize(bytes: &[u8]) -> usize {
bytes[0] as usize | (bytes[1] as usize) << 8
}
#[allow(clippy::cognitive_complexity)]
fn main() {
solana_logger::setup();
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
.arg(
Arg::with_name("num_chunks")
.long("num-chunks")
.takes_value(true)
.value_name("SIZE")
.help("Number of transaction chunks."),
)
.arg(
Arg::with_name("packets_per_chunk")
.long("packets-per-chunk")
.takes_value(true)
.value_name("SIZE")
.help("Packets per chunk"),
)
.arg(
Arg::with_name("skip_sanity")
.long("skip-sanity")
.takes_value(false)
.help("Skip transaction sanity execution"),
)
.arg(
Arg::with_name("same_payer")
.long("same-payer")
.takes_value(false)
.help("Use the same payer for transfers"),
)
.arg(
Arg::with_name("iterations")
.long("iterations")
.takes_value(true)
.help("Number of iterations"),
)
.arg(
Arg::with_name("num_threads")
.long("num-threads")
.takes_value(true)
.help("Number of iterations"),
)
.get_matches();
let num_threads =
value_t!(matches, "num_threads", usize).unwrap_or(BankingStage::num_threads() as usize);
let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk duplicates to avoid races
let num_chunks = value_t!(matches, "num_chunks", usize).unwrap_or(16);
let packets_per_chunk = value_t!(matches, "packets_per_chunk", usize).unwrap_or(192);
let iterations = value_t!(matches, "iterations", usize).unwrap_or(1000);
let total_num_transactions = num_chunks * num_threads * packets_per_chunk;
const CHUNKS: usize = 8 * 2;
const PACKETS_PER_BATCH: usize = 192;
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
let mint_total = 1_000_000_000_000;
let GenesisConfigInfo {
genesis_config,
@ -169,56 +106,46 @@ fn main() {
let (verified_sender, verified_receiver) = unbounded();
let (vote_sender, vote_receiver) = unbounded();
let bank0 = Bank::new(&genesis_config);
let mut bank_forks = BankForks::new(bank0);
let mut bank_forks = BankForks::new(0, bank0);
let mut bank = bank_forks.working_bank();
info!("threads: {} txs: {}", num_threads, total_num_transactions);
info!("threads: {} txs: {}", num_threads, txes);
let same_payer = matches.is_present("same_payer");
let mut transactions =
make_accounts_txs(total_num_transactions, genesis_config.hash(), same_payer);
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
// fund all the accounts
transactions.iter().for_each(|tx| {
let mut fund = system_transaction::transfer(
let fund = system_transaction::transfer(
&mint_keypair,
&tx.message.account_keys[0],
mint_total / total_num_transactions as u64,
mint_total / txes as u64,
genesis_config.hash(),
);
// Ignore any pesky duplicate signature errors in the case we are using single-payer
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
fund.signatures = vec![Signature::new(&sig[0..64])];
let x = bank.process_transaction(&fund);
x.unwrap();
});
let skip_sanity = matches.is_present("skip_sanity");
if !skip_sanity {
//sanity check, make sure all the transactions can execute sequentially
transactions.iter().for_each(|tx| {
let res = bank.process_transaction(&tx);
assert!(res.is_ok(), "sanity test transactions error: {:?}", res);
});
bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
for r in res {
assert!(r.is_ok(), "sanity parallel execution error: {:?}", r);
}
bank.clear_signatures();
//sanity check, make sure all the transactions can execute sequentially
transactions.iter().for_each(|tx| {
let res = bank.process_transaction(&tx);
assert!(res.is_ok(), "sanity test transactions");
});
bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
for r in res {
assert!(r.is_ok(), "sanity parallel execution");
}
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), packets_per_chunk);
bank.clear_signatures();
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
let ledger_path = get_tmp_ledger_path!();
{
let blockstore = Arc::new(
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
let blocktree = Arc::new(
Blocktree::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, &blocktree, None);
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
let cluster_info = Arc::new(cluster_info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let banking_stage = BankingStage::new(
&cluster_info,
&poh_recorder,
@ -228,26 +155,25 @@ fn main() {
);
poh_recorder.lock().unwrap().set_bank(&bank);
let chunk_len = verified.len() / num_chunks;
let chunk_len = verified.len() / CHUNKS;
let mut start = 0;
// This is so that the signal_receiver does not go out of scope after the closure.
// If it is dropped before poh_service, then poh_service will error when
// calling send() on the channel.
let signal_receiver = Arc::new(signal_receiver);
let mut total_us = 0;
let mut tx_total_us = 0;
let base_tx_count = bank.transaction_count();
let mut total = 0;
let mut tx_total = 0;
let mut txs_processed = 0;
let mut root = 1;
let collector = Pubkey::new_rand();
const ITERS: usize = 1_000;
let config = Config {
packets_per_batch: packets_per_chunk,
packets_per_batch: PACKETS_PER_BATCH,
chunk_len,
num_threads,
};
let mut total_sent = 0;
for _ in 0..iterations {
for _ in 0..ITERS {
let now = Instant::now();
let mut sent = 0;
@ -288,11 +214,7 @@ fn main() {
sleep(Duration::from_millis(5));
}
}
if check_txs(
&signal_receiver,
total_num_transactions / num_chunks,
&poh_recorder,
) {
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
debug!(
"resetting bank {} tx count: {} txs_proc: {}",
bank.slot(),
@ -301,7 +223,7 @@ fn main() {
);
assert!(txs_processed < bank.transaction_count());
txs_processed = bank.transaction_count();
tx_total_us += duration_as_us(&now.elapsed());
tx_total += duration_as_us(&now.elapsed());
let mut poh_time = Measure::start("poh_time");
poh_recorder.lock().unwrap().reset(
@ -323,7 +245,7 @@ fn main() {
poh_recorder.lock().unwrap().set_bank(&bank);
assert!(poh_recorder.lock().unwrap().bank().is_some());
if bank.slot() > 32 {
bank_forks.set_root(root, &None, None);
bank_forks.set_root(root, &None);
root += 1;
}
debug!(
@ -333,21 +255,20 @@ fn main() {
poh_time.as_us(),
);
} else {
tx_total_us += duration_as_us(&now.elapsed());
tx_total += duration_as_us(&now.elapsed());
}
// This signature clear may not actually clear the signatures
// 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();
total_us += duration_as_us(&now.elapsed());
total += duration_as_us(&now.elapsed());
debug!(
"time: {} us checked: {} sent: {}",
duration_as_us(&now.elapsed()),
total_num_transactions / num_chunks,
txes / CHUNKS,
sent,
);
total_sent += sent;
if bank.slot() > 0 && bank.slot() % 16 == 0 {
for tx in transactions.iter_mut() {
@ -355,25 +276,19 @@ fn main() {
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
tx.signatures[0] = Signature::new(&sig[0..64]);
}
verified = to_packets_chunked(&transactions.clone(), packets_per_chunk);
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
}
start += chunk_len;
start %= verified.len();
}
let txs_processed = bank_forks.working_bank().transaction_count();
debug!("processed: {} base: {}", txs_processed, base_tx_count);
eprintln!(
"{{'name': 'banking_bench_total', 'median': '{:.2}'}}",
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
"{{'name': 'banking_bench_total', 'median': '{}'}}",
total / ITERS as u64,
);
eprintln!(
"{{'name': 'banking_bench_tx_total', 'median': '{:.2}'}}",
(1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
);
eprintln!(
"{{'name': 'banking_bench_success_tx_total', 'median': '{:.2}'}}",
(1000.0 * 1000.0 * (txs_processed - base_tx_count) as f64) / (total_us as f64),
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
tx_total / ITERS as u64,
);
drop(verified_sender);
@ -385,5 +300,5 @@ fn main() {
sleep(Duration::from_secs(1));
debug!("waited for poh_service");
}
let _unused = Blockstore::destroy(&ledger_path);
let _unused = Blocktree::destroy(&ledger_path);
}

View File

@ -2,37 +2,40 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "1.2.33"
version = "0.22.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
clap = "2.33.1"
itertools = "0.9.0"
bincode = "1.2.1"
bs58 = "0.3.0"
clap = "2.32.0"
env_logger = "0.7.1"
itertools = "0.8.2"
log = "0.4.8"
num-derive = "0.3"
num-traits = "0.2"
rand = "0.7.0"
rayon = "1.4.0"
serde_json = "1.0.53"
serde_yaml = "0.8.12"
solana-clap-utils = { path = "../clap-utils", version = "1.2.33" }
solana-core = { path = "../core", version = "1.2.33" }
solana-genesis = { path = "../genesis", version = "1.2.33" }
solana-client = { path = "../client", version = "1.2.33" }
solana-faucet = { path = "../faucet", version = "1.2.33" }
solana-exchange-program = { path = "../programs/exchange", version = "1.2.33" }
solana-logger = { path = "../logger", version = "1.2.33" }
solana-metrics = { path = "../metrics", version = "1.2.33" }
solana-net-utils = { path = "../net-utils", version = "1.2.33" }
solana-runtime = { path = "../runtime", version = "1.2.33" }
solana-sdk = { path = "../sdk", version = "1.2.33" }
solana-version = { path = "../version", version = "1.2.33" }
rand = "0.6.5"
rayon = "1.2.0"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-genesis = { path = "../genesis", version = "0.22.0" }
solana-client = { path = "../client", version = "0.22.0" }
solana-faucet = { path = "../faucet", version = "0.22.0" }
solana-exchange-program = { path = "../programs/exchange", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-metrics = { path = "../metrics", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
untrusted = "0.7.0"
ws = "0.9.1"
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "1.2.33" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
solana-local-cluster = { path = "../local-cluster", version = "0.22.0" }

View File

@ -14,9 +14,8 @@ use solana_metrics::datapoint_info;
use solana_sdk::{
client::{Client, SyncClient},
commitment_config::CommitmentConfig,
message::Message,
pubkey::Pubkey,
signature::{Keypair, Signer},
signature::{Keypair, KeypairUtil},
timing::{duration_as_ms, duration_as_s},
transaction::Transaction,
{system_instruction, system_program},
@ -450,7 +449,7 @@ fn swapper<T>(
}
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator, _last_valid_slot) = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
@ -458,14 +457,16 @@ fn swapper<T>(
.map(|(signer, swap, profit)| {
let s: &Keypair = &signer;
let owner = &signer.pubkey();
let instruction = exchange_instruction::swap_request(
owner,
&swap.0.pubkey,
&swap.1.pubkey,
&profit,
);
let message = Message::new(&[instruction], Some(&s.pubkey()));
Transaction::new(&[s], message, blockhash)
Transaction::new_signed_instructions(
&[s],
vec![exchange_instruction::swap_request(
owner,
&swap.0.pubkey,
&swap.1.pubkey,
&profit,
)],
blockhash,
)
})
.collect();
@ -576,7 +577,7 @@ fn trader<T>(
}
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator, _last_valid_slot) = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash");
@ -587,26 +588,28 @@ fn trader<T>(
let owner_pubkey = &owner.pubkey();
let trade_pubkey = &trade.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
let instructions = [
system_instruction::create_account(
owner_pubkey,
trade_pubkey,
1,
space,
&id(),
),
exchange_instruction::trade_request(
owner_pubkey,
trade_pubkey,
*side,
pair,
tokens,
price,
src,
),
];
let message = Message::new(&instructions, Some(&owner_pubkey));
Transaction::new(&[owner.as_ref(), trade], message, blockhash)
Transaction::new_signed_instructions(
&[owner.as_ref(), trade],
vec![
system_instruction::create_account(
owner_pubkey,
trade_pubkey,
1,
space,
&id(),
),
exchange_instruction::trade_request(
owner_pubkey,
trade_pubkey,
*side,
pair,
tokens,
price,
src,
),
],
blockhash,
)
})
.collect();
@ -698,7 +701,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
false
}
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
let total = lamports * (dests.len() as u64 + 1);
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
@ -744,19 +747,22 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
let mut to_fund_txs: Vec<_> = chunk
.par_iter()
.map(|(k, m)| {
let instructions = system_instruction::transfer_many(&k.pubkey(), &m);
let message = Message::new(&instructions, Some(&k.pubkey()));
(k.clone(), Transaction::new_unsigned(message))
(
k.clone(),
Transaction::new_unsigned_instructions(system_instruction::transfer_many(
&k.pubkey(),
&m,
)),
)
})
.collect();
let mut retries = 0;
let amount = chunk[0].1[0].1;
while !to_fund_txs.is_empty() {
let receivers: usize = to_fund_txs
let receivers = to_fund_txs
.iter()
.map(|(_, tx)| tx.message().instructions.len())
.sum();
.fold(0, |len, (_, tx)| len + tx.message().instructions.len());
debug!(
" {} to {} in {} txs",
@ -769,7 +775,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
to_fund_txs.len(),
);
let (blockhash, _fee_calculator, _last_valid_slot) = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
@ -818,11 +824,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
}
}
pub fn create_token_accounts<T: Client>(
client: &T,
signers: &[Arc<Keypair>],
accounts: &[Keypair],
) {
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Keypair]) {
let mut notfunded: Vec<(&Arc<Keypair>, &Keypair)> = signers.iter().zip(accounts).collect();
while !notfunded.is_empty() {
@ -841,18 +843,16 @@ pub fn create_token_accounts<T: Client>(
);
let request_ix =
exchange_instruction::account_request(owner_pubkey, &new_keypair.pubkey());
let message = Message::new(&[create_ix, request_ix], Some(&owner_pubkey));
(
(from_keypair, new_keypair),
Transaction::new_unsigned(message),
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
)
})
.collect();
let accounts: usize = to_create_txs
let accounts = to_create_txs
.iter()
.map(|(_, tx)| tx.message().instructions.len() / 2)
.sum();
.fold(0, |len, (_, tx)| len + tx.message().instructions.len() / 2);
debug!(
" Creating {} accounts in {} txs",
@ -862,7 +862,7 @@ pub fn create_token_accounts<T: Client>(
let mut retries = 0;
while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator, _last_valid_slot) = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash");
to_create_txs
@ -968,12 +968,7 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
rnd.gen_n_keypairs(num)
}
pub fn airdrop_lamports<T: Client>(
client: &T,
faucet_addr: &SocketAddr,
id: &Keypair,
amount: u64,
) {
pub fn airdrop_lamports(client: &dyn Client, faucet_addr: &SocketAddr, id: &Keypair, amount: u64) {
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
let balance = balance.unwrap_or(0);
if balance >= amount {
@ -991,7 +986,7 @@ pub fn airdrop_lamports<T: Client>(
let mut tries = 0;
loop {
let (blockhash, _fee_calculator, _last_valid_slot) = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash");
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {

View File

@ -1,7 +1,7 @@
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
use solana_core::gen_keys::GenKeys;
use solana_faucet::faucet::FAUCET_PORT;
use solana_sdk::signature::{read_keypair_file, Keypair};
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;

View File

@ -5,13 +5,13 @@ pub mod order_book;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::Signer;
use solana_sdk::signature::KeypairUtil;
fn main() {
solana_logger::setup();
solana_metrics::set_panic_hook("bench-exchange");
let matches = cli::build_args(solana_version::version!()).get_matches();
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
let cli_config = cli::extract_args(&matches);
let cli::Config {
@ -54,9 +54,10 @@ fn main() {
);
} else {
info!("Connecting to the cluster");
let nodes = discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let (nodes, _archivers) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let (client, num_clients) = get_multi_client(&nodes);

View File

@ -10,13 +10,12 @@ use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_config::create_genesis_config;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::process::exit;
use std::sync::mpsc::channel;
use std::time::Duration;
#[test]
#[ignore]
fn test_exchange_local_cluster() {
solana_logger::setup();
@ -59,7 +58,7 @@ fn test_exchange_local_cluster() {
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
info!("Connecting to the cluster");
let nodes =
let (nodes, _) =
discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
exit(1);
@ -86,7 +85,7 @@ fn test_exchange_bank_client() {
solana_logger::setup();
let (genesis_config, identity) = create_genesis_config(100_000_000_000_000);
let mut bank = Bank::new(&genesis_config);
bank.add_builtin_program("exchange_program", id(), process_instruction);
bank.add_instruction_processor(id(), process_instruction);
let clients = vec![BankClient::new(bank)];
let mut config = Config::default();

View File

@ -2,18 +2,14 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "1.2.33"
version = "0.22.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.1"
solana-clap-utils = { path = "../clap-utils", version = "1.2.33" }
solana-streamer = { path = "../streamer", version = "1.2.33" }
solana-logger = { path = "../logger", version = "1.2.33" }
solana-net-utils = { path = "../net-utils", version = "1.2.33" }
solana-version = { path = "../version", version = "1.2.33" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
clap = "2.33.0"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }

View File

@ -1,13 +1,14 @@
use clap::{crate_description, crate_name, App, Arg};
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
use solana_streamer::streamer::{receiver, PacketReceiver};
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
use solana_core::result::Result;
use solana_core::streamer::{receiver, PacketReceiver};
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::thread::sleep;
use std::thread::{spawn, JoinHandle, Result};
use std::thread::{spawn, JoinHandle};
use std::time::Duration;
use std::time::SystemTime;
@ -27,7 +28,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
let mut num = 0;
for p in &msgs.packets {
let a = p.meta.addr();
assert!(p.meta.size <= PACKET_DATA_SIZE);
assert!(p.meta.size < PACKET_DATA_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();
num += 1;
}
@ -52,7 +53,7 @@ fn main() -> Result<()> {
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
.version(solana_clap_utils::version!())
.arg(
Arg::with_name("num-recv-sockets")
.long("num-recv-sockets")
@ -67,8 +68,7 @@ fn main() -> Result<()> {
}
let mut port = 0;
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
let mut addr = SocketAddr::new(ip_addr, 0);
let mut addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
let exit = Arc::new(AtomicBool::new(false));
@ -76,7 +76,7 @@ fn main() -> Result<()> {
let mut read_threads = Vec::new();
let recycler = PacketsRecycler::default();
for _ in 0..num_sockets {
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
let read = solana_net_utils::bind_to(port, false).unwrap();
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
addr = read.local_addr().unwrap();

View File

@ -2,35 +2,38 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "1.2.33"
version = "0.22.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
bincode = "1.3.1"
clap = "2.33.1"
bincode = "1.2.1"
clap = "2.33.0"
log = "0.4.8"
rayon = "1.4.0"
serde_json = "1.0.53"
serde_yaml = "0.8.12"
solana-clap-utils = { path = "../clap-utils", version = "1.2.33" }
solana-core = { path = "../core", version = "1.2.33" }
solana-genesis = { path = "../genesis", version = "1.2.33" }
solana-client = { path = "../client", version = "1.2.33" }
solana-faucet = { path = "../faucet", version = "1.2.33" }
solana-logger = { path = "../logger", version = "1.2.33" }
solana-metrics = { path = "../metrics", version = "1.2.33" }
solana-measure = { path = "../measure", version = "1.2.33" }
solana-net-utils = { path = "../net-utils", version = "1.2.33" }
solana-runtime = { path = "../runtime", version = "1.2.33" }
solana-sdk = { path = "../sdk", version = "1.2.33" }
solana-version = { path = "../version", version = "1.2.33" }
rayon = "1.2.0"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-genesis = { path = "../genesis", version = "0.22.0" }
solana-client = { path = "../client", version = "0.22.0" }
solana-faucet = { path = "../faucet", version = "0.22.0" }
solana-librapay = { path = "../programs/librapay", version = "0.22.0", optional = true }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-metrics = { path = "../metrics", version = "0.22.0" }
solana-measure = { path = "../measure", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
solana-move-loader-program = { path = "../programs/move_loader", version = "0.22.0", optional = true }
[dev-dependencies]
serial_test = "0.4.0"
serial_test_derive = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "1.2.33" }
serial_test = "0.3.2"
serial_test_derive = "0.3.1"
solana-local-cluster = { path = "../local-cluster", version = "0.22.0" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[features]
move = ["solana-librapay", "solana-move-loader-program"]

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
use solana_faucet::faucet::FAUCET_PORT;
use solana_sdk::fee_calculator::FeeRateGovernor;
use solana_sdk::signature::{read_keypair_file, Keypair};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
use std::{net::SocketAddr, process::exit, time::Duration};
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::SOL_LAMPORTS;
/// Holds the configuration for a single run of the benchmark
pub struct Config {
@ -23,8 +23,8 @@ pub struct Config {
pub read_from_client_file: bool,
pub target_lamports_per_signature: u64,
pub multi_client: bool,
pub use_move: bool,
pub num_lamports_per_account: u64,
pub target_slots_per_epoch: u64,
}
impl Default for Config {
@ -43,10 +43,10 @@ impl Default for Config {
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature,
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
multi_client: true,
use_move: false,
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
target_slots_per_epoch: 0,
}
}
}
@ -107,6 +107,11 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
.long("sustained")
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
)
.arg(
Arg::with_name("use-move")
.long("use-move")
.help("Use Move language transactions to perform transfers."),
)
.arg(
Arg::with_name("no-multi-client")
.long("no-multi-client")
@ -167,15 +172,6 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
"Number of lamports per account.",
),
)
.arg(
Arg::with_name("target_slots_per_epoch")
.long("target-slots-per-epoch")
.value_name("SLOTS")
.takes_value(true)
.help(
"Wait until epochs are this many slots long.",
),
)
}
/// Parses a clap `ArgMatches` structure into a `Config`
@ -256,18 +252,12 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
}
args.use_move = matches.is_present("use-move");
args.multi_client = !matches.is_present("no-multi-client");
if let Some(v) = matches.value_of("num_lamports_per_account") {
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");
}
if let Some(t) = matches.value_of("target_slots_per_epoch") {
args.target_slots_per_epoch = t
.to_string()
.parse()
.expect("can't parse target slots per epoch");
}
args
}

View File

@ -3,19 +3,19 @@ use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate
use solana_bench_tps::cli;
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
use solana_genesis::Base64Account;
use solana_sdk::fee_calculator::FeeRateGovernor;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_program;
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit};
/// Number of signatures for all transactions in ~1 week at ~100K TPS
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
fn main() {
solana_logger::setup_with_default("solana=info");
solana_logger::setup_with_filter("solana=info");
solana_metrics::set_panic_hook("bench-tps");
let matches = cli::build_args(solana_version::version!()).get_matches();
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
let cli_config = cli::extract_args(&matches);
let cli::Config {
@ -29,6 +29,7 @@ fn main() {
write_to_client_file,
read_from_client_file,
target_lamports_per_signature,
use_move,
multi_client,
num_lamports_per_account,
..
@ -40,7 +41,7 @@ fn main() {
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
let num_accounts = keypairs.len() as u64;
let max_fee =
FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
FeeCalculator::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
/ num_accounts
+ num_lamports_per_account;
@ -66,10 +67,11 @@ fn main() {
}
info!("Connecting to the cluster");
let nodes = discover_cluster(&entrypoint_addr, *num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let (nodes, _archivers) =
discover_cluster(&entrypoint_addr, *num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let client = if *multi_client {
let (client, num_clients) = get_multi_client(&nodes);
@ -80,12 +82,12 @@ fn main() {
);
exit(1);
}
Arc::new(client)
client
} else {
Arc::new(get_client(&nodes))
get_client(&nodes)
};
let keypairs = if *read_from_client_file {
let (keypairs, move_keypairs, keypair_balance) = if *read_from_client_file && !use_move {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
@ -114,15 +116,16 @@ fn main() {
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
// This prevents the amount of storage needed for bench-tps accounts from creeping up
// across multiple runs.
keypairs.sort_by_key(|x| x.pubkey().to_string());
keypairs
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
(keypairs, None, last_balance)
} else {
generate_and_fund_keypairs(
client.clone(),
&client,
Some(*faucet_addr),
&id,
keypair_count,
*num_lamports_per_account,
*use_move,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
@ -130,5 +133,11 @@ fn main() {
})
};
do_bench_tps(client, cli_config, keypairs);
do_bench_tps(
vec![client],
cli_config,
keypairs,
keypair_balance,
move_keypairs,
);
}

View File

@ -6,11 +6,17 @@ use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
use solana_core::validator::ValidatorConfig;
use solana_faucet::faucet::run_local_faucet;
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_sdk::signature::{Keypair, Signer};
use std::sync::{mpsc::channel, Arc};
#[cfg(feature = "move")]
use solana_sdk::move_loader::solana_move_loader_program;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::mpsc::channel;
use std::time::Duration;
fn test_bench_tps_local_cluster(config: Config) {
#[cfg(feature = "move")]
let native_instruction_processors = vec![solana_move_loader_program()];
#[cfg(not(feature = "move"))]
let native_instruction_processors = vec![];
solana_logger::setup();
@ -30,10 +36,10 @@ fn test_bench_tps_local_cluster(config: Config) {
100_000_000,
);
let client = Arc::new(create_client(
let client = create_client(
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
VALIDATOR_PORT_RANGE,
));
);
let (addr_sender, addr_receiver) = channel();
run_local_faucet(faucet_keypair, addr_sender, None);
@ -42,16 +48,17 @@ fn test_bench_tps_local_cluster(config: Config) {
let lamports_per_account = 100;
let keypair_count = config.tx_count * config.keypair_multiplier;
let keypairs = generate_and_fund_keypairs(
client.clone(),
let (keypairs, move_keypairs, _keypair_balance) = generate_and_fund_keypairs(
&client,
Some(faucet_addr),
&config.id,
keypair_count,
lamports_per_account,
config.use_move,
)
.unwrap();
let _total = do_bench_tps(client, config, keypairs);
let _total = do_bench_tps(vec![client], config, keypairs, 0, move_keypairs);
#[cfg(not(debug_assertions))]
assert!(_total > 100);
@ -66,3 +73,14 @@ fn test_bench_tps_local_cluster_solana() {
test_bench_tps_local_cluster(config);
}
#[test]
#[serial]
fn test_bench_tps_local_cluster_move() {
let mut config = Config::default();
config.tx_count = 100;
config.duration = Duration::from_secs(10);
config.use_move = true;
test_bench_tps_local_cluster(config);
}

1
book/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
theme/highlight.js binary

26
book/README.md Normal file
View File

@ -0,0 +1,26 @@
Building the Solana book
---
Install the book's dependnecies, build, and test the book:
```bash
$ ./build.sh
```
Run any Rust tests in the markdown:
```bash
$ make test
```
Render markdown as HTML:
```bash
$ make build
```
Render and view the book:
```bash
$ make open
```

View File

@ -24,7 +24,7 @@ msc {
... ;
Validator abox Validator [label="\nmax\nlockout\n"];
|||;
Cluster box Cluster [label="credits redeemed (at epoch)"];
StakerX => Cluster [label="StakeState::RedeemCredits()"];
StakerY => Cluster [label="StakeState::RedeemCredits()"] ;
}

20
book/art/sdk-tools.bob Normal file
View File

@ -0,0 +1,20 @@
.----------------------------------------.
| Solana Runtime |
| |
| .------------. .------------. |
| | | | | |
.-------->| Verifier +-->| Accounts | |
| | | | | | |
.----------. | | `------------` `------------` |
| +--------` | ^ |
| Client | | LoadAccounts | |
| +--------. | .----------------` |
`----------` | | | |
| | .------+-----. .-------------. |
| | | | | | |
`-------->| Loader +-->| Interpreter | |
| | | | | |
| `------------` `-------------` |
| |
`----------------------------------------`

View File

@ -0,0 +1,18 @@
+------------+
| Bank-Merkle|
+------------+
^ ^
/ \
+-----------------+ +-------------+
| Bank-Diff-Merkle| | Block-Merkle|
+-----------------+ +-------------+
^ ^
/ \
+------+ +--------------------------+
| Hash | | Previous Bank-Diff-Merkle|
+------+ +--------------------------+
^ ^
/ \
+---------------+ +---------------+
| Hash(Account1)| | Hash(Account2)|
+---------------+ +---------------+

22
book/art/tvu.bob Normal file
View File

@ -0,0 +1,22 @@
.--------.
| Leader |
`--------`
^
|
.------------------------------------|--------------------.
| TVU | |
| | |
| .-------. .------------. .----+---. .---------. |
.------------. | | Shred | | Retransmit | | Replay | | Storage | |
| Upstream +----->| Fetch +-->| Stage +-->| Stage +-->| Stage | |
| Validators | | | Stage | | | | | | | |
`------------` | `-------` `----+-------` `----+---` `---------` |
| ^ | | |
| | | | |
`--------|----------|----------------|--------------------`
| | |
| V v
.+-----------. .------.
| Gossip | | Bank |
| Service | `------`
`------------`

View File

@ -18,9 +18,9 @@
| | `-------` `--------` `--+---------` | | | | |
| | ^ ^ | | | `------------` |
| | | | v | | |
| | | .--+---------. | | |
| | | | Blockstore | | | |
| | | `------------` | | .------------. |
| | | .--+--------. | | |
| | | | Blocktree | | | |
| | | `-----------` | | .------------. |
| | | ^ | | | | |
| | | | | | | Downstream | |
| | .--+--. .-------+---. | | | Validators | |

10
book/book.toml Normal file
View File

@ -0,0 +1,10 @@
[book]
title = "Solana: Blockchain Rebuilt for Scale"
authors = ["The Solana Team"]
[build]
build-dir = "html"
create-missing = false
[output.html]
theme = "theme"

34
book/build-cli-usage.sh Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
usage=$(cargo -q run -p solana-cli -- -C ~/.foo --help | sed 's|'"$HOME"'|~|g')
out=${1:-src/api-reference/cli.md}
cat src/api-reference/.cli.md > "$out"
section() {
declare mark=${2:-"###"}
declare section=$1
read -r name rest <<<"$section"
printf '%s %s
' "$mark" "$name"
printf '```text
%s
```
' "$section"
}
section "$usage" >> "$out"
in_subcommands=0
while read -r subcommand rest; do
[[ $subcommand == "SUBCOMMANDS:" ]] && in_subcommands=1 && continue
if ((in_subcommands)); then
section "$(cargo -q run -p solana-cli -- help "$subcommand" | sed 's|'"$HOME"'|~|g')" "####" >> "$out"
fi
done <<<"$usage">>"$out"

6
book/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
make -j"$(nproc)" test

43
book/makefile Normal file
View File

@ -0,0 +1,43 @@
BOB_SRCS=$(wildcard art/*.bob)
MSC_SRCS=$(wildcard art/*.msc)
MD_SRCS=$(wildcard src/*.md)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/.gitbook/assets/%.svg) $(MSC_SRCS:art/%.msc=src/.gitbook/assets/%.svg)
TARGET=html/index.html
TEST_STAMP=src/tests.ok
all: $(TARGET)
svg: $(SVG_IMGS)
test: $(TEST_STAMP)
open: $(TEST_STAMP)
mdbook build --open
watch: $(SVG_IMGS)
mdbook watch
src/.gitbook/assets/%.svg: art/%.bob
@mkdir -p $(@D)
svgbob < $< > $@
src/.gitbook/assets/%.svg: art/%.msc
@mkdir -p $(@D)
mscgen -T svg -i $< -o $@
src/%.md: %.md
@mkdir -p $(@D)
@cp $< $@
$(TEST_STAMP): $(TARGET)
mdbook test
touch $@
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
mdbook build
clean:
rm -f $(SVG_IMGS) src/tests.ok
rm -rf html

View File

Before

Width:  |  Height:  |  Size: 542 KiB

After

Width:  |  Height:  |  Size: 542 KiB

View File

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View File

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 269 KiB

89
book/src/SUMMARY.md Normal file
View File

@ -0,0 +1,89 @@
# Table of contents
* [Introduction](introduction.md)
* [Terminology](terminology.md)
* [Getting Started](getting-started/README.md)
* [Testnet Participation](getting-started/testnet-participation.md)
* [Example Client: Web Wallet](getting-started/webwallet.md)
* [Programming Model](programs/README.md)
* [Example: Tic-Tac-Toe](programs/tictactoe.md)
* [Drones](programs/drones.md)
* [A Solana Cluster](cluster/README.md)
* [Synchronization](cluster/synchronization.md)
* [Leader Rotation](cluster/leader-rotation.md)
* [Fork Generation](cluster/fork-generation.md)
* [Managing Forks](cluster/managing-forks.md)
* [Turbine Block Propagation](cluster/turbine-block-propagation.md)
* [Ledger Replication](cluster/ledger-replication.md)
* [Secure Vote Signing](cluster/vote-signing.md)
* [Stake Delegation and Rewards](cluster/stake-delegation-and-rewards.md)
* [Performance Metrics](cluster/performance-metrics.md)
* [Anatomy of a Validator](validator/README.md)
* [TPU](validator/tpu.md)
* [TVU](validator/tvu/README.md)
* [Blocktree](validator/tvu/blocktree.md)
* [Gossip Service](validator/gossip.md)
* [The Runtime](validator/runtime.md)
* [Anatomy of a Transaction](transaction.md)
* [Running a Validator](running-validator/README.md)
* [Validator Requirements](running-validator/validator-reqs.md)
* [Choosing a Testnet](running-validator/validator-testnet.md)
* [Installing the Validator Software](running-validator/validator-software.md)
* [Starting a Validator](running-validator/validator-start.md)
* [Staking](running-validator/validator-stake.md)
* [Monitoring a Validator](running-validator/validator-monitor.md)
* [Publishing Validator Info](running-validator/validator-info.md)
* [Troubleshooting](running-validator/validator-troubleshoot.md)
* [Running an Archiver](running-archiver.md)
* [Paper Wallet](paper-wallet/README.md)
* [Installation](paper-wallet/installation.md)
* [Paper Wallet Usage](paper-wallet/usage.md)
* [Offline Signing](offline-signing/README.md)
* [API Reference](api-reference/README.md)
* [Transaction](api-reference/transaction-api.md)
* [Instruction](api-reference/instruction-api.md)
* [Blockstreamer](api-reference/blockstreamer.md)
* [JSON RPC API](api-reference/jsonrpc-api.md)
* [JavaScript API](api-reference/javascript-api.md)
* [solana CLI](api-reference/cli.md)
* [Accepted Design Proposals](proposals/README.md)
* [Ledger Replication](proposals/ledger-replication-to-implement.md)
* [Secure Vote Signing](proposals/vote-signing-to-implement.md)
* [Cluster Test Framework](proposals/cluster-test-framework.md)
* [Validator](proposals/validator-proposal.md)
* [Simple Payment and State Verification](proposals/simple-payment-and-state-verification.md)
* [Cross-Program Invocation](proposals/cross-program-invocation.md)
* [Inter-chain Transaction Verification](proposals/interchain-transaction-verification.md)
* [Snapshot Verification](proposals/snapshot-verification.md)
* [Bankless Leader](proposals/bankless-leader.md)
* [Slashing](proposals/slashing.md)
* [Implemented Design Proposals](implemented-proposals/README.md)
* [Blocktree](implemented-proposals/blocktree.md)
* [Cluster Software Installation and Updates](implemented-proposals/installer.md)
* [Cluster Economics](implemented-proposals/ed_overview/README.md)
* [Validation-client Economics](implemented-proposals/ed_overview/ed_validation_client_economics/README.md)
* [State-validation Protocol-based Rewards](implemented-proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md)
* [State-validation Transaction Fees](implemented-proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md)
* [Replication-validation Transaction Fees](implemented-proposals/ed_overview/ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md)
* [Validation Stake Delegation](implemented-proposals/ed_overview/ed_validation_client_economics/ed_vce_validation_stake_delegation.md)
* [Replication-client Economics](implemented-proposals/ed_overview/ed_replication_client_economics/README.md)
* [Storage-replication Rewards](implemented-proposals/ed_overview/ed_replication_client_economics/ed_rce_storage_replication_rewards.md)
* [Replication-client Reward Auto-delegation](implemented-proposals/ed_overview/ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md)
* [Economic Sustainability](implemented-proposals/ed_overview/ed_economic_sustainability.md)
* [Attack Vectors](implemented-proposals/ed_overview/ed_attack_vectors.md)
* [Economic Design MVP](implemented-proposals/ed_overview/ed_mvp.md)
* [References](implemented-proposals/ed_overview/ed_references.md)
* [Deterministic Transaction Fees](implemented-proposals/transaction-fees.md)
* [Tower BFT](implemented-proposals/tower-bft.md)
* [Leader-to-Leader Transition](implemented-proposals/leader-leader-transition.md)
* [Leader-to-Validator Transition](implemented-proposals/leader-validator-transition.md)
* [Persistent Account Storage](implemented-proposals/persistent-account-storage.md)
* [Reliable Vote Transmission](implemented-proposals/reliable-vote-transmission.md)
* [Repair Service](implemented-proposals/repair-service.md)
* [Testing Programs](implemented-proposals/testing-programs.md)
* [Credit-only Accounts](implemented-proposals/readonly-accounts.md)
* [Embedding the Move Langauge](implemented-proposals/embedding-move.md)
* [Staking Rewards](implemented-proposals/staking-rewards.md)
* [Rent](implemented-proposals/rent.md)
* [Durable Transaction Nonces](implemented-proposals/durable-tx-nonces.md)
* [Validator Timestamp Oracle](implemented-proposals/validator-timestamp-oracle.md)

View File

@ -1,6 +1,4 @@
---
title: CLI Usage Reference
---
# solana CLI
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
@ -10,7 +8,7 @@ The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-l
```bash
// Command
$ solana-keygen pubkey
$ solana address
// Return
<PUBKEY>
@ -24,6 +22,12 @@ $ solana airdrop 2
// Return
"2.00000000 SOL"
// Command
$ solana airdrop 123 --lamports
// Return
"123 lamports"
```
### Get Balance

View File

@ -0,0 +1,4 @@
# API Reference
The following sections contain API references material you may find useful when developing applications utilizing a Solana cluster.

View File

@ -0,0 +1,28 @@
# Blockstreamer
Solana supports a node type called an _blockstreamer_. This validator variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
A blockstreamer runs without a vote signer, and can optionally stream ledger entries out to a Unix domain socket as they are processed. The JSON-RPC service still functions as on any other node.
To run a blockstreamer, include the argument `no-signer` and \(optional\) `blockstream` socket location:
```bash
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
```
The stream will output a series of JSON objects:
* An Entry event JSON object is sent when each ledger entry is processed, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "entry"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `entry`, the entry, as JSON object
* A Block event JSON object is sent when a block is complete, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "block"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `l`, the slot leader id, as base-58 encoded string
* `hash`, the [blockhash](terminology.md#blockhash), as base-58 encoded string

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
# Instruction
For the purposes of building a [Transaction](../transaction.md), a more verbose instruction format is used:
* **Instruction:**
* **program\_id:** The pubkey of the on-chain program that executes the
instruction
* **accounts:** An ordered list of accounts that should be passed to
the program processing the instruction, including metadata detailing
if an account is a signer of the transaction and if it is a credit
only account.
* **data:** A byte array that is passed to the program executing the
instruction
A more compact form is actually included in a `Transaction`:
* **CompiledInstruction:**
* **program\_id\_index:** The index of the `program_id` in the
`account_keys` list
* **accounts:** An ordered list of indices into `account_keys`
specifying the accounds that should be passed to the program
processing the instruction.
* **data:** A byte array that is passed to the program executing the
instruction

View File

@ -1,5 +1,4 @@
---
title: JavaScript API
---
# JavaScript API
See [solana-web3](https://solana-labs.github.io/solana-web3.js/).

View File

@ -0,0 +1,994 @@
# JSON RPC API
Solana nodes accept HTTP requests using the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
To interact with a Solana node inside a JavaScript application, use the [solana-web3.js](https://github.com/solana-labs/solana-web3.js) library, which gives a convenient interface for the RPC methods.
## RPC HTTP Endpoint
**Default port:** 8899 eg. [http://localhost:8899](http://localhost:8899), [http://192.168.1.88:8899](http://192.168.1.88:8899)
## RPC PubSub WebSocket Endpoint
**Default port:** 8900 eg. ws://localhost:8900, [http://192.168.1.88:8900](http://192.168.1.88:8900)
## Methods
* [confirmTransaction](jsonrpc-api.md#confirmtransaction)
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
* [getBalance](jsonrpc-api.md#getbalance)
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
* [getBlockTime](jsonrpc-api.md#getblocktime)
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
* [getConfirmedBlocks](jsonrpc-api.md#getconfirmedblocks)
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
* [getEpochSchedule](jsonrpc-api.md#getepochschedule)
* [getGenesisHash](jsonrpc-api.md#getgenesishash)
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption)
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
* [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash)
* [getSignatureStatus](jsonrpc-api.md#getsignaturestatus)
* [getSlot](jsonrpc-api.md#getslot)
* [getSlotLeader](jsonrpc-api.md#getslotleader)
* [getSlotsPerSegment](jsonrpc-api.md#getslotspersegment)
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
* [getTotalSupply](jsonrpc-api.md#gettotalsupply)
* [getVersion](jsonrpc-api.md#getversion)
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
* [sendTransaction](jsonrpc-api.md#sendtransaction)
* [startSubscriptionChannel](jsonrpc-api.md#startsubscriptionchannel)
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
* [accountSubscribe](jsonrpc-api.md#accountsubscribe)
* [accountUnsubscribe](jsonrpc-api.md#accountunsubscribe)
* [programSubscribe](jsonrpc-api.md#programsubscribe)
* [programUnsubscribe](jsonrpc-api.md#programunsubscribe)
* [signatureSubscribe](jsonrpc-api.md#signaturesubscribe)
* [signatureUnsubscribe](jsonrpc-api.md#signatureunsubscribe)
## Request Formatting
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
* `jsonrpc`, set to `"2.0"`
* `id`, a unique client-generated identifying integer
* `method`, a string containing the method to be invoked
* `params`, a JSON array of ordered parameter values
Example using curl:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
```
The response output will be a JSON object with the following fields:
* `jsonrpc`, matching the request specification
* `id`, matching the request identifier
* `result`, requested data or success confirmation
Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
## Definitions
* Hash: A SHA-256 hash of a chunk of data.
* Pubkey: The public key of a Ed25519 key-pair.
* Signature: An Ed25519 signature of a chunk of data.
* Transaction: A Solana instruction signed by a client key-pair.
## Configuring State Commitment
Solana nodes choose which bank state to query based on a commitment requirement
set by the client. Clients may specify either:
* `{"commitment":"max"}` - the node will query the most recent bank having reached `MAX_LOCKOUT_HISTORY` confirmations
* `{"commitment":"recent"}` - the node will query its most recent bank state
The commitment parameter should be included as the last element in the `params` array:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri",{"commitment":"max"}]}' 192.168.1.88:8899
```
#### Default:
If commitment configuration is not provided, the node will default to `"commitment":"max"`
Only methods that query bank state accept the commitment parameter. They are indicated in the API Reference below.
#### RpcResponse Structure
Many methods that take a commitment parameter return an RpcResponse JSON object comprised of two parts:
* `context` : An RpcResponseContext JSON structure including a `slot` field at which the operation was evaluated.
* `value` : The value returned by the operation itself.
## JSON RPC API Reference
### confirmTransaction
Returns a transaction receipt
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<boolean>` - RpcResponse JSON object with `value` field set to Transaction status, boolean true if Transaction is confirmed
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":true},"id":1}
```
### getAccountInfo
Returns all information associated with the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result value will be an RpcResponse JSON object containing an AccountInfo JSON object.
* `RpcResponse<AccountInfo>`, RpcResponse JSON object with `value` field set to AccountInfo, a JSON object containing:
* `lamports`, number of lamports assigned to this account, as a u64
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]}},"id":1}
```
### getBalance
Returns the balance of the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<u64>` - RpcResponse JSON object with `value` field set to quantity
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":0},"id":1}
```
### getBlockCommitment
Returns commitment for particular block
#### Parameters:
* `u64` - block, identified by Slot
#### Results:
The result field will be an array with two fields:
* Commitment
* `null` - Unknown block
* `object` - BlockCommitment
* `array` - commitment, array of u64 integers logging the amount of cluster stake in lamports that has voted on the block at each depth from 0 to `MAX_LOCKOUT_HISTORY`
* 'integer' - total active stake, in lamports, of the current epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockCommitment","params":[5]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"commitment":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,32]},42],"id":1}
```
### getBlockTime
Returns the estimated production time of a block. Validators report their UTC
time to the ledger on a regular interval. A block's time is calculated as an
offset from the median value of the most recent validator time report.
#### Parameters:
* `u64` - block, identified by Slot
#### Results:
* `null` - block has not yet been produced
* `i64` - estimated production time, as Unix timestamp (seconds since the Unix epoch)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockTime","params":[5]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":1574721591,"id":1}
```
### getClusterNodes
Returns information about all the nodes participating in the cluster
#### Parameters:
None
#### Results:
The result field will be an array of JSON objects, each with the following sub fields:
* `pubkey` - Node public key, as base-58 encoded string
* `gossip` - Gossip network address for the node
* `tpu` - TPU network address for the node
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
### getConfirmedBlock
Returns identity and transaction information about a confirmed block in the ledger
#### Parameters:
* `integer` - slot, as u64 integer
#### Results:
The result field will be an object with the following fields:
* `blockhash` - the blockhash of this block
* `previousBlockhash` - the blockhash of this block's parent
* `parentSlot` - the slot index of this block's parent
* `transactions` - an array of tuples containing:
* [Transaction](transaction-api.md) object, in JSON format
* Transaction status object, containing:
* `status` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* `fee` - fee this transaction was charged, as u64 integer
* `preBalances` - array of u64 account balances from before the transaction was processed
* `postBalances` - array of u64 account balances after the transaction was processed
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430]}' localhost:8899
// Result
{"jsonrpc":"2.0","result":{"blockhash":[165,245,120,183,32,205,89,222,249,114,229,49,250,231,149,122,156,232,181,83,238,194,157,153,7,213,180,54,177,6,25,101],"parentSlot":429,"previousBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166],"transactions":[[{"message":{"accountKeys":[[5],[219,181,202,40,52,148,34,136,186,59,137,160,250,225,234,17,244,160,88,116,24,176,30,227,68,11,199,38,141,68,131,228],[233,48,179,56,91,40,254,206,53,48,196,176,119,248,158,109,121,77,11,69,108,160,128,27,228,122,146,249,53,184,68,87],[6,167,213,23,25,47,10,175,198,242,101,227,251,119,204,122,218,130,197,41,208,190,59,19,110,45,0,85,32,0,0,0],[6,167,213,23,24,199,116,201,40,86,99,152,105,29,94,182,139,94,184,163,155,75,109,92,115,85,91,33,0,0,0,0],[7,97,72,29,53,116,116,187,124,77,118,36,235,211,189,179,216,53,94,115,209,16,67,252,13,163,83,128,0,0,0,0]],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":2},"instructions":[[1],{"accounts":[[3],1,2,3],"data":[[52],2,0,0,0,1,0,0,0,0,0,0,0,173,1,0,0,0,0,0,0,86,55,9,248,142,238,135,114,103,83,247,124,67,68,163,233,55,41,59,129,64,50,110,221,234,234,27,213,205,193,219,50],"program_id_index":4}],"recentBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166]},"signatures":[[2],[119,9,95,108,35,95,7,1,69,101,65,45,5,204,61,114,172,88,123,238,32,201,135,229,57,50,13,21,106,216,129,183,238,43,37,101,148,81,56,232,88,136,80,65,46,189,39,106,94,13,238,54,186,48,118,186,0,62,121,122,172,171,66,5],[78,40,77,250,10,93,6,157,48,173,100,40,251,9,7,218,7,184,43,169,76,240,254,34,235,48,41,175,119,126,75,107,106,248,45,161,119,48,174,213,57,69,111,225,245,60,148,73,124,82,53,6,203,126,120,180,111,169,89,64,29,23,237,13]]},{"fee":100000,"status":{"Ok":null},"preBalances":[499998337500,15298080,1,1,1],"postBalances":[499998237500,15298080,1,1,1]}]]},"id":1}
```
### getConfirmedBlocks
Returns a list of confirmed blocks
#### Parameters:
* `integer` - start_slot, as u64 integer
* `integer` - (optional) end_slot, as u64 integer
#### Results:
The result field will be an array of u64 integers listing confirmed blocks
between start_slot and either end_slot, if provided, or latest confirmed block,
inclusive.
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlocks","params":[5, 10]}' localhost:8899
// Result
{"jsonrpc":"2.0","result":[5,6,7,8,9,10],"id":1}
```
### getEpochInfo
Returns information about the current epoch
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be an object with the following fields:
* `epoch`, the current epoch
* `slotIndex`, the current slot relative to the start of the current epoch
* `slotsInEpoch`, the number of slots in this epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
```
### getEpochSchedule
Returns epoch schedule information from this cluster's genesis config
#### Parameters:
None
#### Results:
The result field will be an object with the following fields:
* `slots_per_epoch`, the maximum number of slots in each epoch
* `leader_schedule_slot_offset`, the number of slots before beginning of an epoch to calculate a leader schedule for that epoch
* `warmup`, whether epochs start short and grow
* `first_normal_epoch`, first normal-length epoch, log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)
* `first_normal_slot`, MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochSchedule"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"first_normal_epoch":8,"first_normal_slot":8160,"leader_schedule_slot_offset":8192,"slots_per_epoch":8192,"warmup":true},"id":1}
```
### getGenesisHash
Returns the genesis hash
#### Parameters:
None
#### Results:
* `string` - a Hash as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisHash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
### getLeaderSchedule
Returns the leader schedule for an epoch
#### Parameters:
* `slot` - (optional) Fetch the leader schedule for the epoch that corresponds to the provided slot. If unspecified, the leader schedule for the current epoch is fetched
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be a dictionary of leader public keys \(as base-58 encoded
strings\) and their corresponding leader slot indices as values (indices are to
the first slot in the requested epoch)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63]},"id":1}
```
### getMinimumBalanceForRentExemption
Returns minimum balance required to make account rent exempt.
#### Parameters:
* `u64` - account data length
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - minimum lamports required in account
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getMinimumBalanceForRentExemption", "params":[50]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":500,"id":1}
```
### getNumBlocksSinceSignatureConfirmation
Returns the current number of blocks since signature has been confirmed.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - count
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":8,"id":1}
```
### getProgramAccounts
Returns all accounts owned by the provided program Pubkey
#### Parameters:
* `string` - Pubkey of program, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be an array of arrays. Each sub array will contain:
* `string` - the account Pubkey as base-58 encoded string and a JSON object, with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a u64
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
```
### getRecentBlockhash
Returns a recent block hash from the ledger, and a fee schedule that can be used to compute the cost of submitting a transaction using it.
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
An RpcResponse containing an array consisting of a string blockhash and FeeCalculator JSON object.
* `RpcResponse<array>` - RpcResponse JSON object with `value` field set to an array including:
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}]},"id":1}
```
### getSignatureStatus
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `null` - Unknown transaction
* `object` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
```
### getSlot
Returns the current slot the node is processing
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Current slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1234","id":1}
```
### getSlotLeader
Returns the current slot leader
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `string` - Node Id as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
```
### getSlotsPerSegment
Returns the current storage segment size in terms of slots
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Number of slots in a storage segment
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getStorageTurn
Returns the current storage turn's blockhash and slot
#### Parameters:
None
#### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
* `u64` - the current storage turn slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
```
### getStorageTurnRate
Returns the current storage turn rate in terms of slots per turn
#### Parameters:
None
#### Results:
* `u64` - Number of slots in storage turn
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getTransactionCount
Returns the current Transaction count from the ledger
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - count
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":268,"id":1}
```
### getTotalSupply
Returns the current total supply in Lamports
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Total supply
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":10126,"id":1}
```
### getVersion
Returns the current solana versions running on the node
#### Parameters:
None
#### Results:
The result field will be a JSON object with the following sub fields:
* `solana-core`, software version of solana-core
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
```
### getVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current bank.
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be a JSON object of `current` and `delinquent` accounts, each containing an array of JSON objects with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
* `commission`, percentage (0-100) of rewards payout owed to the vote account
* `lastVote` - Most recent slot voted on by this vote account
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
```
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
#### Parameters:
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
* `integer` - lamports, as a u64
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) (used for retrieving blockhash and verifying airdrop success)
#### Results:
* `string` - Transaction Signature of airdrop, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
```
### sendTransaction
Creates new transaction
#### Parameters:
* `array` - array of octets containing a fully-signed Transaction
#### Results:
* `string` - Transaction Signature, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
```
### Subscription Websocket
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
* Submit subscription requests to the websocket using the methods below
* Multiple subscriptions may be active at once
* All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
### accountSubscribe
Subscribe to an account to receive notifications when the lamports or data for a given account public key changes
#### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
### accountUnsubscribe
Unsubscribe from account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### programSubscribe
Subscribe to a program to receive notifications when the lamports or data for a given account owned by the program changes
#### Parameters:
* `string` - program\_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
* `string` - account Pubkey, as base-58 encoded string
* `object` - account info JSON object \(see [getAccountInfo](jsonrpc-api.md#getaccountinfo) for field details\)
```bash
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
```
### programUnsubscribe
Unsubscribe from program-owned account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### signatureSubscribe
Subscribe to a transaction signature to receive notification when the transaction is confirmed On `signatureNotification`, the subscription is automatically cancelled
#### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
```
### signatureUnsubscribe
Unsubscribe from signature confirmation notification
#### Parameters:
* `integer` - subscription id to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```

View File

@ -0,0 +1,62 @@
# Transaction
## Components of a `Transaction`
* **Transaction:**
* **message:** Defines the transaction
* **header:** Details the account types of and signatures required by
the transaction
* **num\_required\_signatures:** The total number of signatures
required to make the transaction valid.
* **num\_credit\_only\_signed\_accounts:** The last
`num_readonly_signed_accounts` signatures refer to signing
credit only accounts. Credit only accounts can be used concurrently
by multiple parallel transactions, but their balance may only be
increased, and their account data is read-only.
* **num\_credit\_only\_unsigned\_accounts:** The last
`num_readonly_unsigned_accounts` public keys in `account_keys` refer
to non-signing credit only accounts
* **account\_keys:** List of public keys used by the transaction, including
by the instructions and for signatures. The first
`num_required_signatures` public keys must sign the transaction.
* **recent\_blockhash:** The ID of a recent ledger entry. Validators will
reject transactions with a `recent_blockhash` that is too old.
* **instructions:** A list of [instructions](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/instruction.md) that are
run sequentially and committed in one atomic transaction if all
succeed.
* **signatures:** A list of signatures applied to the transaction. The
list is always of length `num_required_signatures`, and the signature
at index `i` corresponds to the public key at index `i` in `account_keys`.
The list is initialized with empty signatures \(i.e. zeros\), and
populated as signatures are added.
## Transaction Signing
A `Transaction` is signed by using an ed25519 keypair to sign the serialization of the `message`. The resulting signature is placed at the index of `signatures` matching the index of the keypair's public key in `account_keys`.
## Transaction Serialization
`Transaction`s \(and their `message`s\) are serialized and deserialized using the [bincode](https://crates.io/crates/bincode) crate with a non-standard vector serialization that uses only one byte for the length if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3 bytes if it requires 15 or 16 bits. The vector serialization is defined by Solana's [short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).

View File

@ -1,12 +1,10 @@
---
title: Block Confirmation
---
# Block Confirmation
A validator votes on a PoH hash for two purposes. First, the vote indicates it
believes the ledger is valid up until that point in time. Second, since many
valid forks may exist at a given height, the vote also indicates exclusive
support for the fork. This document describes only the former. The latter is
described in [Tower BFT](../implemented-proposals/tower-bft.md).
described in [Tower BFT](tower-bft.md).
## Current Design
@ -16,16 +14,16 @@ height of the block it is voting on. The account stores the 32 highest heights.
### Problems
- Only the validator knows how to find its own votes directly.
* Only the validator knows how to find its own votes directly.
Other components, such as the one that calculates confirmation time, needs to
be baked into the validator code. The validator code queries the bank for all
accounts owned by the vote program.
- Voting ballots do not contain a PoH hash. The validator is only voting that
* Voting ballots do not contain a PoH hash. The validator is only voting that
it has observed an arbitrary block at some height.
- Voting ballots do not contain a hash of the bank state. Without that hash,
* Voting ballots do not contain a hash of the bank state. Without that hash,
there is no evidence that the validator executed the transactions and
verified there were no double spends.
@ -52,8 +50,8 @@ log the time since the NewBlock transaction was submitted.
### Finality and Payouts
[Tower BFT](../implemented-proposals/tower-bft.md) is the proposed fork selection algorithm. It proposes
that payment to miners be postponed until the _stack_ of validator votes reaches
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
that payment to miners be postponed until the *stack* of validator votes reaches
a certain depth, at which point rollback is not economically feasible. The vote
program may therefore implement Tower BFT. Vote instructions would need to
reference a global Tower account so that it can track cross-block state.
@ -64,7 +62,7 @@ reference a global Tower account so that it can track cross-block state.
Using programs and accounts to implement this is a bit tedious. The hardest
part is figuring out how much space to allocate in NewBlock. The two variables
are the _active set_ and the stakes of those validators. If we calculate the
are the *active set* and the stakes of those validators. If we calculate the
active set at the time NewBlock is submitted, the number of validators to
allocate space for is known upfront. If, however, we allow new validators to
vote on old blocks, then we'd need a way to allocate space dynamically.

View File

@ -1,22 +1,20 @@
---
title: A Solana Cluster
---
# A Solana Cluster
A Solana cluster is a set of validators working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this section, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
A Solana cluster is a set of validators working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this chapter, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
## Creating a Cluster
Before starting any validators, one first needs to create a _genesis config_. The config references two public keys, a _mint_ and a _bootstrap validator_. The validator holding the bootstrap validator's private key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis config. The second validator then contacts the bootstrap validator to register as a _validator_. Additional validators then register with any registered member of the cluster.
Before starting any validators, one first needs to create a _genesis config_. The config references two public keys, a _mint_ and a _bootstrap leader_. The validator holding the bootstrap leader's private key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis config. The second validator then contacts the bootstrap leader to register as a _validator_ or _archiver_. Additional validators then register with any registered member of the cluster.
A validator receives all entries from the leader and submits votes confirming those entries are valid. After voting, the validator is expected to store those entries. Once the validator observes a sufficient number of copies exist, it deletes its copy.
A validator receives all entries from the leader and submits votes confirming those entries are valid. After voting, the validator is expected to store those entries until archiver nodes submit proofs that they have stored copies of it. Once the validator observes a sufficient number of copies exist, it deletes its copy.
## Joining a Cluster
Validators enter the cluster via registration messages sent to its _control plane_. The control plane is implemented using a _gossip_ protocol, meaning that a node may register with any existing node, and expect its registration to propagate to all nodes in the cluster. The time it takes for all nodes to synchronize is proportional to the square of the number of nodes participating in the cluster. Algorithmically, that's considered very slow, but in exchange for that time, a node is assured that it eventually has all the same information as every other node, and that that information cannot be censored by any one node.
Validators and archivers enter the cluster via registration messages sent to its _control plane_. The control plane is implemented using a _gossip_ protocol, meaning that a node may register with any existing node, and expect its registration to propagate to all nodes in the cluster. The time it takes for all nodes to synchronize is proportional to the square of the number of nodes participating in the cluster. Algorithmically, that's considered very slow, but in exchange for that time, a node is assured that it eventually has all the same information as every other node, and that that information cannot be censored by any one node.
## Sending Transactions to a Cluster
Clients send transactions to any validator's Transaction Processing Unit \(TPU\) port. If the node is in the validator role, it forwards the transaction to the designated leader. If in the leader role, the node bundles incoming transactions, timestamps them creating an _entry_, and pushes them onto the cluster's _data plane_. Once on the data plane, the transactions are validated by validator nodes, effectively appending them to the ledger.
Clients send transactions to any validator's Transaction Processing Unit \(TPU\) port. If the node is in the validator role, it forwards the transaction to the designated leader. If in the leader role, the node bundles incoming transactions, timestamps them creating an _entry_, and pushes them onto the cluster's _data plane_. Once on the data plane, the transactions are validated by validator nodes and replicated by archiver nodes, effectively appending them to the ledger.
## Confirming Transactions
@ -39,4 +37,4 @@ Solana rotates leaders at fixed intervals, called _slots_. Each leader may only
Next, transactions are broken into batches so that a node can send transactions to multiple parties without making multiple copies. If, for example, the leader needed to send 60 transactions to 6 nodes, it would break that collection of 60 into batches of 10 transactions and send one to each node. This allows the leader to put 60 transactions on the wire, not 60 transactions for each node. Each node then shares its batch with its peers. Once the node has collected all 6 batches, it reconstructs the original set of 60 transactions.
A batch of transactions can only be split so many times before it is so small that header information becomes the primary consumer of network bandwidth. At the time of this writing, the approach is scaling well up to about 150 validators. To scale up to hundreds of thousands of validators, each node can apply the same technique as the leader node to another set of nodes of equal size. We call the technique [_Turbine Block Propogation_](turbine-block-propagation.md).
A batch of transactions can only be split so many times before it is so small that header information becomes the primary consumer of network bandwidth. At the time of this writing, the approach is scaling well up to about 150 validators. To scale up to hundreds of thousands of validators, each node can apply the same technique as the leader node to another set of nodes of equal size. We call the technique _data plane fanout_; learn more in the [data plan fanout](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/data-plane-fanout.md) section.

View File

@ -1,8 +1,6 @@
---
title: Fork Generation
---
# Fork Generation
This section describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
The chapter describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
## Overview
@ -60,7 +58,7 @@ Validators vote based on a greedy choice to maximize their reward described in [
The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and `E`s represent entries from that leader during that leader's slot. The `x`s represent ticks only, and time flows downwards in the diagram.
![Fork generation](/img/fork-generation.svg)
![Fork generation](../.gitbook/assets/fork-generation.svg)
Note that an `E` appearing on 2 forks at the same slot is a slashable condition, so a validator observing `E3` and `E3'` can slash L3 and safely choose `x` for that slot. Once a validator commits to a forks, other forks can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain to be proposed by a leader. But multiple virtual entries may overlap as they link back to the a previous slot.
@ -68,10 +66,10 @@ Note that an `E` appearing on 2 forks at the same slot is a slashable condition,
It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the cluster. The following table presents the above tree of forks as a time-divided ledger.
| leader slot | L1 | L2 | L3 | L4 | L5 |
| :--------------- | :-- | :-- | :-- | :-- | :-- |
| data | E1 | E2 | E3 | E4 | E5 |
| ticks since prev | | | | x | xx |
| leader slot | L1 | L2 | L3 | L4 | L5 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| data | E1 | E2 | E3 | E4 | E5 |
| ticks since prev | | | | x | xx |
Note that only data from leader L3 will be accepted during leader slot L3. Data from L3 may include "catchup" ticks back to a slot other than L2 if L3 did not observe L2's data. L4 and L5's transmissions include the "ticks to prev" PoH entries.

View File

@ -1,6 +1,4 @@
---
title: Leader Rotation
---
# Leader Rotation
At any given moment, a cluster expects only one validator to produce ledger entries. By having only one leader at a time, all validators are able to replay identical copies of the ledger. The drawback of only one leader at a time, however, is that a malicious leader is capable of censoring votes and transactions. Since censoring cannot be distinguished from the network dropping packets, the cluster cannot simply elect a single node to hold the leader role indefinitely. Instead, the cluster minimizes the influence of a malicious leader by rotating which node takes the lead.
@ -33,8 +31,8 @@ Two partitions that are generating half of the blocks each. Neither is coming to
In this unstable scenario, multiple valid leader schedules exist.
- A leader schedule is generated for every fork whose direct parent is in the previous epoch.
- The leader schedule is valid after the start of the next epoch for descendant forks until it is updated.
* A leader schedule is generated for every fork whose direct parent is in the previous epoch.
* The leader schedule is valid after the start of the next epoch for descendant forks until it is updated.
Each partition's schedule will diverge after the partition lasts more than an epoch. For this reason, the epoch duration should be selected to be much much larger then slot time and the expected length for a fork to be committed to root.
@ -75,8 +73,8 @@ The seed that is selected is predictable but unbiasable. There is no grinding at
A leader can bias the active set by censoring validator votes. Two possible ways exist for leaders to censor the active set:
- Ignore votes from validators
- Refuse to vote for blocks with votes from validators
* Ignore votes from validators
* Refuse to vote for blocks with votes from validators
To reduce the likelihood of censorship, the active set is calculated at the leader schedule offset boundary over an _active set sampling duration_. The active set sampling duration is long enough such that votes will have been collected by multiple leaders.

View File

@ -1,16 +1,4 @@
---
title: Ledger Replication
---
Note: this ledger replication solution was partially implemented, but not
completed. The partial implementation was removed by
https://github.com/solana-labs/solana/pull/9992 in order to prevent the security
risk of unused code. The first part of this design document reflects the
once-implemented parts of ledger replication. The
[second part of this document](#ledger-replication-not-implemented) describes the
parts of the solution never implemented.
## Proof of Replication
# Ledger Replication
At full capacity on a 1gbps network solana will generate 4 petabytes of data per year. To prevent the network from centralizing around validators that have to store the full data set this protocol proposes a way for mining nodes to provide storage capacity for pieces of the data.
@ -30,7 +18,7 @@ Archivers are specialized _light clients_. They download a part of the ledger \(
We have the following constraints:
- Verification requires generating the CBC blocks. That requires space of 2
* Verification requires generating the CBC blocks. That requires space of 2
blocks per identity, and 1 CUDA core per identity for the same dataset. So as
@ -38,7 +26,7 @@ We have the following constraints:
identities verified concurrently for the same dataset.
- Validators will randomly sample the set of storage proofs to the set that
* Validators will randomly sample the set of storage proofs to the set that
they can handle, and only the creators of those chosen proofs will be
@ -50,31 +38,31 @@ We have the following constraints:
### Constants
1. SLOTS_PER_SEGMENT: Number of slots in a segment of ledger data. The
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
unit of storage for an archiver.
2. NUM_KEY_ROTATION_SEGMENTS: Number of segments after which archivers
2. NUM\_KEY\_ROTATION\_SEGMENTS: Number of segments after which archivers
regenerate their encryption keys and select a new dataset to store.
3. NUM_STORAGE_PROOFS: Number of storage proofs required for a storage proof
3. NUM\_STORAGE\_PROOFS: Number of storage proofs required for a storage proof
claim to be successfully rewarded.
4. RATIO_OF_FAKE_PROOFS: Ratio of fake proofs to real proofs that a storage
4. RATIO\_OF\_FAKE\_PROOFS: Ratio of fake proofs to real proofs that a storage
mining proof claim has to contain to be valid for a reward.
5. NUM_STORAGE_SAMPLES: Number of samples required for a storage mining
5. NUM\_STORAGE\_SAMPLES: Number of samples required for a storage mining
proof.
6. NUM_CHACHA_ROUNDS: Number of encryption rounds performed to generate
6. NUM\_CHACHA\_ROUNDS: Number of encryption rounds performed to generate
encrypted state.
7. NUM_SLOTS_PER_TURN: Number of slots that define a single storage epoch or
7. NUM\_SLOTS\_PER\_TURN: Number of slots that define a single storage epoch or
a "turn" of the PoRep game.
@ -116,14 +104,14 @@ We have the following constraints:
depending on how paranoid an archiver is:
- \(a\) archiver can ask a validator
- \(b\) archiver can ask multiple validators
- \(c\) archiver can ask other archivers
- \(d\) archiver can subscribe to the full transaction stream and generate
* \(a\) archiver can ask a validator
* \(b\) archiver can ask multiple validators
* \(c\) archiver can ask other archivers
* \(d\) archiver can subscribe to the full transaction stream and generate
the information itself \(assuming the slot is recent enough\)
- \(e\) archiver can subscribe to an abbreviated transaction stream to
* \(e\) archiver can subscribe to an abbreviated transaction stream to
generate the information itself \(assuming the slot is recent enough\)
@ -183,17 +171,17 @@ The Proof of Replication game has 4 primary stages. For each "turn" multiple PoR
The 4 stages of the PoRep Game are as follows:
1. Proof submission stage
- Archivers: submit as many proofs as possible during this stage
- Validators: No-op
* Archivers: submit as many proofs as possible during this stage
* Validators: No-op
2. Proof verification stage
- Archivers: No-op
- Validators: Select archivers and verify their proofs from the previous turn
* Archivers: No-op
* Validators: Select archivers and verify their proofs from the previous turn
3. Proof challenge stage
- Archivers: Submit the proof mask with justifications \(for fake proofs submitted 2 turns ago\)
- Validators: No-op
* Archivers: Submit the proof mask with justifications \(for fake proofs submitted 2 turns ago\)
* Validators: No-op
4. Reward collection stage
- Archivers: Collect rewards for 3 turns ago
- Validators: Collect rewards for 3 turns ago
* Archivers: Collect rewards for 3 turns ago
* Validators: Collect rewards for 3 turns ago
For each turn of the PoRep game, both Validators and Archivers evaluate each stage. The stages are run as separate transactions on the storage program.
@ -209,7 +197,7 @@ For each turn of the PoRep game, both Validators and Archivers evaluate each sta
The validator provides an RPC interface to access the this map. Using this API, clients
can map a segment to an archiver's network address \(correlating it via cluster_info table\).
can map a segment to an archiver's network address \(correlating it via cluster\_info table\).
The clients can then send repair requests to the archiver to retrieve segments.
@ -225,17 +213,17 @@ Our solution to this is to force the clients to continue using the same identity
## Validator attacks
- If a validator approves fake proofs, archiver can easily out them by
* If a validator approves fake proofs, archiver can easily out them by
showing the initial state for the hash.
- If a validator marks real proofs as fake, no on-chain computation can be done
* If a validator marks real proofs as fake, no on-chain computation can be done
to distinguish who is correct. Rewards would have to rely on the results from
multiple validators to catch bad actors and archivers from being denied rewards.
- Validator stealing mining proof results for itself. The proofs are derived
* Validator stealing mining proof results for itself. The proofs are derived
from a signature from an archiver, since the validator does not know the
@ -251,167 +239,31 @@ Some percentage of fake proofs are also necessary to receive a reward from stora
## Notes
- We can reduce the costs of verification of PoRep by using PoH, and actually
* We can reduce the costs of verification of PoRep by using PoH, and actually
make it feasible to verify a large number of proofs for a global dataset.
- We can eliminate grinding by forcing everyone to sign the same PoH hash and
* We can eliminate grinding by forcing everyone to sign the same PoH hash and
use the signatures as the seed
- The game between validators and archivers is over random blocks and random
* The game between validators and archivers is over random blocks and random
encryption identities and random data samples. The goal of randomization is
to prevent colluding groups from having overlap on data or validation.
- Archiver clients fish for lazy validators by submitting fake proofs that
* Archiver clients fish for lazy validators by submitting fake proofs that
they can prove are fake.
- To defend against Sybil client identities that try to store the same block we
* To defend against Sybil client identities that try to store the same block we
force the clients to store for multiple rounds before receiving a reward.
- Validators should also get rewarded for validating submitted storage proofs
* Validators should also get rewarded for validating submitted storage proofs
as incentive for storing the ledger. They can only validate proofs if they
are storing that slice of the ledger.
# Ledger Replication Not Implemented
Replication behavior yet to be implemented.
## Storage epoch
The storage epoch should be the number of slots which results in around 100GB-1TB of ledger to be generated for archivers to store. Archivers will start storing ledger when a given fork has a high probability of not being rolled back.
## Validator behavior
1. Every NUM_KEY_ROTATION_TICKS it also validates samples received from
archivers. It signs the PoH hash at that point and uses the following
algorithm with the signature as the input:
- The low 5 bits of the first byte of the signature creates an index into
another starting byte of the signature.
- The validator then looks at the set of storage proofs where the byte of
the proof's sha state vector starting from the low byte matches exactly
with the chosen byte\(s\) of the signature.
- If the set of proofs is larger than the validator can handle, then it
increases to matching 2 bytes in the signature.
- Validator continues to increase the number of matching bytes until a
workable set is found.
- It then creates a mask of valid proofs and fake proofs and sends it to
the leader. This is a storage proof confirmation transaction.
2. After a lockout period of NUM_SECONDS_STORAGE_LOCKOUT seconds, the
validator then submits a storage proof claim transaction which then causes the
distribution of the storage reward if no challenges were seen for the proof to
the validators and archivers party to the proofs.
## Archiver behavior
1. The archiver then generates another set of offsets which it submits a fake
proof with an incorrect sha state. It can be proven to be fake by providing the
seed for the hash result.
- A fake proof should consist of an archiver hash of a signature of a PoH
value. That way when the archiver reveals the fake proof, it can be
verified on chain.
2. The archiver monitors the ledger, if it sees a fake proof integrated, it
creates a challenge transaction and submits it to the current leader. The
transacation proves the validator incorrectly validated a fake storage proof.
The archiver is rewarded and the validator's staking balance is slashed or
frozen.
## Storage proof contract logic
Each archiver and validator will have their own storage account. The validator's account would be separate from their gossip id similiar to their vote account. These should be implemented as two programs one which handles the validator as the keysigner and one for the archiver. In that way when the programs reference other accounts, they can check the program id to ensure it is a validator or archiver account they are referencing.
### SubmitMiningProof
```text
SubmitMiningProof {
slot: u64,
sha_state: Hash,
signature: Signature,
};
keys = [archiver_keypair]
```
Archivers create these after mining their stored ledger data for a certain hash value. The slot is the end slot of the segment of ledger they are storing, the sha_state the result of the archiver using the hash function to sample their encrypted ledger segment. The signature is the signature that was created when they signed a PoH value for the current storage epoch. The list of proofs from the current storage epoch should be saved in the account state, and then transfered to a list of proofs for the previous epoch when the epoch passes. In a given storage epoch a given archiver should only submit proofs for one segment.
The program should have a list of slots which are valid storage mining slots. This list should be maintained by keeping track of slots which are rooted slots in which a significant portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS_PER_SEGMENT number of slots would be added to this set. The program should check that the slot is in this set. The set can be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of the previous storage epoch PoH value.
### ProofValidation
```text
ProofValidation {
proof_mask: Vec<ProofStatus>,
}
keys = [validator_keypair, archiver_keypair(s) (unsigned)]
```
A validator will submit this transaction to indicate that a set of proofs for a given segment are valid/not-valid or skipped where the validator did not look at it. The keypairs for the archivers that it looked at should be referenced in the keys so the program logic can go to those accounts and see that the proofs are generated in the previous epoch. The sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by the validator according to the logic outlined in the validator behavior of sampling.
The included archiver keys will indicate the the storage samples which are being referenced; the length of the proof_mask should be verified against the set of storage proofs in the referenced archiver account\(s\), and should match with the number of proofs submitted in the previous storage epoch in the state of said archiver account.
### ClaimStorageReward
```text
ClaimStorageReward {
}
keys = [validator_keypair or archiver_keypair, validator/archiver_keypairs (unsigned)]
```
Archivers and validators will use this transaction to get paid tokens from a program state where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where proofs have been submitted and validated and there are no ChallengeProofValidations referencing those proofs. For a validator, it should reference the archiver keypairs to which it has validated proofs in the relevant epoch. And for an archiver it should reference validator keypairs for which it has validated and wants to be rewarded.
### ChallengeProofValidation
```text
ChallengeProofValidation {
proof_index: u64,
hash_seed_value: Vec<u8>,
}
keys = [archiver_keypair, validator_keypair]
```
This transaction is for catching lazy validators who are not doing the work to validate proofs. An archiver will submit this transaction when it sees a validator has approved a fake SubmitMiningProof transaction. Since the archiver is a light client not looking at the full chain, it will have to ask a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for a certain segment in the previous storage epoch. The program will look in the validator account state see that a ProofValidation is submitted in the previous storage epoch and hash the hash_seed_value and see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so, then it will save the challenge to the list of challenges that it has in its state.
### AdvertiseStorageRecentBlockhash
```text
AdvertiseStorageRecentBlockhash {
hash: Hash,
slot: u64,
}
```
Validators and archivers will submit this to indicate that a new storage epoch has passed and that the storage proofs which are current proofs should now be for the previous epoch. Other transactions should check to see that the epoch that they are referencing is accurate according to current chain state.

View File

@ -1,8 +1,6 @@
---
title: Managing Forks
---
# Managing Forks
The ledger is permitted to fork at slot boundaries. The resulting data structure forms a tree called a _blockstore_. When the validator interprets the blockstore, it must maintain state for each fork in the chain. We call each instance an _active fork_. It is the responsibility of a validator to weigh those forks, such that it may eventually select a fork.
The ledger is permitted to fork at slot boundaries. The resulting data structure forms a tree called a _blocktree_. When the validator interprets the blocktree, it must maintain state for each fork in the chain. We call each instance an _active fork_. It is the responsibility of a validator to weigh those forks, such that it may eventually select a fork.
A validator selects a fork by submiting a vote to a slot leader on that fork. The vote commits the validator for a duration of time called a _lockout period_. The validator is not permitted to vote on a different fork until that lockout period expires. Each subsequent vote on the same fork doubles the length of the lockout period. After some cluster-configured number of votes \(currently 32\), the length of the lockout period reaches what's called _max lockout_. Until the max lockout is reached, the validator has the option to wait until the lockout period is over and then vote on another fork. When it votes on another fork, it performs a operation called _rollback_, whereby the state rolls back in time to a shared checkpoint and then jumps forward to the tip of the fork that it just voted on. The maximum distance that a fork may roll back is called the _rollback depth_. Rollback depth is the number of votes required to achieve max lockout. Whenever a validator votes, any checkpoints beyond the rollback depth become unreachable. That is, there is no scenario in which the validator will need to roll back beyond rollback depth. It therefore may safely _prune_ unreachable forks and _squash_ all checkpoints beyond rollback depth into the root checkpoint.
@ -10,14 +8,14 @@ A validator selects a fork by submiting a vote to a slot leader on that fork. Th
An active fork is as a sequence of checkpoints that has a length at least one longer than the rollback depth. The shortest fork will have a length exactly one longer than the rollback depth. For example:
![Forks](/img/forks.svg)
![Forks](../.gitbook/assets/forks.svg)
The following sequences are _active forks_:
- {4, 2, 1}
- {5, 2, 1}
- {6, 3, 1}
- {7, 3, 1}
* {4, 2, 1}
* {5, 2, 1}
* {6, 3, 1}
* {7, 3, 1}
## Pruning and Squashing
@ -25,12 +23,12 @@ A validator may vote on any checkpoint in the tree. In the diagram above, that's
Starting from the example above, wth a rollback depth of 2, consider a vote on 5 versus a vote on 6. First, a vote on 5:
![Forks after pruning](/img/forks-pruned.svg)
![Forks after pruning](../.gitbook/assets/forks-pruned.svg)
The new root is 2, and any active forks that are not descendants from 2 are pruned.
Alternatively, a vote on 6:
![Forks](/img/forks-pruned2.svg)
![Forks](../.gitbook/assets/forks-pruned2.svg)
The tree remains with a root of 1, since the active fork starting at 6 is only 2 checkpoints from the root.

View File

@ -1,6 +1,4 @@
---
title: Performance Metrics
---
# Performance Metrics
Solana cluster performance is measured as average number of transactions per second that the network can sustain \(TPS\). And, how long it takes for a transaction to be confirmed by super majority of the cluster \(Confirmation Time\).
@ -23,3 +21,4 @@ The validator software is deployed to GCP n1-standard-16 instances with 1TB pd-s
solana-bench-tps is started after the network converges from a client machine with n1-standard-16 CPU-only instance with the following arguments: `--tx\_count=50000 --thread-batch-sleep 1000`
TPS and confirmation metrics are captured from the dashboard numbers over a 5 minute average of when the bench-tps transfer stage begins.

View File

@ -1,8 +1,6 @@
---
title: Stake Delegation and Rewards
---
# Stake Delegation and Rewards
Stakers are rewarded for helping to validate the ledger. They do this by delegating their stake to validator nodes. Those validators do the legwork of replaying the ledger and send votes to a per-node vote account to which stakers can delegate their stakes. The rest of the cluster uses those stake-weighted votes to select a block when forks arise. Both the validator and staker need some economic incentive to play their part. The validator needs to be compensated for its hardware and the staker needs to be compensated for the risk of getting its stake slashed. The economics are covered in [staking rewards](../implemented-proposals/staking-rewards.md). This section, on the other hand, describes the underlying mechanics of its implementation.
Stakers are rewarded for helping to validate the ledger. They do this by delegating their stake to validator nodes. Those validators do the legwork of replaying the ledger and send votes to a per-node vote account to which stakers can delegate their stakes. The rest of the cluster uses those stake-weighted votes to select a block when forks arise. Both the validator and staker need some economic incentive to play their part. The validator needs to be compensated for its hardware and the staker needs to be compensated for the risk of getting its stake slashed. The economics are covered in [staking rewards](../proposals/staking-rewards.md). This chapter, on the other hand, describes the underlying mechanics of its implementation.
## Basic Design
@ -24,18 +22,22 @@ The rewards process is split into two on-chain programs. The Vote program solves
VoteState is the current state of all the votes the validator has submitted to the network. VoteState contains the following state information:
- `votes` - The submitted votes data structure.
- `credits` - The total number of rewards this vote program has generated over its lifetime.
- `root_slot` - The last slot to reach the full lockout commitment necessary for rewards.
- `commission` - The commission taken by this VoteState for any rewards claimed by staker's Stake accounts. This is the percentage ceiling of the reward.
- Account::lamports - The accumulated lamports from the commission. These do not count as stakes.
- `authorized_voter` - Only this identity is authorized to submit votes. This field can only modified by this identity.
- `node_pubkey` - The Solana node that votes in this account.
- `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's address and the authorized vote signer
* `votes` - The submitted votes data structure.
* `credits` - The total number of rewards this vote program has generated over its lifetime.
* `root_slot` - The last slot to reach the full lockout commitment necessary for rewards.
* `commission` - The commission taken by this VoteState for any rewards claimed by staker's Stake accounts. This is the percentage ceiling of the reward.
* Account::lamports - The accumulated lamports from the commission. These do not count as stakes.
* `authorized_voter` - Only this identity is authorized to submit votes. This field can only modified by this identity.
* `node_pubkey` - The Solana node that votes in this account.
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's
```text
address and the authorized vote signer
```
### VoteInstruction::Initialize\(VoteInit\)
- `account[0]` - RW - The VoteState
* `account[0]` - RW - The VoteState
`VoteInit` carries the new vote account's `node_pubkey`, `authorized_voter`, `authorized_withdrawer`, and `commission`
@ -45,16 +47,18 @@ VoteState is the current state of all the votes the validator has submitted to t
Updates the account with a new authorized voter or withdrawer, according to the VoteAuthorize parameter \(`Voter` or `Withdrawer`\). The transaction must be by signed by the Vote account's current `authorized_voter` or `authorized_withdrawer`.
- `account[0]` - RW - The VoteState
* `account[0]` - RW - The VoteState
`VoteState::authorized_voter` or `authorized_withdrawer` is set to to `Pubkey`.
### VoteInstruction::Vote\(Vote\)
- `account[0]` - RW - The VoteState
* `account[0]` - RW - The VoteState
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](../implemented-proposals/tower-bft.md)
- `account[1]` - RO - `sysvar::slot_hashes` A list of some N most recent slots and their hashes for the vote to be verified against.
- `account[2]` - RO - `sysvar::clock` The current network time, expressed in slots, epochs.
* `account[1]` - RO - `sysvar::slot_hashes` A list of some N most recent slots and their hashes for the vote to be verified against.
* `account[2]` - RO - `sysvar::clock` The current network time, expressed in slots, epochs.
### StakeState
@ -64,15 +68,23 @@ A StakeState takes one of four forms, StakeState::Uninitialized, StakeState::Ini
StakeState::Stake is the current delegation preference of the **staker** and contains the following state information:
- Account::lamports - The lamports available for staking.
- `stake` - the staked amount \(subject to warm up and cool down\) for generating rewards, always less than or equal to Account::lamports
- `voter_pubkey` - The pubkey of the VoteState instance the lamports are delegated to.
- `credits_observed` - The total credits claimed over the lifetime of the program.
- `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
- `deactivated` - the epoch at which this stake was de-activated, some cool down epochs are required before the account is fully deactivated, and the stake available for withdrawal
* Account::lamports - The lamports available for staking.
* `stake` - the staked amount \(subject to warm up and cool down\) for generating rewards, always less than or equal to Account::lamports
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the program.
* `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
* `deactivated` - the epoch at which this stake was de-activated, some cool down epochs are required before the account
- `authorized_staker` - the pubkey of the entity that must sign delegation, activation, and deactivation transactions
- `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's address, and the authorized staker
```text
is fully deactivated, and the stake available for withdrawal
```
* `authorized_staker` - the pubkey of the entity that must sign delegation, activation, and deactivation transactions
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's
```text
address, and the authorized staker
```
### StakeState::RewardsPool
@ -82,29 +94,49 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p
### StakeInstruction::DelegateStake
The Stake account is moved from Initialized to StakeState::Stake form, or from a deactivated (i.e. fully cooled-down) StakeState::Stake to activated StakeState::Stake. This is how stakers choose the vote account and validator node to which their stake account lamports are delegated. The transaction must be signed by the stake's `authorized_staker`.
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch.
- `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
- `account[1]` - R - The VoteState instance.
- `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
- `account[3]` - R - sysvar::stakehistory account, carries information about stake history
- `account[4]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
* `account[1]` - R - The VoteState instance.
* `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
* `account[3]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)
Updates the account with a new authorized staker or withdrawer, according to the StakeAuthorize parameter \(`Staker` or `Withdrawer`\). The transaction must be by signed by the Stakee account's current `authorized_staker` or `authorized_withdrawer`. Any stake lock-up must have expired, or the lock-up custodian must also sign the transaction.
Updates the account with a new authorized staker or withdrawer, according to the StakeAuthorize parameter \(`Staker` or `Withdrawer`\). The transaction must be by signed by the Stakee account's current `authorized_staker` or `authorized_withdrawer`.
- `account[0]` - RW - The StakeState
* `account[0]` - RW - The StakeState
`StakeState::authorized_staker` or `authorized_withdrawer` is set to to `Pubkey`.
### StakeInstruction::RedeemVoteCredits
The staker or the owner of the Stake account sends a transaction with this instruction to claim rewards.
The Vote account and the Stake account pair maintain a lifetime counter of total rewards generated and claimed. Rewards are paid according to a point value supplied by the Bank from inflation. A `point` is one credit \* one staked lamport, rewards paid are proportional to the number of lamports staked.
* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards.
* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey`
* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request \(picked at random\).
* `account[3]` - R - sysvar::rewards account from the Bank that carries point value.
* `account[4]` - R - sysvar::stake\_history account from the Bank that carries stake warmup/cooldown history
Reward is paid out for the difference between `VoteState::credits` to `StakeState::Stake::credits_observed`, multiplied by `sysvar::rewards::Rewards::validator_point_value`. `StakeState::Stake::credits_observed` is updated to`VoteState::credits`. The commission is deposited into the Vote account token balance, and the reward is deposited to the Stake account token balance and the stake account's `stake` is increased by the same amount \(re-invested\).
```text
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
stake_state.credits_observed = vote_state.credits;
```
`credits_to_claim` is used to compute the reward and commission, and `StakeState::Stake::credits_observed` is updated to the latest `VoteState::credits` value.
### StakeInstruction::Deactivate
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
The transaction must be signed by the stake's `authorized_staker`.
- `account[0]` - RW - The StakeState::Stake instance that is deactivating.
- `account[1]` - R - sysvar::clock account from the Bank that carries current epoch
* `account[0]` - RW - The StakeState::Stake instance that is deactivating.
* `account[1]` - R - sysvar::clock account from the Bank that carries current epoch
StakeState::Stake::deactivated is set to the current epoch + cool down. The account's stake will ramp down to zero by that epoch, and Account::lamports will be available for withdrawal.
@ -112,29 +144,29 @@ StakeState::Stake::deactivated is set to the current epoch + cool down. The acco
Lamports build up over time in a Stake account and any excess over activated stake can be withdrawn. The transaction must be signed by the stake's `authorized_withdrawer`.
- `account[0]` - RW - The StakeState::Stake from which to withdraw.
- `account[1]` - RW - Account that should be credited with the withdrawn lamports.
- `account[2]` - R - sysvar::clock account from the Bank that carries current epoch, to calculate stake.
- `account[3]` - R - sysvar::stake_history account from the Bank that carries stake warmup/cooldown history
* `account[0]` - RW - The StakeState::Stake from which to withdraw.
* `account[1]` - RW - Account that should be credited with the withdrawn lamports.
* `account[2]` - R - sysvar::clock account from the Bank that carries current epoch, to calculate stake.
* `account[3]` - R - sysvar::stake\_history account from the Bank that carries stake warmup/cooldown history
## Benefits of the design
- Single vote for all the stakers.
- Clearing of the credit variable is not necessary for claiming rewards.
- Each delegated stake can claim its rewards independently.
- Commission for the work is deposited when a reward is claimed by the delegated stake.
* Single vote for all the stakers.
* Clearing of the credit variable is not necessary for claiming rewards.
* Each delegated stake can claim its rewards independently.
* Commission for the work is deposited when a reward is claimed by the delegated stake.
## Example Callflow
![Passive Staking Callflow](/img/passive-staking-callflow.png)
![Passive Staking Callflow](../.gitbook/assets/passive-staking-callflow.svg)
## Staking Rewards
The specific mechanics and rules of the validator rewards regime is outlined here. Rewards are earned by delegating stake to a validator that is voting correctly. Voting incorrectly exposes that validator's stakes to [slashing](../proposals/slashing.md).
The specific mechanics and rules of the validator rewards regime is outlined here. Rewards are earned by delegating stake to a validator that is voting correctly. Voting incorrectly exposes that validator's stakes to [slashing](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/staking-and-rewards.md).
### Basics
The network pays rewards from a portion of network [inflation](../terminology.md#inflation). The number of lamports available to pay rewards for an epoch is fixed and must be evenly divided among all staked nodes according to their relative stake weight and participation. The weighting unit is called a [point](../terminology.md#point).
The network pays rewards from a portion of network [inflation](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/inflation.md). The number of lamports available to pay rewards for an epoch is fixed and must be evenly divided among all staked nodes according to their relative stake weight and participation. The weighting unit is called a [point](../terminology.md#point).
Rewards for an epoch are not available until the end of that epoch.
@ -156,7 +188,7 @@ Stakers who have delegated to that validator earn points in proportion to their
Stakes, once delegated, do not become effective immediately. They must first pass through a warm up period. During this period some portion of the stake is considered "effective", the rest is considered "activating". Changes occur on epoch boundaries.
The stake program limits the rate of change to total network stake, reflected in the stake program's `config::warmup_rate` \(set to 25% per epoch in the current implementation\).
The stake program limits the rate of change to total network stake, reflected in the stake program's `config::warmup_rate` \(typically 25% per epoch\).
The amount of stake that can be warmed up each epoch is a function of the previous epoch's total effective stake, total activating stake, and the stake program's configured warmup rate.
@ -173,22 +205,22 @@ Consider the situation of a single stake of 1,000 activated at epoch N, with net
At epoch N+1, the amount available to be activated for the network is 400 \(20% of 200\), and at epoch N, this example stake is the only stake activating, and so is entitled to all of the warmup room available.
| epoch | effective | activating | total effective | total activating |
| :---- | --------: | ---------: | --------------: | ---------------: |
| N-1 | | | 2,000 | 0 |
| N | 0 | 1,000 | 2,000 | 1,000 |
| N+1 | 400 | 600 | 2,400 | 600 |
| N+2 | 880 | 120 | 2,880 | 120 |
| N+3 | 1000 | 0 | 3,000 | 0 |
| :--- | ---: | ---: | ---: | ---: |
| N-1 | | | 2,000 | 0 |
| N | 0 | 1,000 | 2,000 | 1,000 |
| N+1 | 400 | 600 | 2,400 | 600 |
| N+2 | 880 | 120 | 2,880 | 120 |
| N+3 | 1000 | 0 | 3,000 | 0 |
Were 2 stakes \(X and Y\) to activate at epoch N, they would be awarded a portion of the 20% in proportion to their stakes. At each epoch effective and activating for each stake is a function of the previous epoch's state.
| epoch | X eff | X act | Y eff | Y act | total effective | total activating |
| :---- | ----: | ----: | ----: | ----: | --------------: | ---------------: |
| N-1 | | | | | 2,000 | 0 |
| N | 0 | 1,000 | 0 | 200 | 2,000 | 1,200 |
| N+1 | 333 | 667 | 67 | 133 | 2,400 | 800 |
| N+2 | 733 | 267 | 146 | 54 | 2,880 | 321 |
| N+3 | 1000 | 0 | 200 | 0 | 3,200 | 0 |
| :--- | ---: | ---: | ---: | ---: | ---: | ---: |
| N-1 | | | | | 2,000 | 0 |
| N | 0 | 1,000 | 0 | 200 | 2,000 | 1,200 |
| N+1 | 333 | 667 | 67 | 133 | 2,400 | 800 |
| N+2 | 733 | 267 | 146 | 54 | 2,880 | 321 |
| N+3 | 1000 | 0 | 200 | 0 | 3,200 | 0 |
### Withdrawal
@ -196,4 +228,4 @@ Only lamports in excess of effective+activating stake may be withdrawn at any ti
### Lock-up
Stake accounts support the notion of lock-up, wherein the stake account balance is unavailable for withdrawal until a specified time. Lock-up is specified as an epoch height, i.e. the minimum epoch height that must be reached by the network before the stake account balance is available for withdrawal, unless the transaction is also signed by a specified custodian. This information is gathered when the stake account is created, and stored in the Lockup field of the stake account's state. Changing the authorized staker or withdrawer is also subject to lock-up, as such an operation is effectively a transfer.
Stake accounts support the notion of lock-up, wherein the stake account balance is unavailable for withdrawal until a specified time. Lock-up is specified as an epoch height, i.e. the minimum epoch height that must be reached by the network before the stake account balance is available for withdrawal, unless the transaction is also signed by a specified custodian. This information is gathered when the stake account is created, and stored in the Lockup field of the stake account's state.

View File

@ -1,6 +1,4 @@
---
title: Synchronization
---
# Synchronization
Fast, reliable synchronization is the biggest reason Solana is able to achieve such high throughput. Traditional blockchains synchronize on large chunks of transactions called blocks. By synchronizing on blocks, a transaction cannot be processed until a duration called "block time" has passed. In Proof of Work consensus, these block times need to be very large \(~10 minutes\) to minimize the odds of multiple validators producing a new valid block at the same time. There's no such constraint in Proof of Stake consensus, but without reliable timestamps, a validator cannot determine the order of incoming blocks. The popular workaround is to tag each block with a [wallclock timestamp](https://en.bitcoin.it/wiki/Block_timestamp). Because of clock drift and variance in network latencies, the timestamp is only accurate within an hour or two. To workaround the workaround, these systems lengthen block times to provide reasonable certainty that the median timestamp on each block is always increasing.
@ -20,9 +18,10 @@ Another difference between PoH and VDFs is that a VDF is used only for tracking
## Relationship to Consensus Mechanisms
Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the data plane protocols.
Proof of History is not a consensus mechanism, but it is used to improve the performance of Solana's Proof of Stake consensus. It is also used to improve the performance of the data plane and replication protocols.
## More on Proof of History
- [water clock analogy](https://medium.com/solana-labs/proof-of-history-explained-by-a-water-clock-e682183417b8)
- [Proof of History overview](https://medium.com/solana-labs/proof-of-history-a-clock-for-blockchain-cf47a61a9274)
* [water clock analogy](https://medium.com/solana-labs/proof-of-history-explained-by-a-water-clock-e682183417b8)
* [Proof of History overview](https://medium.com/solana-labs/proof-of-history-a-clock-for-blockchain-cf47a61a9274)

View File

@ -1,6 +1,4 @@
---
title: Turbine Block Propagation
---
# Turbine Block Propagation
A Solana cluster uses a multi-layer block propagation mechanism called _Turbine_ to broadcast transaction shreds to all nodes with minimal amount of duplicate messages. The cluster divides itself into small collections of nodes, called _neighborhoods_. Each node is responsible for sharing any data it receives with the other nodes in its neighborhood, as well as propagating the data on to a small set of nodes in other neighborhoods. This way each node only has to communicate with a small number of nodes.
@ -10,7 +8,7 @@ During its slot, the leader node distributes shreds between the validator nodes
In order for data plane fanout to work, the entire cluster must agree on how the cluster is divided into neighborhoods. To achieve this, all the recognized validator nodes \(the TVU peers\) are sorted by stake and stored in a list. This list is then indexed in different ways to figure out neighborhood boundaries and retransmit peers. For example, the leader will simply select the first nodes to make up layer 0. These will automatically be the highest stake holders, allowing the heaviest votes to come back to the leader first. Layer-0 and lower-layer nodes use the same logic to find their neighbors and next layer peers.
To reduce the possibility of attack vectors, each shred is transmitted over a random tree of neighborhoods. Each node uses the same set of nodes representing the cluster. A random tree is generated from the set for each shred using a seed derived from the leader id, slot and shred index.
To reduce the possibility of attack vectors, each shred is transmitted over a random tree of neighborhoods. Each node uses the same set of nodes representing the cluster. A random tree is generated from the set for each shred using randomness derived from the shred itself. Since the random seed is not known in advance, attacks that try to eclipse neighborhoods from certain leaders or blocks become very difficult, and should require almost complete control of the stake in the cluster.
## Layer and Neighborhood Structure
@ -22,15 +20,15 @@ This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FAN
The following diagram shows how the Leader sends shreds with a Fanout of 2 to Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data with each other.
![Leader sends shreds to Neighborhood 0 in Layer 0](/img/data-plane-seeding.svg)
![Leader sends shreds to Neighborhood 0 in Layer 0](../.gitbook/assets/data-plane-seeding.svg)
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
![Neighborhood 0 Fanout to Neighborhood 1 and 2](/img/data-plane-fanout.svg)
![Neighborhood 0 Fanout to Neighborhood 1 and 2](../.gitbook/assets/data-plane-fanout.svg)
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
![Two layer cluster with a Fanout of 2](/img/data-plane.svg)
![Two layer cluster with a Fanout of 2](../.gitbook/assets/data-plane.svg)
### Configuration Values
@ -40,62 +38,59 @@ Currently, configuration is set when the cluster is launched. In the future, the
## Calcuating the required FEC rate
Turbine relies on retransmission of packets between validators. Due to
Turbine relies on retransmission of packets between validators. Due to
retransmission, any network wide packet loss is compounded, and the
probability of the packet failing to reach is destination increases
on each hop. The FEC rate needs to take into account the network wide
on each hop. The FEC rate needs to take into account the network wide
packet loss, and the propagation depth.
A shred group is the set of data and coding packets that can be used
to reconstruct each other. Each shred group has a chance of failure,
to reconstruct each other. Each shred group has a chance of failure,
based on the likelyhood of the number of packets failing that exceeds
the FEC rate. If a validator fails to reconstruct the shred group,
then the block cannot be reconstructed, and the validator has to rely
on repair to fixup the blocks.
The probability of the shred group failing can be computed using the
binomial distribution. If the FEC rate is `16:4`, then the group size
binomial distribution. If the FEC rate is `16:4`, then the group size
is 20, and at least 4 of the shreds must fail for the group to fail.
Which is equal to the sum of the probability of 4 or more trails failing
out of 20.
Probability of a block succeeding in turbine:
- Probability of packet failure: `P = 1 - (1 - network_packet_loss_rate)^2`
- FEC rate: `K:M`
- Number of trials: `N = K + M`
- Shred group failure rate: `S = SUM of i=0 -> M for binomial(prob_failure = P, trials = N, failures = i)`
- Shreds per block: `G`
- Block success rate: `B = (1 - S) ^ (G / N)`
- Binomial distribution for exactly `i` results with probability of P in N trials is defined as `(N choose i) * P^i * (1 - P)^(N-i)`
* Probability of packet failure: `P = 1 - (1 - network_packet_loss_rate)^2`
* FEC rate: `K:M`
* Number of trials: `N = K + M`
* Shred group failure rate: `S = SUM of i=0 -> M for binomial(prob_failure = P, trials = N, failures = i)`
* Shreds per block: `G`
* Block success rate: `B = (1 - S) ^ (G / N) `
* Binomial distribution for exactly `i` results with probability of P in N trials is defined as `(N choose i) * P^i * (1 - P)^(N-i)`
For example:
- Network packet loss rate is 15%.
- 50kpts network generates 6400 shreds per second.
- FEC rate increases the total shres per block by the FEC ratio.
* Network packet loss rate is 15%.
* 50kpts network generates 6400 shreds per second.
* FEC rate increases the total shres per block by the FEC ratio.
With a FEC rate: `16:4`
- `G = 8000`
- `P = 1 - 0.85 * 0.85 = 1 - 0.7225 = 0.2775`
- `S = SUM of i=0 -> 4 for binomial(prob_failure = 0.2775, trials = 20, failures = i) = 0.689414`
- `B = (1 - 0.689) ^ (8000 / 20) = 10^-203`
* `G = 8000`
* `P = 1 - 0.85 * 0.85 = 1 - 0.7225 = 0.2775`
* `S = SUM of i=0 -> 4 for binomial(prob_failure = 0.2775, trials = 20, failures = i) = 0.689414`
* `B = (1 - 0.689) ^ (8000 / 20) = 10^-203`
With FEC rate of `16:16`
- `G = 12800`
- `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.002132`
- `B = (1 - 0.002132) ^ (12800 / 32) = 0.42583`
* `G = 12800`
* `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.002132`
* `B = (1 - 0.002132) ^ (12800 / 32) = 0.42583`
With FEC rate of `32:32`
- `G = 12800`
- `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.000048`
- `B = (1 - 0.000048) ^ (12800 / 64) = 0.99045`
* `G = 12800`
* `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.000048`
* `B = (1 - 0.000048) ^ (12800 / 64) = 0.99045`
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact. To cripple a neighborhood, enough nodes \(erasure codes +1\) from the neighborhood above need to fail. Since each neighborhood receives shreds from multiple nodes in a neighborhood in the upper layer, we'd need a big network failure in the upper layers to end up with incomplete data.
![Inner workings of a neighborhood](/img/data-plane-neighborhood.svg)
![Inner workings of a neighborhood](../.gitbook/assets/data-plane-neighborhood.svg)

View File

@ -1,6 +1,4 @@
---
title: Secure Vote Signing
---
# Secure Vote Signing
A validator receives entries from the current leader and submits votes confirming those entries are valid. This vote submission presents a security challenge, because forged votes that violate consensus rules could be used to slash the validator's stake.
@ -22,30 +20,30 @@ Currently, there is a 1:1 relationship between validators and vote signers, and
The vote signing service consists of a JSON RPC server and a request processor. At startup, the service starts the RPC server at a configured port and waits for validator requests. It expects the following type of requests: 1. Register a new validator node
- The request must contain validator's identity \(public key\)
- The request must be signed with the validator's private key
- The service drops the request if signature of the request cannot be
* The request must contain validator's identity \(public key\)
* The request must be signed with the validator's private key
* The service drops the request if signature of the request cannot be
verified
- The service creates a new voting asymmetric key for the validator, and
* The service creates a new voting asymmetric key for the validator, and
returns the public key as a response
- If a validator tries to register again, the service returns the public key
* If a validator tries to register again, the service returns the public key
from the pre-existing keypair
1. Sign a vote
- The request must contain a voting transaction and all verification data
- The request must be signed with the validator's private key
- The service drops the request if signature of the request cannot be
* The request must contain a voting transaction and all verification data
* The request must be signed with the validator's private key
* The service drops the request if signature of the request cannot be
verified
- The service verifies the voting data
- The service returns a signature for the transaction
* The service verifies the voting data
* The service returns a signature for the transaction
## Validator voting
@ -66,3 +64,4 @@ The validator looks up the votes submitted by all the nodes in the cluster for t
### New Vote Signing
The validator creates a "new vote" transaction and sends it to the signing service using JSON RPC. The RPC request also includes the vote verification data. On success, the RPC call returns the signature for the vote. On failure, RPC call returns the failure code.

View File

@ -0,0 +1,18 @@
## Storage Rent Economics
Each transaction that is submitted to the Solana ledger imposes costs. Transaction fees paid by the submitter, and collected by a validator, in theory, account for the acute, transacitonal, costs of validating and adding that data to the ledger. At the same time, our compensation design for archivers (see [Replication-client Economics](ed_replication_client_economics.md)), in theory, accounts for the long term storage of the historical ledger. Unaccounted in this process is the mid-term storage of active ledger state, necessarily maintined by the rotating validator set. This type of storage imposes costs not only to validators but also to the broader network as active state grows so does data transmission and validation overhead. To account for these costs, we describe here our preliminary design and implementation of storage rent.
Storage rent can be paid via one of two methods:
Method 1: Set it and forget it
With this approach, accounts with two-years worth of rent deposits secured are exempt from network rent charges. By maintaining this minimum-balance, the broader network benefits from reduced liquitity and the account holder can trust that their `Account::data` will be retained for continual access/usage.
Method 2: Pay per byte
If an account has less than two-years worth of deposited rent the network charges rent on a per-epoch basis, in credit for the next epoch (but in arrears when necessary). This rent is deducted at a rate specified in genesis, in lamports per kilobyte-year.
For information on the technical implementation details of this design, see the [Rent](rent.md) section.

View File

@ -1,6 +1,4 @@
---
title: Benchmark a Cluster
---
# Getting Started
The Solana git repository contains all the scripts you might need to spin up your own local testnet. Depending on what you're looking to achieve, you may want to run a different variation, as the full-fledged, performance-enhanced multinode testnet is considerably more complex to set up than a Rust-only, singlenode testnode. If you are looking to develop high-level features, such as experimenting with smart contracts, save yourself some setup headaches and stick to the Rust-only singlenode demo. If you're doing performance optimization of the transaction pipeline, consider the enhanced singlenode demo. If you're doing consensus work, you'll need at least a Rust-only multinode demo. If you want to reproduce our TPS metrics, run the enhanced multinode demo.
@ -54,12 +52,12 @@ $ NDEBUG=1 ./multinode-demo/faucet.sh
### Singlenode Testnet
Before you start a validator, make sure you know the IP address of the machine you want to be the bootstrap validator for the demo, and make sure that udp ports 8000-10000 are open on all the machines you want to test with.
Before you start a validator, make sure you know the IP address of the machine you want to be the bootstrap leader for the demo, and make sure that udp ports 8000-10000 are open on all the machines you want to test with.
Now start the bootstrap validator in a separate shell:
Now start the bootstrap leader in a separate shell:
```bash
$ NDEBUG=1 ./multinode-demo/bootstrap-validator.sh
$ NDEBUG=1 ./multinode-demo/bootstrap-leader.sh
```
Wait a few seconds for the server to initialize. It will print "leader ready..." when it's ready to receive transactions. The leader will request some tokens from the faucet if it doesn't have any. The faucet does not need to be running for subsequent leader starts.
@ -76,7 +74,7 @@ To run a performance-enhanced validator on Linux, [CUDA 10.0](https://developer.
```bash
$ ./fetch-perf-libs.sh
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/bootstrap-validator.sh
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/validator.sh
```
@ -94,17 +92,17 @@ What just happened? The client demo spins up several threads to send 500,000 tra
### Testnet Debugging
There are some useful debug messages in the code, you can enable them on a per-module and per-level basis. Before running a leader or validator set the normal RUST_LOG environment variable.
There are some useful debug messages in the code, you can enable them on a per-module and per-level basis. Before running a leader or validator set the normal RUST\_LOG environment variable.
For example
- To enable `info` everywhere and `debug` only in the solana::banking_stage module:
* To enable `info` everywhere and `debug` only in the solana::banking\_stage module:
```bash
$ export RUST_LOG=solana=info,solana::banking_stage=debug
```
- To enable BPF program logging:
* To enable BPF program logging:
```bash
$ export RUST_LOG=solana_bpf_loader=trace
@ -123,12 +121,12 @@ thread apply all bt
This will dump all the threads stack traces into gdb.txt
## Developer Testnet
## Public Testnet
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
```bash
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint devnet.solana.com:8001 --faucet devnet.solana.com:9900 --duration 60 --tx_count 50
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint testnet.solana.com:8001 --faucet testnet.solana.com:9900 --duration 60 --tx_count 50
```
You can observe the effects of your client's transactions on our [metrics dashboard](https://metrics.solana.com:3000/d/monitor/cluster-telemetry?var-testnet=devnet)
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)

View File

@ -0,0 +1,7 @@
# Testnet Participation
Participate in our testnet:
* [Running a Validator](../running-validator/)
* [Running an Archiver](../running-archiver.md)

View File

@ -1,6 +1,4 @@
---
title: "Example Client: Web Wallet"
---
# Example Client: Web Wallet
## Build and run a web wallet locally
@ -15,3 +13,4 @@ $ git checkout $TAG
```
Next, follow the steps in the git repository's [README](https://github.com/solana-labs/example-webwallet/blob/master/README.md).

View File

@ -0,0 +1,4 @@
# Implemented Design Proposals
The following design proposals are fully implemented.

View File

@ -0,0 +1,90 @@
# Blocktree
After a block reaches finality, all blocks from that one on down to the genesis block form a linear chain with the familiar name blockchain. Until that point, however, the validator must maintain all potentially valid chains, called _forks_. The process by which forks naturally form as a result of leader rotation is described in [fork generation](../cluster/fork-generation.md). The _blocktree_ data structure described here is how a validator copes with those forks until blocks are finalized.
The blocktree allows a validator to record every shred it observes on the network, in any order, as long as the shred is signed by the expected leader for a given slot.
Shreds are moved to a fork-able key space the tuple of `leader slot` + `shred index` \(within the slot\). This permits the skip-list structure of the Solana protocol to be stored in its entirety, without a-priori choosing which fork to follow, which Entries to persist or when to persist them.
Repair requests for recent shreds are served out of RAM or recent files and out of deeper storage for less recent shreds, as implemented by the store backing Blocktree.
## Functionalities of Blocktree
1. Persistence: the Blocktree lives in the front of the nodes verification
pipeline, right behind network receive and signature verification. If the
shred received is consistent with the leader schedule \(i.e. was signed by the
leader for the indicated slot\), it is immediately stored.
2. Repair: repair is the same as window repair above, but able to serve any
shred that's been received. Blocktree stores shreds with signatures,
preserving the chain of origination.
3. Forks: Blocktree supports random access of shreds, so can support a
validator's need to rollback and replay from a Bank checkpoint.
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
ordered enumeration of entries from slot 0. The logic of the replay stage
\(i.e. dealing with forks\) will have to be used for the most recent entries in
the Blocktree.
## Blocktree Design
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated slot index and shred index for an entry, and the value is the entry data. Note shred indexes are zero-based for each slot \(i.e. they're slot-relative\).
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
* `slot_index` - The index of this slot
* `num_blocks` - The number of blocks in the slot \(used for chaining to a previous slot\)
* `consumed` - The highest shred index `n`, such that for all `m < n`, there exists a shred in this slot with shred index equal to `n` \(i.e. the highest consecutive shred index\).
* `received` - The highest received shred index for the slot
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
the ledger to find possible fork points.
* `last_index` - The index of the shred that is flagged as the last shred for this slot. This flag on a shred will be set by the leader for a slot when they are transmitting the last shred for a slot.
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is\_rooted for each slot with the following rules. Let slot\(n\) be the slot with index `n`, and slot\(n\).is\_full\(\) is true if the slot with index `n` has all the ticks expected for that slot. Let is\_rooted\(n\) be the statement that "the slot\(n\).is\_rooted is true". Then:
is\_rooted\(0\) is\_rooted\(n+1\) iff \(is\_rooted\(n\) and slot\(n\).is\_full\(\)
3. Chaining - When a shred for a new slot `x` arrives, we check the number of blocks \(`num_blocks`\) for that new slot \(this information is encoded in the shred\). We then know that this new slot chains to slot `x - num_blocks`.
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.
5. Update notifications - The Blocktree notifies listeners when slot\(n\).is\_rooted is flipped from false to true for any `n`.
## Blocktree APIs
The Blocktree offers a subscription based API that ReplayStage uses to ask for entries it's interested in. The entries will be sent on a channel exposed by the Blocktree. These subscription API's are as follows: 1. `fn get_slots_since(slot_indexes: &[u64]) -> Vec<SlotMeta>`: Returns new slots connecting to any element of the list `slot_indexes`.
1. `fn get_slot_entries(slot_index: u64, entry_start_index: usize, max_entries: Option<u64>) -> Vec<Entry>`: Returns the entry vector for the slot starting with `entry_start_index`, capping the result at `max` if `max_entries == Some(max)`, otherwise, no upper limit on the length of the return vector is imposed.
Note: Cumulatively, this means that the replay stage will now have to know when a slot is finished, and subscribe to the next slot it's interested in to get the next set of entries. Previously, the burden of chaining slots fell on the Blocktree.
## Interfacing with Bank
The bank exposes to replay stage:
1. `prev_hash`: which PoH chain it's working on as indicated by the hash of the last
entry it processed
2. `tick_height`: the ticks in the PoH chain currently being verified by this
bank
3. `votes`: a stack of records that contain: 1. `prev_hashes`: what anything after this vote must chain to in PoH 2. `tick_height`: the tick height at which this vote was cast 3. `lockout period`: how long a chain must be observed to be in the ledger to
be able to be chained below this vote
Replay stage uses Blocktree APIs to find the longest chain of entries it can hang off a previous vote. If that chain of entries does not hang off the latest vote, the replay stage rolls back the bank to that vote and replays the chain from there.
## Pruning Blocktree
Once Blocktree entries are old enough, representing all the possible forks becomes less useful, perhaps even problematic for replay upon restart. Once a validator's votes have reached max lockout, however, any Blocktree contents that are not on the PoH chain for that vote for can be pruned, expunged.
Archiver nodes will be responsible for storing really old ledger contents, and validators need only persist their bank periodically.

View File

@ -1,6 +1,4 @@
---
title: Commitment
---
# Commitment
The commitment metric aims to give clients a measure of the network confirmation
and stake levels on a particular block. Clients can then use this information to
@ -30,7 +28,7 @@ lockout on a bank `b`.
This computation is performed on a votable candidate bank `b` as follows.
```text
```
let output: HashMap<b, StakeLockout> = HashMap::new();
for vote_account in b.vote_accounts {
for v in vote_account.vote_stack {
@ -50,10 +48,9 @@ banks are not included in the commitment calculations here.
Now we can naturally augment the above computation to also build a
`BlockCommitment` array for every bank `b` by:
1. Adding a `ForkCommitmentCache` to collect the `BlockCommitment` structs
2. Replacing `f` with `f'` such that the above computation also builds this
`BlockCommitment` for every bank `b`.
1) Adding a `ForkCommitmentCache` to collect the `BlockCommitment` structs
2) Replacing `f` with `f'` such that the above computation also builds this
`BlockCommitment` for every bank `b`.
We will proceed with the details of 2) as 1) is trivial.
@ -65,7 +62,7 @@ votes > v as the number of confirmations will be lower).
Now more specifically, we augment the above computation to:
```text
```
let output: HashMap<b, StakeLockout> = HashMap::new();
let fork_commitment_cache = ForkCommitmentCache::default();
for vote_account in b.vote_accounts {
@ -79,8 +76,7 @@ Now more specifically, we augment the above computation to:
```
where `f'` is defined as:
```text
```
fn f`(
stake_lockout: &mut StakeLockout,
some_ancestor: &mut BlockCommitment,

View File

@ -0,0 +1,123 @@
# Durable Transaction Nonces
## Problem
To prevent replay, Solana transactions contain a nonce field populated with a
"recent" blockhash value. A transaction containing a blockhash that is too old
(~2min as of this writing) is rejected by the network as invalid. Unfortunately
certain use cases, such as custodial services, require more time to produce a
signature for the transaction. A mechanism is needed to enable these potentially
offline network participants.
## Requirements
1) The transaction's signature needs to cover the nonce value
2) The nonce must not be reusable, even in the case of signing key disclosure
## A Contract-based Solution
Here we describe a contract-based solution to the problem, whereby a client can
"stash" a nonce value for future use in a transaction's `recent_blockhash`
field. This approach is akin to the Compare and Swap atomic instruction,
implemented by some CPU ISAs.
When making use of a durable nonce, the client must first query its value from
account data. A transaction is now constructed in the normal way, but with the
following additional requirements:
1) The durable nonce value is used in the `recent_blockhash` field
2) A `Nonce` instruction is issued (first?)
3) The appropriate transaction flag is set, signaling that the usual
hash age check should be skipped and the previous requirements enforced. This
may be unnecessary, see [Runtime Support](#runtime-support) below
### Contract Mechanics
TODO: svgbob this into a flowchart
```text
Start
Create Account
state = Uninitialized
NonceInstruction
if state == Uninitialized
if account.balance < rent_exempt
error InsufficientFunds
state = Initialized
elif state != Initialized
error BadState
if sysvar.recent_blockhashes.is_empty()
error EmptyRecentBlockhashes
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
stored_hash = sysvar.recent_blockhashes[0]
success
WithdrawInstruction(to, lamports)
if state == Uninitialized
if !signers.contains(owner)
error MissingRequiredSignatures
elif state == Initialized
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
if lamports != account.balance && lamports + rent_exempt > account.balance
error InsufficientFunds
account.balance -= lamports
to.balance += lamports
success
```
A client wishing to use this feature starts by creating a nonce account and
depositing sufficient lamports as to make it rent-exempt. The resultant account
will be in the `Uninitialized` state with no stored hash and thus unusable.
The `Nonce` instruction is used to request that a new nonce be stored for the
calling account. The first `Nonce` instruction run on a newly created account
will drive the account's state to `Initialized`. As such, a `Nonce` instruction
MUST be issued before the account can be used.
To discard a `NonceAccount`, the client should issue a `Withdraw` instruction
which withdraws all lamports, leaving a zero balance and making the account
eligible for deletion.
`Nonce` and `Withdraw` instructions each will only succeed if the stored
blockhash is no longer resident in sysvar.recent_blockhashes.
### Runtime Support
The contract alone is not sufficient for implementing this feature. To enforce
an extant `recent_blockhash` on the transaction and prevent fee theft via
failed transaction replay, runtime modifications are necessary.
Any transaction failing the usual `check_hash_age` validation will be tested
for a Durable Transaction Nonce. This specifics of this test are undecided, some
options:
1) Require that the `Nonce` instruction be the first in the transaction
* + No ABI changes
* + Fast and simple
* - Sets a precedent that may lead to incompatible instruction combinations
2) Blind search for a `Nonce` instruction over all instructions in the
transaction
* + No ABI changes
* - Potentially slow
3) [2], but guarded by a transaction flag
* - ABI changes
* - Wire size increase
* + We'll probably end up with some sort of flags eventually anyway
Current prototyping will use [1]. If it is determined that a Durable Transaction
Nonce is in use, the runtime will take the following actions to validate the
transaction:
1) The `NonceAccount` specified in the `Nonce` instruction is loaded.
2) The `NonceState` is deserialized from the `NonceAccount`'s data field and
confirmed to be in the `Initialized` state.
3) The nonce value stored in the `NonceAccount` is tested to match against the
one specified in the transaction's `recent_blockhash` field.
If all three of the above checks succeed, the transaction is allowed to continue
validation.
### Open Questions
* Should this feature be restricted in the number of uses per transaction?

View File

@ -0,0 +1,16 @@
# Cluster Economics
**Subject to change.**
Solanas crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients and replication-clients. Their contributions to the network, state validation and data storage respectively, and their requisite incentive mechanisms are discussed below.
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are issuances from a global, protocol-defined, inflation rate. These rewards will constitute the total reward delivered to replication and validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to participate in the network.
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be a result of a global supply inflation rate, calculated per Solana epoch and distributed amongst the active validator set. As discussed further below, the per annum inflation rate is based on a pre-determined disinflationary schedule. This provides the network with monetary supply predictability which supports long term economic stability and security.
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction \(be it a state execution or proof-of-replication verification\). A mechanism for long-term economic stability and forking protection through partial burning of each transaction fee is also discussed below.
A high-level schematic of Solanas crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics/), [State-validation Protocol-based Rewards](ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md). Also, the chapter titled [Validation Stake Delegation](ed_validation_client_economics/ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. Additionally, in [Storage Rent Economics](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/ed_storage_rent_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. The [Replication-client Economics](ed_replication_client_economics/) chapter will review the Solana network design for global ledger storage/redundancy and archiver-client economics \([Storage-replication rewards](ed_replication_client_economics/ed_rce_storage_replication_rewards.md)\) along with an archiver-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md). An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in chapter [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
**Figure 1**: Schematic overview of Solana economic incentive design.

View File

@ -0,0 +1,14 @@
# Attack Vectors
**Subject to change.**
## Colluding validation and replication clients
A colluding validation-client, may take the strategy to mark PoReps from non-colluding archiver nodes as invalid as an attempt to maximize the rewards for the colluding archiver nodes. In this case, it isnt feasible for the offended-against archiver nodes to petition the network for resolution as this would result in a network-wide vote on each offending PoRep and create too much overhead for the network to progress adequately. Also, this mitigation attempt would still be vulnerable to a &gt;= 51% staked colluder.
Alternatively, transaction fees from submitted PoReps are pooled and distributed across validation-clients in proportion to the number of valid PoReps discounted by the number of invalid PoReps as voted by each validator-client. Thus invalid votes are directly dis-incentivized through this reward channel. Invalid votes that are revealed by archiver nodes as fishing PoReps, will not be discounted from the payout PoRep count.
Another collusion attack involves a validator-client who may take the strategy to ignore invalid PoReps from colluding archiver and vote them as valid. In this case, colluding archiver-clients would not have to store the data while still receiving rewards for validated PoReps. Additionally, colluding validator nodes would also receive rewards for validating these PoReps. To mitigate this attack, validators must randomly sample PoReps corresponding to the ledger block they are validating and because of this, there will be multiple validators that will receive the colluding archivers invalid submissions. These non-colluding validators will be incentivized to mark these PoReps as invalid as they have no way to determine whether the proposed invalid PoRep is actually a fishing PoRep, for which a confirmation vote would result in the validators stake being slashed.
In this case, the proportion of time a colluding pair will be successful has an upper limit determined by the % of stake of the network claimed by the colluding validator. This also sets bounds to the value of such an attack. For example, if a colluding validator controls 10% of the total validator stake, transaction fees will be lost \(likely sent to mining pool\) by the colluding archiver 90% of the time and so the attack vector is only profitable if the per-PoRep reward at least 90% higher than the average PoRep transaction fee. While, probabilistically, some colluding archiver-client PoReps will find their way to colluding validation-clients, the network can also monitor rates of paired \(validator + archiver\) discrepancies in voting patterns and censor identified colluders in these cases.

Some files were not shown because too many files have changed in this diff Show More