Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
390af512de | |||
2a9be901da | |||
d794fee66f | |||
9b66d4d363 | |||
bff8f2614b | |||
8f0648e8fc | |||
d81eaf69db | |||
b5935a3830 | |||
c1b07d0f21 | |||
a1579b5a47 | |||
77949a4be6 | |||
af58940964 | |||
21963b8c82 |
@ -1,42 +0,0 @@
|
||||
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
|
1
.buildkite/env/.gitignore
vendored
1
.buildkite/env/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/secrets_unencrypted.ejson
|
31
.buildkite/env/README.md
vendored
31
.buildkite/env/README.md
vendored
@ -1,31 +0,0 @@
|
||||
|
||||
[ejson](https://github.com/Shopify/ejson) and
|
||||
[ejson2env](https://github.com/Shopify/ejson2env) are used to manage access
|
||||
tokens and other secrets required for CI.
|
||||
|
||||
#### Setup
|
||||
```bash
|
||||
$ sudo gem install ejson ejson2env
|
||||
```
|
||||
|
||||
then obtain the necessary keypair and place it in `/opt/ejson/keys/`.
|
||||
|
||||
#### Usage
|
||||
Run the following command to decrypt the secrets into the environment:
|
||||
```bash
|
||||
eval $(ejson2env secrets.ejson)
|
||||
```
|
||||
|
||||
#### Managing secrets.ejson
|
||||
To decrypt `secrets.ejson` for modification, run:
|
||||
```bash
|
||||
$ ejson decrypt secrets.ejson -o secrets_unencrypted.ejson
|
||||
```
|
||||
|
||||
Edit, then run the following to re-encrypt the file **BEFORE COMMITING YOUR
|
||||
CHANGES**:
|
||||
```bash
|
||||
$ ejson encrypt secrets_unencrypted.ejson
|
||||
$ mv secrets_unencrypted.ejson secrets.ejson
|
||||
```
|
||||
|
15
.buildkite/env/secrets.ejson
vendored
15
.buildkite/env/secrets.ejson
vendored
@ -1,15 +0,0 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"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=]"
|
||||
}
|
||||
}
|
@ -1,42 +1,2 @@
|
||||
CI_BUILD_START=$(date +%s)
|
||||
export CI_BUILD_START
|
||||
|
||||
source ci/env.sh
|
||||
|
||||
#
|
||||
# Kill any running docker containers, which are potentially left over from the
|
||||
# previous CI job
|
||||
#
|
||||
(
|
||||
containers=$(docker ps -q)
|
||||
if [[ $(hostname) != metrics-solana-com && -n $containers ]]; then
|
||||
echo "+++ Killing stale docker containers"
|
||||
docker ps
|
||||
|
||||
# shellcheck disable=SC2086 # Don't want to double quote $containers
|
||||
docker kill $containers
|
||||
fi
|
||||
)
|
||||
|
||||
# Processes from previously aborted CI jobs seem to loiter, unclear why as one
|
||||
# would expect the buildkite-agent to clean up all child processes of the
|
||||
# aborted CI job.
|
||||
# But as a workaround for now manually kill some known loiterers. These
|
||||
# processes will all have the `init` process as their PPID:
|
||||
(
|
||||
victims=
|
||||
for name in bash cargo docker solana; do
|
||||
victims="$victims $(pgrep -u "$(id -u)" -P 1 -d \ $name)"
|
||||
done
|
||||
for victim in $victims; do
|
||||
echo "Killing pid $victim"
|
||||
kill -9 "$victim" || true
|
||||
done
|
||||
)
|
||||
|
||||
# HACK: These are in our docker images, need to be removed from CARGO_HOME
|
||||
# because we try to cache downloads across builds with CARGO_HOME
|
||||
# cargo lacks a facility for "system" tooling, always tries CARGO_HOME first
|
||||
cargo uninstall cargo-audit || true
|
||||
cargo uninstall svgbob_cli || true
|
||||
cargo uninstall mdbook || true
|
||||
|
@ -3,16 +3,15 @@
|
||||
#
|
||||
# Save target/ for the next CI build on this machine
|
||||
#
|
||||
(
|
||||
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"
|
||||
)
|
||||
if [[ -n $CARGO_TARGET_CACHE_NAME ]]; then
|
||||
(
|
||||
d=$HOME/cargo-target-cache/"$CARGO_TARGET_CACHE_NAME"
|
||||
mkdir -p "$d"
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$PWD" target "$d"
|
||||
du -hs "$d"
|
||||
)
|
||||
fi
|
||||
|
||||
#
|
||||
# Add job_stats data point
|
||||
|
@ -1,7 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
eval "$(ejson2env .buildkite/env/secrets.ejson)"
|
||||
#!/bin/bash -e
|
||||
|
||||
# Ensure the pattern "+++ ..." never occurs when |set -x| is set, as buildkite
|
||||
# interprets this as the start of a log group.
|
||||
@ -11,23 +8,20 @@ export PS4="++"
|
||||
#
|
||||
# Restore target/ from the previous CI build on this machine
|
||||
#
|
||||
(
|
||||
set -x
|
||||
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
|
||||
MAX_CACHE_SIZE=18 # gigabytes
|
||||
[[ -n "$CARGO_TARGET_CACHE_NAME" ]] || (
|
||||
d=$HOME/cargo-target-cache/"$CARGO_TARGET_CACHE_NAME"
|
||||
|
||||
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 "--- $d is too large, removing it"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1000000000 "$d")
|
||||
if [[ $cacheSizeInGB -gt 5 ]]; then
|
||||
echo "$d has gotten too large, removing it"
|
||||
rm -rf "$d"
|
||||
fi
|
||||
else
|
||||
echo "--- $d not present"
|
||||
fi
|
||||
|
||||
mkdir -p "$d"/target
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$d" "$d"/target .
|
||||
)
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script is used to upload the full buildkite pipeline. The steps defined
|
||||
# in the buildkite UI should simply be:
|
||||
#
|
||||
# steps:
|
||||
# - command: ".buildkite/pipeline-upload.sh"
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
buildkite-agent annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
buildkite-agent pipeline upload ci/buildkite-release.yml
|
||||
else
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
buildkite-agent annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
fi
|
||||
|
||||
if [[ $BUILDKITE_MESSAGE =~ GitBook: ]]; then
|
||||
buildkite-agent annotate --style info --context gitbook-ci-skip \
|
||||
"GitBook commit detected, CI skipped"
|
||||
exit
|
||||
fi
|
||||
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
fi
|
11
.codecov.yml
11
.codecov.yml
@ -1,12 +1,5 @@
|
||||
ignore:
|
||||
- "src/bin"
|
||||
coverage:
|
||||
range: 50..100
|
||||
round: down
|
||||
precision: 1
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
|
||||
comment:
|
||||
layout: "diff"
|
||||
behavior: default
|
||||
require_changes: no
|
||||
|
@ -1,5 +0,0 @@
|
||||
root: ./book/src
|
||||
|
||||
structure:
|
||||
readme: introduction.md
|
||||
summary: SUMMARY.md
|
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@ -1,6 +0,0 @@
|
||||
#### Problem
|
||||
|
||||
|
||||
|
||||
#### Proposed Solution
|
||||
|
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,5 +0,0 @@
|
||||
#### Problem
|
||||
|
||||
#### Summary of Changes
|
||||
|
||||
Fixes #
|
28
.github/RELEASE_TEMPLATE.md
vendored
28
.github/RELEASE_TEMPLATE.md
vendored
@ -1,28 +0,0 @@
|
||||
# Release v0.X.Y <milestone name>
|
||||
|
||||
fun blurb about the name, what's in the release
|
||||
|
||||
## Major Features And Improvements
|
||||
|
||||
* bulleted
|
||||
* list of features and improvements
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
* bulleted
|
||||
* list
|
||||
* of
|
||||
* protocol changes/breaks
|
||||
* API breaks
|
||||
* CLI changes
|
||||
* etc.
|
||||
|
||||
## Bug Fixes and Other Changes
|
||||
|
||||
* can be pulled from commit log, or synthesized
|
||||
|
||||
## Thanks to our Contributors
|
||||
|
||||
This release contains contributions from many people at Solana, as well as:
|
||||
|
||||
pull from commit log
|
24
.github/stale.yml
vendored
24
.github/stale.yml
vendored
@ -1,24 +0,0 @@
|
||||
only: pulls
|
||||
|
||||
# Number of days of inactivity before a pull request becomes stale
|
||||
daysUntilStale: 7
|
||||
|
||||
# Number of days of inactivity before a stale pull request is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- security
|
||||
|
||||
# Label to use when marking a pull request as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking a pull request as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
# Comment to post when closing a stale pull request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This stale pull request has been automatically closed.
|
||||
Thank you for your contributions.
|
26
.gitignore
vendored
26
.gitignore
vendored
@ -1,24 +1,16 @@
|
||||
/book/html/
|
||||
/book/src/tests.ok
|
||||
/book/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
/solana-metrics/
|
||||
/solana-metrics.tar.bz2
|
||||
Cargo.lock
|
||||
/target/
|
||||
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# node configuration files
|
||||
/config/
|
||||
/config-private/
|
||||
/config-drone/
|
||||
/config-validator/
|
||||
/config-client/
|
||||
/multinode-demo/test/config-client/
|
||||
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
log-*/
|
||||
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
# test temp files, ledgers, etc.
|
||||
/farf/
|
||||
|
45
.mergify.yml
45
.mergify.yml
@ -1,45 +0,0 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.21 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.21
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.21
|
||||
- name: v0.22 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.22
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.22
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.23
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.23
|
42
.travis.yml
42
.travis.yml
@ -1,42 +0,0 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- ci/publish-tarball.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+/
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
on_success: change
|
||||
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
|
||||
|
||||
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
|
268
CONTRIBUTING.md
268
CONTRIBUTING.md
@ -1,249 +1,53 @@
|
||||
# Solana Coding Guidelines
|
||||
Solana Coding Guidelines
|
||||
===
|
||||
|
||||
The goal of these guidelines is to improve developer productivity by allowing
|
||||
developers to jump into any file in the codebase and not need to adapt to
|
||||
inconsistencies in how the code is written. The codebase should appear as if it
|
||||
had been authored by a single developer. If you don't agree with a convention,
|
||||
submit a PR patching this document and let's discuss! Once the PR is accepted,
|
||||
*all* code should be updated as soon as possible to reflect the new
|
||||
The goal of these guidelines is to improve developer productivity by allowing developers to
|
||||
jump any file in the codebase and not need to adapt to inconsistencies in how the code is
|
||||
written. The codebase should appear as if it had been authored by a single developer. If you
|
||||
don't agree with a convention, submit a PR patching this document and let's discuss! Once
|
||||
the PR is accepted, *all* code should be updated as soon as possible to reflect the new
|
||||
conventions.
|
||||
|
||||
## Pull Requests
|
||||
Rust coding conventions
|
||||
---
|
||||
|
||||
Small, frequent PRs are much preferred to large, infrequent ones. A large PR is
|
||||
difficult to review, can block others from making progress, and can quickly get
|
||||
its author into "rebase hell". A large PR oftentimes arises when one change
|
||||
requires another, which requires another, and then another. When you notice
|
||||
those dependencies, put the fix into a commit of its own, then checkout a new
|
||||
branch, and cherry-pick it.
|
||||
* All Rust code is formatted using the latest version of `rustfmt`. Once installed, it will be
|
||||
updated automatically when you update the compiler with `rustup`.
|
||||
|
||||
```bash
|
||||
$ git commit -am "Fix foo, needed by bar"
|
||||
$ git checkout master
|
||||
$ git checkout -b fix-foo
|
||||
$ git cherry-pick fix-bar
|
||||
$ git push --set-upstream origin fix-foo
|
||||
```
|
||||
* All Rust code is linted with Clippy. If you'd prefer to ignore its advice, do so explicitly:
|
||||
|
||||
Open a PR to start the review process and then jump back to your original
|
||||
branch to keep making progress. Consider rebasing to make your fix the first
|
||||
commit:
|
||||
|
||||
```bash
|
||||
$ git checkout fix-bar
|
||||
$ git rebase -i master <Move fix-foo to top>
|
||||
```
|
||||
|
||||
Once the commit is merged, rebase the original branch to purge the
|
||||
cherry-picked commit:
|
||||
|
||||
```bash
|
||||
$ git pull --rebase upstream master
|
||||
```
|
||||
|
||||
### How big is too big?
|
||||
|
||||
If there are no functional changes, PRs can be very large and that's no
|
||||
problem. If, however, your changes are making meaningful changes or additions,
|
||||
then about 1,000 lines of changes is about the most you should ask a Solana
|
||||
maintainer to review.
|
||||
|
||||
### Should I send small PRs as I develop large, new components?
|
||||
|
||||
Add only code to the codebase that is ready to be deployed. If you are building
|
||||
a large library, consider developing it in a separate git repository. When it
|
||||
is ready to be integrated, the Solana maintainers will work with you to decide
|
||||
on a path forward. Smaller libraries may be copied in whereas very large ones
|
||||
may be pulled in with a package manager.
|
||||
|
||||
## Getting Pull Requests Merged
|
||||
|
||||
There is no single person assigned to watching GitHub PR queue and ushering you
|
||||
through the process. Typically, you will ask the person that wrote a component
|
||||
to review changes to it. You can find the author using `git blame` or asking on
|
||||
Discord. When working to get your PR merged, it's most important to understand
|
||||
that changing the code is your priority and not necessarily a priority of the
|
||||
person you need an approval from. Also, while you may interact the most with
|
||||
the component author, you should aim to be inclusive of others. Providing a
|
||||
detailed problem description is the most effective means of engaging both the
|
||||
component author and other potentially interested parties.
|
||||
|
||||
Consider opening all PRs as Draft Pull Requests first. Using a draft PR allows
|
||||
you to kickstart the CI automation, which typically takes between 10 and 30
|
||||
minutes to execute. Use that time to write a detailed problem description. Once
|
||||
the description is written and CI succeeds, click the "Ready to Review" button
|
||||
and add reviewers. Adding reviewers before CI succeeds is a fast path to losing
|
||||
reviewer engagement. Not only will they be notified and see the PR is not yet
|
||||
ready for them, they will also be bombarded them with additional notifications
|
||||
each time you push a commit to get past CI or until they "mute" the PR. Once
|
||||
muted, you'll need to reach out over some other medium, such as Discord, to
|
||||
request they have another look. When you use draft PRs, no notifications are
|
||||
sent when you push commits and edit the PR description. Use draft PRs
|
||||
liberally. Don't bug the humans until you have gotten past the bots.
|
||||
|
||||
### What should be in my PR description?
|
||||
|
||||
Reviewing code is hard work and generally involves an attempt to guess the
|
||||
author's intent at various levels. Please assume reviewer time is scarce and do
|
||||
what you can to make your PR as consumable as possible. Inspired by techniques
|
||||
for writing good whitepapers, the guidance here aims to maximize reviewer
|
||||
engagement.
|
||||
|
||||
Assume the reviewer will spend no more than a few seconds reading the PR title.
|
||||
If it doesn't describe a noteworthy change, don't expect the reviewer to click
|
||||
to see more.
|
||||
|
||||
Next, like the abstract of a whitepaper, the reviewer will spend ~30 seconds
|
||||
reading the PR problem description. If what is described there doesn't look
|
||||
more important than competing issues, don't expect the reviewer to read on.
|
||||
|
||||
Next, the reviewer will read the proposed changes. At this point, the reviewer
|
||||
needs to be convinced the proposed changes are a *good* solution to the problem
|
||||
described above. If the proposed changes, not the code changes, generates
|
||||
discussion, consider closing the PR and returning with a design proposal
|
||||
instead.
|
||||
|
||||
Finally, once the reviewer understands the problem and agrees with the approach
|
||||
to solving it, the reviewer will view the code changes. At this point, the
|
||||
reviewer is simply looking to see if the implementation actually implements
|
||||
what was proposed and if that implementation is maintainable. When a concise,
|
||||
readable test for each new code path is present, the reviewer can safely ignore
|
||||
the details of its implementation. When those tests are missing, expect to
|
||||
either lose engagement or get a pile of review comments as the reviewer
|
||||
attempts to consider every ambiguity in your implementation.
|
||||
|
||||
### The PR Title
|
||||
|
||||
The PR title should contain a brief summary of the change, from the perspective
|
||||
of the user. Examples of good titles:
|
||||
|
||||
* Add rent to accounts
|
||||
* Fix out-of-memory error in validator
|
||||
* Clean up `process_message()` in runtime
|
||||
|
||||
The conventions here are all the same as a good git commit title:
|
||||
|
||||
* First word capitalized and in the imperative mood, not past tense ("add", not
|
||||
"added")
|
||||
* No trailing period
|
||||
* What was done, whom it was done to, and in what context
|
||||
|
||||
### The PR Problem Statement
|
||||
|
||||
The git repo implements a product with various features. The problem statement
|
||||
should describe how the product is missing a feature, how a feature is
|
||||
incomplete, or how the implementation of a feature is somehow undesirable. If
|
||||
an issue being fixed already describes the problem, go ahead and copy-paste it.
|
||||
As mentioned above, reviewer time is scarce. Given a queue of PRs to review,
|
||||
the reviewer may ignore PRs that expect them to click through links to see if
|
||||
the PR warrants attention.
|
||||
|
||||
### The Proposed Changes
|
||||
|
||||
Typically the content under the "Proposed changes" section will be a bulleted
|
||||
list of steps taken to solve the problem. Oftentimes, the list is identical to
|
||||
the subject lines of the git commits contained in the PR. It's especially
|
||||
generous (and not expected) to rebase or reword commits such that each change
|
||||
matches the logical flow in your PR description.
|
||||
|
||||
### When will my PR be reviewed?
|
||||
|
||||
PRs are typically reviewed and merged in under 7 days. If your PR has been open
|
||||
for longer, it's a strong indicator that the reviewers aren't confident the
|
||||
change meets the quality standards of the codebase. You might consider closing
|
||||
it and coming back with smaller PRs and longer descriptions detailing what
|
||||
problem it solves and how it solves it. Old PRs will be marked stale and then
|
||||
closed automatically 7 days later.
|
||||
|
||||
### How to manage review feedback?
|
||||
|
||||
After a reviewer provides feedback, you can quickly say "acknowledged, will
|
||||
fix" using a thumb's up emoji. If you're confident your fix is exactly as
|
||||
prescribed, add a reply "Fixed in COMMIT\_HASH" and mark the comment as
|
||||
resolved. If you're not sure, reply "Is this what you had in mind?
|
||||
COMMIT\_HASH" and if so, the reviewer will reply and mark the conversation as
|
||||
resolved. Marking conversations as resolved is an excellent way to engage more
|
||||
reviewers. Leaving conversations open may imply the PR is not yet ready for
|
||||
additional review.
|
||||
|
||||
### When will my PR be re-reviewed?
|
||||
|
||||
Recall that once your PR is opened, a notification is sent every time you push
|
||||
a commit. After a reviewer adds feedback, they won't be checking on the status
|
||||
of that feedback after every new commit. Instead, directly mention the reviewer
|
||||
when you feel your PR is ready for another pass.
|
||||
|
||||
## Draft Pull Requests
|
||||
|
||||
If you want early feedback on your PR, use GitHub's "Draft Pull Request"
|
||||
mechanism. Draft PRs are a convenient way to collaborate with the Solana
|
||||
maintainers without triggering notifications as you make changes. When you feel
|
||||
your PR is ready for a broader audience, you can transition your draft PR to a
|
||||
standard PR with the click of a button.
|
||||
|
||||
Do not add reviewers to draft PRs. GitHub doesn't automatically clear
|
||||
approvals when you click "Ready for Review", so a review that meant "I approve
|
||||
of the direction" suddenly has the appearance of "I approve of these changes."
|
||||
Instead, add a comment that mentions the usernames that you would like a review
|
||||
from. Ask explicitly what you would like feedback on.
|
||||
|
||||
## Rust coding conventions
|
||||
|
||||
* All Rust code is formatted using the latest version of `rustfmt`. Once
|
||||
installed, it will be updated automatically when you update the compiler with
|
||||
`rustup`.
|
||||
|
||||
* All Rust code is linted with Clippy. If you'd prefer to ignore its advice, do
|
||||
so explicitly:
|
||||
|
||||
```rust #[allow(clippy::too_many_arguments)] ```
|
||||
```rust
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
```
|
||||
|
||||
Note: Clippy defaults can be overridden in the top-level file `.clippy.toml`.
|
||||
|
||||
* For variable names, when in doubt, spell it out. The mapping from type names
|
||||
to variable names is to lowercase the type name, putting an underscore before
|
||||
each capital letter. Variable names should *not* be abbreviated unless being
|
||||
used as closure arguments and the brevity improves readability. When a function
|
||||
has multiple instances of the same type, qualify each with a prefix and
|
||||
underscore (i.e. alice\_keypair) or a numeric suffix (i.e. tx0).
|
||||
* For variable names, when in doubt, spell it out. The mapping from type names to variable names
|
||||
is to lowercase the type name, putting an underscore before each capital letter. Variable names
|
||||
should *not* be abbreviated unless being used as closure arguments and the brevity improves
|
||||
readability. When a function has multiple instances of the same type, qualify each with a
|
||||
prefix and underscore (i.e. alice_keypair) or a numeric suffix (i.e. tx0).
|
||||
|
||||
* For function and method names, use `<verb>_<subject>`. For unit tests, that
|
||||
verb should always be `test` and for benchmarks the verb should always be
|
||||
`bench`. Avoid namespacing function names with some arbitrary word. Avoid
|
||||
abbreviating words in function names.
|
||||
* For function and method names, use `<verb>_<subject>`. For unit tests, that verb should
|
||||
always be `test` and for benchmarks the verb should always be `bench`. Avoid namespacing
|
||||
function names with some arbitrary word. Avoid abreviating words in function names.
|
||||
|
||||
* As they say, "When in Rome, do as the Romans do." A good patch should
|
||||
acknowledge the coding conventions of the code that surrounds it, even in the
|
||||
case where that code has not yet been updated to meet the conventions described
|
||||
here.
|
||||
* As they say, "When in Rome, do as the Romans do." A good patch should acknowledge the coding
|
||||
conventions of the code that surrounds it, even in the case where that code has not yet been
|
||||
updated to meet the conventions described here.
|
||||
|
||||
|
||||
## Terminology
|
||||
Terminology
|
||||
---
|
||||
|
||||
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.
|
||||
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](book/src/terminology.md)
|
||||
Some terms we currently use regularly in the codebase:
|
||||
|
||||
* fullnode: n. A fully participating network node.
|
||||
* hash: n. A SHA-256 Hash.
|
||||
* keypair: n. A Ed25519 key-pair, containing a public and private key.
|
||||
* pubkey: n. The public key of a Ed25519 key-pair.
|
||||
* sigverify: v. To verify a Ed25519 digital signature.
|
||||
|
||||
## Design Proposals
|
||||
|
||||
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
|
||||
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.
|
||||
3. Submit PRs that implement the proposal. When the implementation reveals the
|
||||
need for tweaks to the proposal, be sure to update the proposal and have that
|
||||
change reviewed by the same people as in step 1.
|
||||
4. Once the implementation is complete, submit a PR that moves the link from
|
||||
the Accepted Proposals to the Implemented Proposals section.
|
||||
|
6006
Cargo.lock
generated
6006
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
169
Cargo.toml
169
Cargo.toml
@ -1,59 +1,114 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking-bench",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"faucet",
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis-programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"ledger",
|
||||
"ledger-tool",
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/btc_spv",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/ownable",
|
||||
"programs/stake",
|
||||
"programs/storage",
|
||||
"programs/vest",
|
||||
"programs/vote",
|
||||
"archiver",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"scripts",
|
||||
"sys-tuner",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"fixed-buf",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"watchtower",
|
||||
[package]
|
||||
name = "solana"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.8.0"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "http://solana.com/"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
authors = [
|
||||
"Anatoly Yakovenko <anatoly@solana.com>",
|
||||
"Greg Fitzgerald <greg@solana.com>",
|
||||
"Stephen Akridge <stephen@solana.com>",
|
||||
"Michael Vines <mvines@solana.com>",
|
||||
"Rob Walker <rob@solana.com>",
|
||||
"Pankaj Garg <pankaj@solana.com>",
|
||||
"Tyera Eulberg <tyera@solana.com>",
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
"programs/move_loader",
|
||||
"programs/librapay",
|
||||
]
|
||||
[[bin]]
|
||||
name = "solana-bench-tps"
|
||||
path = "src/bin/bench-tps.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-bench-streamer"
|
||||
path = "src/bin/bench-streamer.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-drone"
|
||||
path = "src/bin/drone.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-fullnode"
|
||||
path = "src/bin/fullnode.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-fullnode-config"
|
||||
path = "src/bin/fullnode-config.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-genesis"
|
||||
path = "src/bin/genesis.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-ledger-tool"
|
||||
path = "src/bin/ledger-tool.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-keygen"
|
||||
path = "src/bin/keygen.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-wallet"
|
||||
path = "src/bin/wallet.rs"
|
||||
|
||||
[badges]
|
||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
ipv6 = []
|
||||
cuda = []
|
||||
erasure = []
|
||||
test = []
|
||||
|
||||
[dependencies]
|
||||
atty = "0.2"
|
||||
bincode = "1.0.0"
|
||||
bs58 = "0.2.0"
|
||||
byteorder = "1.2.1"
|
||||
bytes = "0.4"
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
clap = "2.31"
|
||||
dirs = "1.0.2"
|
||||
env_logger = "0.5.12"
|
||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||
getopts = "0.2"
|
||||
influx_db_client = "0.3.4"
|
||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc", rev = "4b6060b" }
|
||||
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc", rev = "4b6060b" }
|
||||
jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc", rev = "4b6060b" }
|
||||
itertools = "0.7.8"
|
||||
log = "0.4.2"
|
||||
matches = "0.1.6"
|
||||
nix = "0.11.0"
|
||||
pnet_datalink = "0.21.0"
|
||||
rand = "0.5.1"
|
||||
rayon = "1.0.0"
|
||||
reqwest = "0.8.6"
|
||||
ring = "0.13.2"
|
||||
sha2 = "0.7.0"
|
||||
serde = "1.0.27"
|
||||
serde_derive = "1.0.27"
|
||||
serde_json = "1.0.10"
|
||||
socket2 = "0.3.8"
|
||||
sys-info = "0.5.6"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[[bench]]
|
||||
name = "bank"
|
||||
|
||||
[[bench]]
|
||||
name = "banking_stage"
|
||||
|
||||
[[bench]]
|
||||
name = "ledger"
|
||||
|
||||
[[bench]]
|
||||
name = "signature"
|
||||
|
||||
[[bench]]
|
||||
name = "sigverify"
|
||||
|
408
README.md
408
README.md
@ -1,9 +1,9 @@
|
||||
[](https://crates.io/crates/solana-core)
|
||||
[](https://docs.rs/solana-core)
|
||||
[](https://buildkite.com/solana-labs/solana/builds?branch=master)
|
||||
[](https://crates.io/crates/solana)
|
||||
[](https://docs.rs/solana)
|
||||
[](https://solana-ci-gate.herokuapp.com/buildkite_public_log?https://buildkite.com/solana-labs/solana/builds/latest/master)
|
||||
[](https://codecov.io/gh/solana-labs/solana)
|
||||
|
||||
Blockchain Rebuilt for Scale
|
||||
Blockchain, Rebuilt for Scale
|
||||
===
|
||||
|
||||
Solana™ is a new blockchain architecture built from the ground up for scale. The architecture supports
|
||||
@ -21,48 +21,246 @@ It's possible for a centralized database to process 710,000 transactions per sec
|
||||
|
||||
> 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.
|
||||
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 in route towards that theoretical limit of 710,000 transactions per second.
|
||||
|
||||
Architecture
|
||||
|
||||
Testnet Demos
|
||||
===
|
||||
|
||||
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
|
||||
The Solana repo 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.
|
||||
|
||||
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
|
||||
For all four variations, you'd need the latest Rust toolchain and the Solana
|
||||
source code:
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
First, install Rust's package manager Cargo.
|
||||
|
||||
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.
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
```
|
||||
|
||||
### 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)
|
||||
Now checkout the code from github:
|
||||
|
||||
### 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)
|
||||
```bash
|
||||
$ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana
|
||||
```
|
||||
|
||||
The demo code is sometimes broken between releases as we add new low-level
|
||||
features, so if this is your first time running the demo, you'll improve
|
||||
your odds of success if you check out the
|
||||
[latest release](https://github.com/solana-labs/solana/releases)
|
||||
before proceeding:
|
||||
|
||||
```bash
|
||||
$ git checkout v0.8.0
|
||||
```
|
||||
|
||||
Configuration Setup
|
||||
---
|
||||
|
||||
The network is initialized with a genesis ledger and leader/validator configuration files.
|
||||
These files can be generated by running the following script.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/setup.sh
|
||||
```
|
||||
|
||||
Drone
|
||||
---
|
||||
|
||||
In order for the leader, client and validators to work, we'll need to
|
||||
spin up a drone to give out some test tokens. The drone delivers Milton
|
||||
Friedman-style "air drops" (free tokens to requesting clients) to be used in
|
||||
test transactions.
|
||||
|
||||
Start the drone on the leader node with:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/drone.sh
|
||||
```
|
||||
|
||||
Singlenode Testnet
|
||||
---
|
||||
|
||||
Before you start a fullnode, make sure you know the IP address of the machine you
|
||||
want to be the 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 server in a separate shell:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/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 drone if it doesn't have any.
|
||||
The drone does not need to be running for subsequent leader starts.
|
||||
|
||||
Multinode Testnet
|
||||
---
|
||||
|
||||
To run a multinode testnet, after starting a leader node, spin up some validator nodes in
|
||||
separate shells:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator.sh
|
||||
```
|
||||
|
||||
To run a performance-enhanced leader or validator (on Linux),
|
||||
[CUDA 9.2](https://developer.nvidia.com/cuda-downloads) must be installed on
|
||||
your system:
|
||||
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/leader.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
|
||||
```
|
||||
|
||||
|
||||
Testnet Client Demo
|
||||
---
|
||||
|
||||
Now that your singlenode or multinode testnet is up and running let's send it
|
||||
some transactions!
|
||||
|
||||
In a separate shell start the client:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/client.sh # runs against localhost by default
|
||||
```
|
||||
|
||||
What just happened? The client demo spins up several threads to send 500,000 transactions
|
||||
to the testnet as quickly as it can. The client then pings the testnet periodically to see
|
||||
how many transactions it processed in that time. Take note that the demo intentionally
|
||||
floods the network with UDP packets, such that the network will almost certainly drop a
|
||||
bunch of them. This ensures the testnet has an opportunity to reach 710k TPS. The client
|
||||
demo completes after it has convinced itself the testnet won't process any additional
|
||||
transactions. You should see several TPS measurements printed to the screen. In the
|
||||
multinode variation, you'll see TPS measurements for each validator node as well.
|
||||
|
||||
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
|
||||
$ ./multinode-demo/client.sh --network $(dig +short testnet.solana.com):8001 --identity config-private/client-id.json --duration 60
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
|
||||
Linux Snap
|
||||
---
|
||||
A Linux [Snap](https://snapcraft.io/) is available, which can be used to
|
||||
easily get Solana running on supported Linux systems without building anything
|
||||
from source. The `edge` Snap channel is updated daily with the latest
|
||||
development from the `master` branch. To install:
|
||||
|
||||
```bash
|
||||
$ sudo snap install solana --edge --devmode
|
||||
```
|
||||
|
||||
(`--devmode` flag is required only for `solana.fullnode-cuda`)
|
||||
|
||||
Once installed the usual Solana programs will be available as `solona.*` instead
|
||||
of `solana-*`. For example, `solana.fullnode` instead of `solana-fullnode`.
|
||||
|
||||
Update to the latest version at any time with:
|
||||
|
||||
```bash
|
||||
$ snap info solana
|
||||
$ sudo snap refresh solana --devmode
|
||||
```
|
||||
|
||||
### Daemon support
|
||||
The snap supports running a leader, validator or leader+drone node as a system
|
||||
daemon.
|
||||
|
||||
Run `sudo snap get solana` to view the current daemon configuration. To view
|
||||
daemon logs:
|
||||
1. Run `sudo snap logs -n=all solana` to view the daemon initialization log
|
||||
2. Runtime logging can be found under `/var/snap/solana/current/leader/`,
|
||||
`/var/snap/solana/current/validator/`, or `/var/snap/solana/current/drone/` depending
|
||||
on which `mode=` was selected. Within each log directory the file `current`
|
||||
contains the latest log, and the files `*.s` (if present) contain older rotated
|
||||
logs.
|
||||
|
||||
Disable the daemon at any time by running:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=
|
||||
```
|
||||
|
||||
Runtime configuration files for the daemon can be found in
|
||||
`/var/snap/solana/current/config`.
|
||||
|
||||
#### Leader daemon
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader
|
||||
```
|
||||
|
||||
If CUDA is available:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader enable-cuda=1
|
||||
```
|
||||
|
||||
`rsync` must be configured and running on the leader.
|
||||
|
||||
1. Ensure rsync is installed with `sudo apt-get -y install rsync`
|
||||
2. Edit `/etc/rsyncd.conf` to include the following
|
||||
```
|
||||
[config]
|
||||
path = /var/snap/solana/current/config
|
||||
hosts allow = *
|
||||
read only = true
|
||||
```
|
||||
3. Run `sudo systemctl enable rsync; sudo systemctl start rsync`
|
||||
4. Test by running `rsync -Pzravv rsync://<ip-address-of-leader>/config
|
||||
solana-config` from another machine. **If the leader is running on a cloud
|
||||
provider it may be necessary to configure the Firewall rules to permit ingress
|
||||
to port tcp:873, tcp:9900 and the port range udp:8000-udp:10000**
|
||||
|
||||
|
||||
To run both the Leader and Drone:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader+drone
|
||||
|
||||
```
|
||||
|
||||
#### Validator daemon
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator
|
||||
|
||||
```
|
||||
If CUDA is available:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator enable-cuda=1
|
||||
```
|
||||
|
||||
By default the validator will connect to **testnet.solana.com**, override
|
||||
the leader IP address by running:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator leader-address=127.0.0.1 #<-- change IP address
|
||||
```
|
||||
|
||||
It's assumed that the leader will be running `rsync` configured as described in
|
||||
the previous **Leader daemon** section.
|
||||
|
||||
Developing
|
||||
===
|
||||
@ -75,10 +273,10 @@ Install rustc, cargo and rustfmt:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt
|
||||
$ rustup component add rustfmt-preview
|
||||
```
|
||||
|
||||
If your rustc version is lower than 1.39.0, please update it:
|
||||
If your rustc version is lower than 1.26.1, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@ -87,7 +285,7 @@ $ rustup update
|
||||
On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc. On Ubuntu:
|
||||
|
||||
```bash
|
||||
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev llvm clang
|
||||
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev
|
||||
```
|
||||
|
||||
Download the source code:
|
||||
@ -97,17 +295,6 @@ $ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana
|
||||
```
|
||||
|
||||
Build
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
Then to run a minimal local cluster
|
||||
```bash
|
||||
$ ./run.sh
|
||||
```
|
||||
|
||||
Testing
|
||||
---
|
||||
|
||||
@ -117,91 +304,43 @@ Run the test suite:
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
Local Testnet
|
||||
To emulate all the tests that will run on a Pull Request, run:
|
||||
|
||||
```bash
|
||||
$ ./ci/run-local.sh
|
||||
```
|
||||
|
||||
Debugging
|
||||
---
|
||||
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
|
||||
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.
|
||||
|
||||
Remote Testnets
|
||||
---
|
||||
For example, to enable info everywhere and debug only in the solana::banking_stage module:
|
||||
|
||||
We maintain several testnets:
|
||||
```bash
|
||||
$ export RUST_LOG=info,solana::banking_stage=debug
|
||||
```
|
||||
|
||||
* `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
|
||||
Generally we are using debug for infrequent debug messages, trace for potentially frequent
|
||||
messages and info for performance-related logging.
|
||||
|
||||
## Deploy process
|
||||
You can also attach to a running process with GDB. The leader's process is named
|
||||
_solana-fullnode_:
|
||||
|
||||
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
|
||||
```bash
|
||||
$ sudo gdb
|
||||
attach <PID>
|
||||
set logging on
|
||||
thread apply all bt
|
||||
```
|
||||
|
||||
This will dump all the threads stack traces into gdb.txt
|
||||
|
||||
Benchmarking
|
||||
---
|
||||
|
||||
First install the nightly build of rustc. `cargo bench` requires use of the
|
||||
unstable features only available in the nightly build.
|
||||
First install the nightly build of rustc. `cargo bench` requires unstable features:
|
||||
|
||||
```bash
|
||||
$ rustup install nightly
|
||||
@ -210,24 +349,33 @@ $ rustup install nightly
|
||||
Run the benchmarks:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly bench
|
||||
$ cargo +nightly bench --features="unstable"
|
||||
```
|
||||
|
||||
Release Process
|
||||
---
|
||||
The release process for this project is described [here](RELEASE.md).
|
||||
The release process for this project is described [here](rfcs/rfc-005-branches-tags-and-channels.md).
|
||||
|
||||
|
||||
Code coverage
|
||||
---
|
||||
|
||||
To generate code coverage statistics:
|
||||
To generate code coverage statistics, install cargo-cov. Note: the tool currently only works
|
||||
in Rust nightly.
|
||||
|
||||
```bash
|
||||
$ scripts/coverage.sh
|
||||
$ open target/cov/lcov-local/index.html
|
||||
$ cargo +nightly install cargo-cov
|
||||
```
|
||||
|
||||
Run cargo-cov and generate a report:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly cov test
|
||||
$ cargo +nightly cov report --open
|
||||
```
|
||||
|
||||
The coverage report will be written to `./target/cov/report/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
|
||||
|
204
RELEASE.md
204
RELEASE.md
@ -1,204 +0,0 @@
|
||||
# Solana Release process
|
||||
|
||||
## Branches and Tags
|
||||
|
||||
```
|
||||
========================= master branch (edge channel) =======================>
|
||||
\ \ \
|
||||
\___v0.7.0 tag \ \
|
||||
\ \ v0.9.0 tag__\
|
||||
\ v0.8.0 tag__\ \
|
||||
v0.7.1 tag__\ \ v0.9 branch (beta channel)
|
||||
\___v0.7.2 tag \___v0.8.1 tag
|
||||
\ \
|
||||
\ \
|
||||
v0.7 branch v0.8 branch (stable channel)
|
||||
|
||||
```
|
||||
|
||||
### master branch
|
||||
All new development occurs on the `master` branch.
|
||||
|
||||
Bug fixes that affect a `vX.Y` branch are first made on `master`. This is to
|
||||
allow a fix some soak time on `master` before it is applied to one or more
|
||||
stabilization branches.
|
||||
|
||||
Merging to `master` first also helps ensure that fixes applied to one release
|
||||
are present for future releases. (Sometimes the joy of landing a critical
|
||||
release blocker in a branch causes you to forget to propagate back to
|
||||
`master`!)"
|
||||
|
||||
Once the bug fix lands on `master` it is cherry-picked into the `vX.Y` branch
|
||||
and potentially the `vX.Y-1` branch. The exception to this rule is when a bug
|
||||
fix for `vX.Y` doesn't apply to `master` or `vX.Y-1`.
|
||||
|
||||
Immediately after a new stabilization branch is forged, the `Cargo.toml` minor
|
||||
version (*Y*) in the `master` branch is incremented by the release engineer.
|
||||
Incrementing the major version of the `master` branch is outside the scope of
|
||||
this document.
|
||||
|
||||
### v*X.Y* stabilization branches
|
||||
These are stabilization branches for a given milestone. They are created off
|
||||
the `master` branch as late as possible prior to the milestone release.
|
||||
|
||||
### v*X.Y.Z* release tag
|
||||
The release tags are created as desired by the owner of the given stabilization
|
||||
branch, and cause that *X.Y.Z* release to be shipped to https://crates.io
|
||||
|
||||
Immediately after a new v*X.Y.Z* branch tag has been created, the `Cargo.toml`
|
||||
patch version number (*Z*) of the stabilization branch is incremented by the
|
||||
release engineer.
|
||||
|
||||
## Channels
|
||||
Channels are used by end-users (humans and bots) to consume the branches
|
||||
described in the previous section, so they may automatically update to the most
|
||||
recent version matching their desired stability.
|
||||
|
||||
There are three release channels that map to branches as follows:
|
||||
* edge - tracks the `master` branch, least stable.
|
||||
* beta - tracks the largest (and latest) `vX.Y` stabilization branch, more stable.
|
||||
* stable - tracks the second largest `vX.Y` stabilization branch, most stable.
|
||||
|
||||
## Steps to Create a Branch
|
||||
|
||||
### Create the new branch
|
||||
1. Check out the latest commit on `master` branch:
|
||||
```
|
||||
git fetch --all
|
||||
git checkout upstream/master
|
||||
```
|
||||
1. Determine the new branch name. The name should be "v" + the first 2 version fields
|
||||
from Cargo.toml. For example, a Cargo.toml with version = "0.9.0" implies
|
||||
the next branch name is "v0.9".
|
||||
1. Create the new branch and push this branch to the `solana` repository:
|
||||
```
|
||||
git checkout -b <branchname>
|
||||
git push -u origin <branchname>
|
||||
```
|
||||
|
||||
### Update master branch with the next version
|
||||
|
||||
1. After the new branch has been created and pushed, update the Cargo.toml files on **master** to the next semantic version (e.g. 0.9.0 -> 0.10.0) with:
|
||||
```
|
||||
scripts/increment-cargo-version.sh minor
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the `master` branch with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y to X.Y+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
1. Confirm that your freshly cut release branch is shown as `BETA_CHANNEL` and the previous release branch as `STABLE_CHANNEL`:
|
||||
```
|
||||
ci/channel_info.sh
|
||||
```
|
||||
|
||||
## Steps to Create a Release
|
||||
|
||||
### Create the Release Tag on GitHub
|
||||
|
||||
1. Go to [GitHub's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
1. Click "Draft new release". The release tag must exactly match the `version`
|
||||
field in `/Cargo.toml` prefixed by `v`.
|
||||
1. If the Cargo.toml verion field is **0.12.3**, then the release tag must be **v0.12.3**
|
||||
1. Make sure the Target Branch field matches the branch you want to make a release on.
|
||||
1. If you want to release v0.12.0, the target branch must be v0.12
|
||||
1. If this is the first release on the branch (e.g. v0.13.**0**), paste in [this
|
||||
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md). Engineering Lead can provide summary contents for release notes if needed.
|
||||
1. Click "Save Draft", then confirm the release notes look good and the tag name and branch are correct. Go back into edit the release and click "Publish release" when ready.
|
||||
|
||||
### Update release branch with the next patch version
|
||||
|
||||
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
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the **release branch** with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y.Z to X.Y.Z+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
|
||||
### Verify release automation success
|
||||
1. Go to [Solana Releases](https://github.com/solana-labs/solana/releases) and click on the latest release that you just published. Verify that all of the build artifacts are present. This can take up to 90 minutes after creating the tag.
|
||||
1. The `solana-secondary` Buildkite pipeline handles creating the binary tarballs and updated crates. Look for a job under the tag name of the release: https://buildkite.com/solana-labs/solana-secondary
|
||||
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
|
||||
|
||||
### Update documentation
|
||||
TODO: Documentation update procedure is WIP as we move to gitbook
|
||||
|
||||
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.
|
||||
|
||||
#### Publish updated Book
|
||||
We maintain three copies of the "book" as official documentation:
|
||||
|
||||
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.
|
||||
|
||||
#### Update testnet schedules
|
||||
|
||||
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
|
||||
testnet-management -> Pipeline Settings -> Schedules
|
||||
Or just click here:
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules
|
||||
|
||||
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
|
||||
|
||||
On each schedule:
|
||||
1. Set TESTNET_TAG environment variable to the desired release tag.
|
||||
1. Example, TESTNET_TAG=v0.13.2
|
||||
1. Set the Build Branch to the branch that TESTNET_TAG is from.
|
||||
1. Example: v0.13
|
||||
|
||||
#### Restart the testnet
|
||||
|
||||
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
|
||||
|
||||
1. Go to https://buildkite.com/solana-labs/testnet-management
|
||||
2. Click "New Build" and use the following settings, then click "Create Build"
|
||||
1. Commit: HEAD
|
||||
1. Branch: [channel branch as set in the schedules]
|
||||
1. Environment Variables:
|
||||
```
|
||||
TESTNET=testnet
|
||||
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
|
||||
TESTNET_OP=create-and-start
|
||||
```
|
||||
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
testnet.solana.com is available
|
2
archiver/.gitignore
vendored
2
archiver/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "0.22.2"
|
||||
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.2" }
|
||||
solana-core = { path = "../core", version = "0.22.2" }
|
||||
solana-logger = { path = "../logger", version = "0.22.2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.22.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.22.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.22.2" }
|
||||
|
@ -1,147 +0,0 @@
|
||||
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();
|
||||
}
|
2
banking-bench/.gitignore
vendored
2
banking-bench/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "0.22.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "0.22.2" }
|
||||
solana-ledger = { path = "../ledger", version = "0.22.2" }
|
||||
solana-logger = { path = "../logger", version = "0.22.2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.22.2" }
|
||||
solana-measure = { path = "../measure", version = "0.22.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.22.2" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
@ -1,304 +0,0 @@
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
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_runtime::bank::Bank;
|
||||
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>>,
|
||||
ref_tx_count: usize,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
) -> bool {
|
||||
let mut total = 0;
|
||||
let now = Instant::now();
|
||||
let mut no_bank = false;
|
||||
loop {
|
||||
if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::from_millis(10))
|
||||
{
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
if now.elapsed().as_secs() > 60 {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
trace!("no bank");
|
||||
no_bank = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !no_bank {
|
||||
assert!(total >= ref_tx_count);
|
||||
}
|
||||
no_bank
|
||||
}
|
||||
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
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();
|
||||
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
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Config {
|
||||
packets_per_batch: usize,
|
||||
chunk_len: usize,
|
||||
num_threads: usize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn get_transactions_index(&self, chunk_index: usize) -> usize {
|
||||
chunk_index * (self.chunk_len / self.num_threads) * self.packets_per_batch
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_as_usize(bytes: &[u8]) -> usize {
|
||||
bytes[0] as usize | (bytes[1] as usize) << 8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
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,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, txes);
|
||||
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
//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");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
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, &blocktree, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
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 = 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_BATCH,
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
for _ in 0..ITERS {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
for (i, v) in verified[start..start + chunk_len]
|
||||
.chunks(chunk_len / num_threads)
|
||||
.enumerate()
|
||||
{
|
||||
let mut byte = 0;
|
||||
let index = config.get_transactions_index(start + i);
|
||||
if index < transactions.len() {
|
||||
byte = bytes_as_usize(transactions[index].signatures[0].as_ref());
|
||||
}
|
||||
trace!(
|
||||
"sending... {}..{} {} v.len: {} sig: {} transactions.len: {} index: {}",
|
||||
start + i,
|
||||
start + chunk_len,
|
||||
timestamp(),
|
||||
v.len(),
|
||||
byte,
|
||||
transactions.len(),
|
||||
index,
|
||||
);
|
||||
for xv in v {
|
||||
sent += xv.packets.len();
|
||||
}
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
let start_tx_index = config.get_transactions_index(start);
|
||||
let end_tx_index = config.get_transactions_index(start + chunk_len);
|
||||
for tx in &transactions[start_tx_index..end_tx_index] {
|
||||
loop {
|
||||
if bank.get_signature_status(&tx.signatures[0]).is_some() {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
bank.transaction_count(),
|
||||
txs_processed
|
||||
);
|
||||
assert!(txs_processed < bank.transaction_count());
|
||||
txs_processed = bank.transaction_count();
|
||||
tx_total += duration_as_us(&now.elapsed());
|
||||
|
||||
let mut poh_time = Measure::start("poh_time");
|
||||
poh_recorder.lock().unwrap().reset(
|
||||
bank.last_blockhash(),
|
||||
bank.slot(),
|
||||
Some((bank.slot(), bank.slot() + 1)),
|
||||
);
|
||||
poh_time.stop();
|
||||
|
||||
let mut new_bank_time = Measure::start("new_bank");
|
||||
let new_bank = Bank::new_from_parent(&bank, &collector, bank.slot() + 1);
|
||||
new_bank_time.stop();
|
||||
|
||||
let mut insert_time = Measure::start("insert_time");
|
||||
bank_forks.insert(new_bank);
|
||||
bank = bank_forks.working_bank();
|
||||
insert_time.stop();
|
||||
|
||||
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);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
"new_bank_time: {}us insert_time: {}us poh_time: {}us",
|
||||
new_bank_time.as_us(),
|
||||
insert_time.as_us(),
|
||||
poh_time.as_us(),
|
||||
);
|
||||
} else {
|
||||
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 += duration_as_us(&now.elapsed());
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
txes / CHUNKS,
|
||||
sent,
|
||||
);
|
||||
|
||||
if bank.slot() > 0 && bank.slot() % 16 == 0 {
|
||||
for tx in transactions.iter_mut() {
|
||||
tx.message.recent_blockhash = bank.last_blockhash();
|
||||
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_BATCH);
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
}
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
total / ITERS as u64,
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
tx_total / ITERS as u64,
|
||||
);
|
||||
|
||||
drop(verified_sender);
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
banking_stage.join().unwrap();
|
||||
debug!("waited for banking_stage");
|
||||
poh_service.join().unwrap();
|
||||
sleep(Duration::from_secs(1));
|
||||
debug!("waited for poh_service");
|
||||
}
|
||||
let _unused = Blocktree::destroy(&ledger_path);
|
||||
}
|
4
bench-exchange/.gitignore
vendored
4
bench-exchange/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@ -1,41 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.22.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
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.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.2" }
|
||||
solana-core = { path = "../core", version = "0.22.2" }
|
||||
solana-genesis = { path = "../genesis", version = "0.22.2" }
|
||||
solana-client = { path = "../client", version = "0.22.2" }
|
||||
solana-faucet = { path = "../faucet", version = "0.22.2" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "0.22.2" }
|
||||
solana-logger = { path = "../logger", version = "0.22.2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.22.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.22.2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.22.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.22.2" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "0.22.2" }
|
@ -1,479 +0,0 @@
|
||||
# token-exchange
|
||||
Solana Token Exchange Bench
|
||||
|
||||
If you can't wait; jump to [Running the exchange](#Running-the-exchange) to
|
||||
learn how to start and interact with the exchange.
|
||||
|
||||
### Table of Contents
|
||||
[Overview](#Overview)<br>
|
||||
[Premise](#Premise)<br>
|
||||
[Exchange startup](#Exchange-startup)<br>
|
||||
[Order Requests](#Trade-requests)<br>
|
||||
[Order Cancellations](#Trade-cancellations)<br>
|
||||
[Trade swap](#Trade-swap)<br>
|
||||
[Exchange program operations](#Exchange-program-operations)<br>
|
||||
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
|
||||
[Investor strategies](#Investor-strategies)<br>
|
||||
[Running the exchange](#Running-the-exchange)<br>
|
||||
|
||||
## Overview
|
||||
|
||||
An exchange is a marketplace where one asset can be traded for another. This
|
||||
demo demonstrates one way to host an exchange on the Solana blockchain by
|
||||
emulating a currency exchange.
|
||||
|
||||
The assets are virtual tokens held by investors who may post order requests to
|
||||
the exchange. A Matcher monitors the exchange and posts swap requests for
|
||||
matching orders. All the transactions can execute concurrently.
|
||||
|
||||
## Premise
|
||||
|
||||
- Exchange
|
||||
- An exchange is a marketplace where one asset can be traded for another.
|
||||
The exchange in this demo is the on-chain program that implements the
|
||||
tokens and the policies for trading those tokens.
|
||||
- Token
|
||||
- A virtual asset that can be owned, traded, and holds virtual intrinsic value
|
||||
compared to other assets. There are four types of tokens in this demo, A,
|
||||
B, C, D. Each one may be traded for another.
|
||||
- Token account
|
||||
- An account owned by the exchange that holds a quantity of one type of token.
|
||||
- Account request
|
||||
- A request to create a token account
|
||||
- Token request
|
||||
- A request to deposit tokens of a particular type into a token account.
|
||||
- Asset pair
|
||||
- A struct with fields Base and Quote, representing the two assets which make up a
|
||||
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
|
||||
numerator and the Quote is the denominator for pricing purposes.
|
||||
- Order side
|
||||
- Describes which side of the market an investor wants to place a trade on. Options
|
||||
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
|
||||
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
|
||||
for the Quote asset.
|
||||
- Price ratio
|
||||
- An expression of the relative prices of two tokens. Calculated with the Base
|
||||
Asset as the numerator and the Quote Asset as the denominator. Ratios are
|
||||
represented as fixed point numbers. The fixed point scaler is defined in
|
||||
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
|
||||
- Order request
|
||||
- A Solana transaction sent by a trader to the exchange to submit an order.
|
||||
Order requests are made up of the token pair, the order side (bid or ask),
|
||||
quantity of the primary token, the price ratio, and the two token accounts
|
||||
to be credited/deducted. An example trade request looks like "T AB 5 2"
|
||||
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
deducted and 10 B tokens credited to the trade initiator's token accounts.
|
||||
Successful order requests result in an order.
|
||||
- Order
|
||||
- The result of a successful order request. orders are stored in
|
||||
accounts owned by the submitter of the order request. They can only be
|
||||
canceled by their owner but can be used by anyone in a trade swap. They
|
||||
contain the same information as the order request.
|
||||
- Price spread
|
||||
- The difference between the two matching orders. The spread is the
|
||||
profit of the Matcher initiating the swap request.
|
||||
- Match requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Match request
|
||||
- A request to fill two complementary orders (bid/ask), resulting if successful,
|
||||
in a trade being created.
|
||||
- Trade
|
||||
- A successful trade is created from two matching orders that meet
|
||||
swap requirements which are submitted in a Match Request by a Matcher and
|
||||
executed by the exchange. A trade may not wholly satisfy one or both of the
|
||||
orders in which case the orders are adjusted appropriately. Upon execution,
|
||||
tokens are distributed to the traders' accounts and any overlap or
|
||||
"negative spread" between orders is deposited into the Matcher's profit
|
||||
account. All successful trades are recorded in the data of a new solana
|
||||
account for posterity.
|
||||
- Investor
|
||||
- Individual investors who hold a number of tokens and wish to trade them on
|
||||
the exchange. Investors operate as Solana thin clients who own a set of
|
||||
accounts containing tokens and/or order requests. Investors post
|
||||
transactions to the exchange in order to request tokens and post or cancel
|
||||
order requests.
|
||||
- Matcher
|
||||
- An agent who facilitates trading between investors. Matchers operate as
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Matcher issues a swap request to the exchange.
|
||||
Matchers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Matchers also
|
||||
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
|
||||
information on demand via a public network port.
|
||||
- Transaction fees
|
||||
- Solana transaction fees are paid for by the transaction submitters who are
|
||||
the Investors and Matchers.
|
||||
|
||||
## Exchange startup
|
||||
|
||||
The exchange is up and running when it reaches a state where it can take
|
||||
investors' trades and Matchers' match requests. To achieve this state the
|
||||
following must occur in order:
|
||||
|
||||
- Start the Solana blockchain
|
||||
- Start the thin-client
|
||||
- The Matcher subscribes to change notifications for all the accounts owned by
|
||||
the exchange program id. The subscription is managed via Solana's JSON RPC
|
||||
interface.
|
||||
- The Matcher starts responding to queries for bid/ask price and OHLCV
|
||||
|
||||
The Matcher responding successfully to price and OHLCV requests is the signal to
|
||||
the investors that trades submitted after that point will be analyzed. <!--This
|
||||
is not ideal, and instead investors should be able to submit trades at any time,
|
||||
and the Matcher could come and go without missing a trade. One way to achieve
|
||||
this is for the Matcher to read the current state of all accounts looking for all
|
||||
open orders.-->
|
||||
|
||||
Investors will initially query the exchange to discover their current balance
|
||||
for each type of token. If the investor does not already have an account for
|
||||
each type of token, they will submit account requests. Matcher as well will
|
||||
request accounts to hold the tokens they earn by initiating trade swaps.
|
||||
|
||||
```rust
|
||||
/// Supported token types
|
||||
pub enum Token {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
}
|
||||
|
||||
/// Supported token pairs
|
||||
pub enum TokenPair {
|
||||
AB,
|
||||
AC,
|
||||
AD,
|
||||
BC,
|
||||
BD,
|
||||
CD,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
}
|
||||
|
||||
/// Token accounts are populated with this structure
|
||||
pub struct TokenAccountInfo {
|
||||
/// Investor who owns this account
|
||||
pub owner: Pubkey,
|
||||
/// Current number of tokens this account holds
|
||||
pub tokens: Tokens,
|
||||
}
|
||||
```
|
||||
|
||||
For this demo investors or Matcher can request more tokens from the exchange at
|
||||
any time by submitting token requests. In non-demos, an exchange of this type
|
||||
would provide another way to exchange a 3rd party asset into tokens.
|
||||
|
||||
To request tokens, investors submit transfer requests:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
}
|
||||
```
|
||||
|
||||
## Order Requests
|
||||
|
||||
When an investor decides to exchange a token of one type for another, they
|
||||
submit a transaction to the Solana Blockchain containing an order request, which,
|
||||
if successful, is turned into an order. orders do not expire but are
|
||||
cancellable. <!-- orders should have a timestamp to enable trade
|
||||
expiration --> When an order is created, tokens are deducted from a token
|
||||
account and the order acts as an escrow. The tokens are held until the
|
||||
order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
of `tokens` are deducted from the primary account, if `From` then `tokens`
|
||||
multiplied by `price` are deducted from the secondary account. orders are
|
||||
no longer valid when the number of `tokens` goes to zero, at which point they
|
||||
can no longer be used. <!-- Could support refilling orders, so order
|
||||
accounts are refilled rather than accumulating -->
|
||||
|
||||
```rust
|
||||
/// Direction of the exchange between two tokens in a pair
|
||||
pub enum Direction {
|
||||
/// Trade first token type (primary) in the pair 'To' the second
|
||||
To,
|
||||
/// Trade first token type in the pair 'From' the second (secondary)
|
||||
From,
|
||||
}
|
||||
|
||||
pub struct OrderRequestInfo {
|
||||
/// Direction of trade
|
||||
pub direction: Direction,
|
||||
|
||||
/// Token pair to trade
|
||||
pub pair: TokenPair,
|
||||
|
||||
/// Number of tokens to exchange; refers to the primary or the secondary depending on the direction
|
||||
pub tokens: u64,
|
||||
|
||||
/// The price ratio the primary price over the secondary price. The primary price is fixed
|
||||
/// and equal to the variable `SCALER`.
|
||||
pub price: u64,
|
||||
|
||||
/// Token account to deposit tokens on successful swap
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
}
|
||||
|
||||
/// Trade accounts are populated with this structure
|
||||
pub struct TradeOrderInfo {
|
||||
/// Owner of the order
|
||||
pub owner: Pubkey,
|
||||
/// Direction of the exchange
|
||||
pub direction: Direction,
|
||||
/// Token pair indicating two tokens to exchange, first is primary
|
||||
pub pair: TokenPair,
|
||||
/// Number of tokens to exchange; primary or secondary depending on direction
|
||||
pub tokens: u64,
|
||||
/// Scaled price of the secondary token given the primary is equal to the scale value
|
||||
/// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens
|
||||
pub price: u64,
|
||||
/// account which the tokens were source from. The trade account holds the tokens in escrow
|
||||
/// until either one or more part of a swap or the trade is canceled.
|
||||
pub src_account: Pubkey,
|
||||
/// account which the tokens the tokens will be deposited into on a successful trade
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
```
|
||||
|
||||
## Order cancellations
|
||||
|
||||
An investor may cancel a trade at anytime, but only trades they own. If the
|
||||
cancellation is successful, any tokens held in escrow are returned to the
|
||||
account from which they came.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
}
|
||||
```
|
||||
|
||||
## Trade swaps
|
||||
|
||||
The Matcher is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The order table is used to identify
|
||||
matching orders which could be fulfilled. When a match is found the
|
||||
Matcher should issue a swap request. Swap requests may not satisfy the entirety
|
||||
of either order, but the exchange will greedily fulfill it. Any leftover tokens
|
||||
in either account will keep the order valid for further swap requests in
|
||||
the future.
|
||||
|
||||
Matching orders are defined by the following swap requirements:
|
||||
|
||||
- Opposite polarity (one `To` and one `From`)
|
||||
- Operate on the same token pair
|
||||
- The price ratio of the `From` order is greater than or equal to the `To` order
|
||||
- There are sufficient tokens to perform the trade
|
||||
|
||||
Orders can be written in the following format:
|
||||
|
||||
`investor direction pair quantity price-ratio`
|
||||
|
||||
For example:
|
||||
|
||||
- `1 T AB 2 1`
|
||||
- Investor 1 wishes to exchange 2 A tokens to B tokens at a ratio of 1 A to 1
|
||||
B
|
||||
- `2 F AC 6 1.2`
|
||||
- Investor 2 wishes to exchange A tokens from 6 B tokens at a ratio of 1 A
|
||||
from 1.2 B
|
||||
|
||||
An order table could look something like the following. Notice how the columns
|
||||
are sorted low to high and high to low, respectively. Prices are dramatic and
|
||||
whole for clarity.
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 2 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 3 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 4 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
As part of a successful swap request, the exchange will credit tokens to the
|
||||
Matcher's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Matcher's profit for initiating the trade.
|
||||
|
||||
The Matcher would initiate the following swap on the order table above:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
|
||||
- Matcher takes 8 B tokens as profit
|
||||
|
||||
Both row 1 trades are fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Row 1 From is not fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 6 6 | 2 F AB 1 8 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
|
||||
| 4 | | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Row 1 To is now fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 5 6 | 2 F AB 2 7 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following last swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 3 6 | 2 F AB 3 5 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 1 5 |
|
||||
| 3 | 1 T AB 2 10 | |
|
||||
|
||||
At this point the lowest To's price is larger than the largest From's price so
|
||||
no more swaps would be initiated until new orders came in.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
|
||||
/// Swap accounts are populated with this structure
|
||||
pub struct TradeSwapInfo {
|
||||
/// Pair swapped
|
||||
pub pair: TokenPair,
|
||||
/// `To` order
|
||||
pub to_trade_order: Pubkey,
|
||||
/// `From` order
|
||||
pub from_trade_order: Pubkey,
|
||||
/// Number of primary tokens exchanged
|
||||
pub primary_tokens: u64,
|
||||
/// Price the primary tokens were exchanged for
|
||||
pub primary_price: u64,
|
||||
/// Number of secondary tokens exchanged
|
||||
pub secondary_tokens: u64,
|
||||
/// Price the secondary tokens were exchanged for
|
||||
pub secondary_price: u64,
|
||||
}
|
||||
```
|
||||
|
||||
## Exchange program operations
|
||||
|
||||
Putting all the commands together from above, the following operations will be
|
||||
supported by the on-chain exchange program:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
```
|
||||
|
||||
## Quotes and OHLCV
|
||||
|
||||
The Matcher will provide current bid/ask price quotes based on trade actively and
|
||||
also provide OHLCV based on some time window. The details of how the bid/ask
|
||||
price quotes are calculated are yet to be decided.
|
||||
|
||||
## Investor strategies
|
||||
|
||||
To make a compelling demo, the investors needs to provide interesting trade
|
||||
behavior. Something as simple as a randomly twiddled baseline would be a
|
||||
minimum starting point.
|
||||
|
||||
## Running the exchange
|
||||
|
||||
The exchange bench posts trades and swaps matches as fast as it can.
|
||||
|
||||
You might want to bump the duration up
|
||||
to 60 seconds and the batch size to 1000 for better numbers. You can modify those
|
||||
in client_demo/src/demo.rs::test_exchange_local_cluster.
|
||||
|
||||
The following command runs the bench:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
||||
|
||||
To also see the cluster messages:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
File diff suppressed because it is too large
Load Diff
@ -1,220 +0,0 @@
|
||||
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, KeypairUtil};
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub faucet_addr: SocketAddr,
|
||||
pub identity: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub transfer_delay: u64,
|
||||
pub fund_amount: u64,
|
||||
pub batch_size: usize,
|
||||
pub chunk_size: usize,
|
||||
pub account_groups: usize,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
|
||||
identity: Keypair::new(),
|
||||
num_nodes: 1,
|
||||
threads: 4,
|
||||
duration: Duration::new(u64::max_value(), 0),
|
||||
transfer_delay: 0,
|
||||
fund_amount: 100_000,
|
||||
batch_size: 100,
|
||||
chunk_size: 100,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:8001")
|
||||
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the faucet; defaults to 127.0.0.1:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.long("threads")
|
||||
.value_name("<threads>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Number of threads submitting transactions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.default_value("60")
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("transfer-delay")
|
||||
.long("transfer-delay")
|
||||
.value_name("<delay>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("0")
|
||||
.help("Delay between each chunk"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fund-amount")
|
||||
.long("fund-amount")
|
||||
.value_name("<fund>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("100000")
|
||||
.help("Number of lamports to fund to each signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch-size")
|
||||
.long("batch-size")
|
||||
.value_name("<batch>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1000")
|
||||
.help("Number of transactions before the signer rolls over"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("chunk-size")
|
||||
.long("chunk-size")
|
||||
.value_name("<cunk>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("500")
|
||||
.help("Number of transactions to generate and send at a time"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("account-groups")
|
||||
.long("account-groups")
|
||||
.value_name("<groups>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("10")
|
||||
.help("Number of account groups to cycle for each batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(
|
||||
matches.value_of("entrypoint").unwrap(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
args.faucet_addr = solana_net_utils::parse_host_port(matches.value_of("faucet").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse faucet address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
} else {
|
||||
args.identity = {
|
||||
let seed = [42_u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
rnd.gen_keypair()
|
||||
};
|
||||
}
|
||||
args.threads = value_t!(matches.value_of("threads"), usize).expect("Failed to parse threads");
|
||||
args.num_nodes =
|
||||
value_t!(matches.value_of("num-nodes"), usize).expect("Failed to parse num-nodes");
|
||||
let duration = value_t!(matches.value_of("duration"), u64).expect("Failed to parse duration");
|
||||
args.duration = Duration::from_secs(duration);
|
||||
args.transfer_delay =
|
||||
value_t!(matches.value_of("transfer-delay"), u64).expect("Failed to parse transfer-delay");
|
||||
args.fund_amount =
|
||||
value_t!(matches.value_of("fund-amount"), u64).expect("Failed to parse fund-amount");
|
||||
args.batch_size =
|
||||
value_t!(matches.value_of("batch-size"), usize).expect("Failed to parse batch-size");
|
||||
args.chunk_size =
|
||||
value_t!(matches.value_of("chunk-size"), usize).expect("Failed to parse chunk-size");
|
||||
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
|
||||
.expect("Failed to parse account-groups");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
args
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
mod order_book;
|
@ -1,83 +0,0 @@
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
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::KeypairUtil;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-exchange");
|
||||
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
faucet_addr,
|
||||
identity,
|
||||
threads,
|
||||
num_nodes,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
..
|
||||
} = cli_config;
|
||||
|
||||
let config = Config {
|
||||
identity,
|
||||
threads,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
};
|
||||
|
||||
if write_to_client_file {
|
||||
create_client_accounts_file(
|
||||
&config.client_ids_and_stake_file,
|
||||
config.batch_size,
|
||||
config.account_groups,
|
||||
config.fund_amount,
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
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);
|
||||
|
||||
info!("{} nodes found", num_clients);
|
||||
if num_clients < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Funding keypair: {}", config.identity.pubkey());
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
}
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::*;
|
||||
use solana_exchange_program::exchange_state::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
other.info.price.cmp(&self.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for ToOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.info.price.cmp(&other.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for FromOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderBook {
|
||||
// TODO scale to x token types
|
||||
to_ab: BinaryHeap<ToOrder>,
|
||||
from_ab: BinaryHeap<FromOrder>,
|
||||
}
|
||||
impl fmt::Display for OrderBook {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"+-Order Book--------------------------+-------------------------------------+"
|
||||
)?;
|
||||
for (i, it) in self
|
||||
.to_ab
|
||||
.iter()
|
||||
.zip_longest(self.from_ab.iter())
|
||||
.enumerate()
|
||||
{
|
||||
match it {
|
||||
Both(to, from) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | F AB {:8} for {:8}/{:8} |{}",
|
||||
to.info.tokens,
|
||||
SCALER,
|
||||
to.info.price,
|
||||
from.info.tokens,
|
||||
SCALER,
|
||||
from.info.price,
|
||||
i
|
||||
)?,
|
||||
Left(to) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | |{}",
|
||||
to.info.tokens, SCALER, to.info.price, i
|
||||
)?,
|
||||
Right(from) => writeln!(
|
||||
f,
|
||||
"| | F AB {:8} for {:8}/{:8} |{}",
|
||||
from.info.tokens, SCALER, from.info.price, i
|
||||
)?,
|
||||
}
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"+-------------------------------------+-------------------------------------+"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderBook {
|
||||
// TODO
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.side, info.tokens, info.price)?;
|
||||
match info.side {
|
||||
OrderSide::Ask => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
OrderSide::Bid => {
|
||||
self.from_ab.push(FromOrder { pubkey, info });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<(ToOrder, FromOrder)> {
|
||||
if let Some(pair) = Self::pop_pair(&mut self.to_ab, &mut self.from_ab) {
|
||||
return Some(pair);
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn get_num_outstanding(&self) -> (usize, usize) {
|
||||
(self.to_ab.len(), self.from_ab.len())
|
||||
}
|
||||
|
||||
fn pop_pair(
|
||||
to_ab: &mut BinaryHeap<ToOrder>,
|
||||
from_ab: &mut BinaryHeap<FromOrder>,
|
||||
) -> Option<(ToOrder, FromOrder)> {
|
||||
let to = to_ab.peek()?;
|
||||
let from = from_ab.peek()?;
|
||||
if from.info.price < to.info.price {
|
||||
debug!("Trade not viable");
|
||||
return None;
|
||||
}
|
||||
let to = to_ab.pop()?;
|
||||
let from = from_ab.pop()?;
|
||||
Some((to, from))
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
use log::*;
|
||||
use solana_bench_exchange::bench::{airdrop_lamports, do_bench_exchange, Config};
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_exchange_program::exchange_processor::process_instruction;
|
||||
use solana_exchange_program::id;
|
||||
use solana_exchange_program::solana_exchange_program;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
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, KeypairUtil};
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_exchange_local_cluster() {
|
||||
solana_logger::setup();
|
||||
|
||||
const NUM_NODES: usize = 1;
|
||||
|
||||
let mut config = Config::default();
|
||||
config.identity = Keypair::new();
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 15
|
||||
config.batch_size = 100; // 1000;
|
||||
config.chunk_size = 10; // 200;
|
||||
config.account_groups = 1; // 10;
|
||||
let Config {
|
||||
fund_amount,
|
||||
batch_size,
|
||||
account_groups,
|
||||
..
|
||||
} = config;
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&faucet_keypair.pubkey(),
|
||||
2_000_000_000_000,
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, Some(1_000_000_000_000));
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
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);
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
info!("clients: {}", num_clients);
|
||||
assert!(num_clients >= NUM_NODES);
|
||||
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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_instruction_processor(id(), process_instruction);
|
||||
let clients = vec![BankClient::new(bank)];
|
||||
|
||||
let mut config = Config::default();
|
||||
config.identity = identity;
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 0;
|
||||
config.batch_size = 100; // 1500;
|
||||
config.chunk_size = 10; // 1500;
|
||||
config.account_groups = 1; // 50;
|
||||
|
||||
do_bench_exchange(clients, config);
|
||||
}
|
2
bench-streamer/.gitignore
vendored
2
bench-streamer/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@ -1,15 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.22.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "0.22.2" }
|
||||
solana-core = { path = "../core", version = "0.22.2" }
|
||||
solana-logger = { path = "../logger", version = "0.22.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.22.2" }
|
4
bench-tps/.gitignore
vendored
4
bench-tps/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.22.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
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.2" }
|
||||
solana-core = { path = "../core", version = "0.22.2" }
|
||||
solana-genesis = { path = "../genesis", version = "0.22.2" }
|
||||
solana-client = { path = "../client", version = "0.22.2" }
|
||||
solana-faucet = { path = "../faucet", version = "0.22.2" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "0.22.2", optional = true }
|
||||
solana-logger = { path = "../logger", version = "0.22.2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.22.2" }
|
||||
solana-measure = { path = "../measure", version = "0.22.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.22.2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.22.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.22.2" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "0.22.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.3.1"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "0.22.2" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
File diff suppressed because it is too large
Load Diff
@ -1,263 +0,0 @@
|
||||
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
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::SOL_LAMPORTS;
|
||||
|
||||
/// Holds the configuration for a single run of the benchmark
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub faucet_addr: SocketAddr,
|
||||
pub id: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub tx_count: usize,
|
||||
pub keypair_multiplier: usize,
|
||||
pub thread_batch_sleep_ms: usize,
|
||||
pub sustained: bool,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
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,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
|
||||
id: Keypair::new(),
|
||||
threads: 4,
|
||||
num_nodes: 1,
|
||||
duration: Duration::new(std::u64::MAX, 0),
|
||||
tx_count: 50_000,
|
||||
keypair_multiplier: 8,
|
||||
thread_batch_sleep_ms: 1000,
|
||||
sustained: false,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines and builds the CLI args for a run of the benchmark
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!()).about(crate_description!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Location of the faucet; defaults to entrypoint:FAUCET_PORT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.short("N")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.short("t")
|
||||
.long("threads")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Number of threads"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("sustained")
|
||||
.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")
|
||||
.help("Disable multi-client support, only transact with the entrypoint."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tx_count")
|
||||
.long("tx_count")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Number of transactions to send per batch")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keypair_multiplier")
|
||||
.long("keypair-multiplier")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Multiply by transaction count to determine number of keypairs to create")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("thread-batch-sleep-ms")
|
||||
.short("z")
|
||||
.long("thread-batch-sleep-ms")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Per-thread-per-iteration sleep in ms"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("target_lamports_per_signature")
|
||||
.long("target-lamports-per-signature")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"The cost in lamports that the cluster will charge for signature \
|
||||
verification when the cluster is operating at target-signatures-per-slot",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_lamports_per_account")
|
||||
.long("num-lamports-per-account")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"Number of lamports per account.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a clap `ArgMatches` structure into a `Config`
|
||||
/// # Arguments
|
||||
/// * `matches` - command line arguments parsed by clap
|
||||
/// # Panics
|
||||
/// Panics if there is trouble parsing any of the arguments
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
if let Some(addr) = matches.value_of("entrypoint") {
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(addr) = matches.value_of("faucet") {
|
||||
args.faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse faucet address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.id = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
}
|
||||
|
||||
if let Some(t) = matches.value_of("threads") {
|
||||
args.threads = t.to_string().parse().expect("can't parse threads");
|
||||
}
|
||||
|
||||
if let Some(n) = matches.value_of("num-nodes") {
|
||||
args.num_nodes = n.to_string().parse().expect("can't parse num-nodes");
|
||||
}
|
||||
|
||||
if let Some(duration) = matches.value_of("duration") {
|
||||
args.duration = Duration::new(
|
||||
duration.to_string().parse().expect("can't parse duration"),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("tx_count") {
|
||||
args.tx_count = s.to_string().parse().expect("can't parse tx_count");
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("keypair_multiplier") {
|
||||
args.keypair_multiplier = s
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("can't parse keypair-multiplier");
|
||||
assert!(args.keypair_multiplier >= 2);
|
||||
}
|
||||
|
||||
if let Some(t) = matches.value_of("thread-batch-sleep-ms") {
|
||||
args.thread_batch_sleep_ms = t
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("can't parse thread-batch-sleep-ms");
|
||||
}
|
||||
|
||||
args.sustained = matches.is_present("sustained");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(v) = matches.value_of("target_lamports_per_signature") {
|
||||
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");
|
||||
}
|
||||
|
||||
args
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
@ -1,143 +0,0 @@
|
||||
use log::*;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
|
||||
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::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_program;
|
||||
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_filter("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
faucet_addr,
|
||||
id,
|
||||
num_nodes,
|
||||
tx_count,
|
||||
keypair_multiplier,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
multi_client,
|
||||
num_lamports_per_account,
|
||||
..
|
||||
} = &cli_config;
|
||||
|
||||
let keypair_count = *tx_count * keypair_multiplier;
|
||||
if *write_to_client_file {
|
||||
info!("Generating {} keypairs", keypair_count);
|
||||
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee =
|
||||
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;
|
||||
let mut accounts = HashMap::new();
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
Base64Account {
|
||||
balance: num_lamports_per_account,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
data: String::new(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
info!("Writing {}", client_ids_and_stake_file);
|
||||
let serialized = serde_yaml::to_string(&accounts).unwrap();
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(&serialized.into_bytes()).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
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);
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
client
|
||||
} else {
|
||||
get_client(&nodes)
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
info!("Reading {}", client_ids_and_stake_file);
|
||||
let accounts: HashMap<String, Base64Account> = serde_yaml::from_reader(file).unwrap();
|
||||
let mut keypairs = vec![];
|
||||
let mut last_balance = 0;
|
||||
|
||||
accounts
|
||||
.into_iter()
|
||||
.for_each(|(keypair, primordial_account)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = primordial_account.balance;
|
||||
});
|
||||
|
||||
if keypairs.len() < keypair_count {
|
||||
eprintln!(
|
||||
"Expected {} accounts in {}, only received {} (--tx_count mismatch?)",
|
||||
keypair_count,
|
||||
client_ids_and_stake_file,
|
||||
keypairs.len(),
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
// 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(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
|
||||
(keypairs, None, last_balance)
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(*faucet_addr),
|
||||
&id,
|
||||
keypair_count,
|
||||
*num_lamports_per_account,
|
||||
*use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
exit(1);
|
||||
})
|
||||
};
|
||||
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
cli_config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
);
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
use serial_test_derive::serial;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs};
|
||||
use solana_bench_tps::cli::Config;
|
||||
use solana_client::thin_client::create_client;
|
||||
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};
|
||||
#[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();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&faucet_keypair.pubkey(),
|
||||
100_000_000,
|
||||
);
|
||||
|
||||
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);
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
let lamports_per_account = 100;
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
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(vec![client], config, keypairs, 0, move_keypairs);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert!(_total > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_bench_tps_local_cluster_solana() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
|
||||
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);
|
||||
}
|
48
benches/bank.rs
Normal file
48
benches/bank.rs
Normal file
@ -0,0 +1,48 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use bincode::serialize;
|
||||
use rayon::prelude::*;
|
||||
use solana::bank::*;
|
||||
use solana::hash::hash;
|
||||
use solana::mint::Mint;
|
||||
use solana::signature::{Keypair, KeypairUtil};
|
||||
use solana::transaction::Transaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_process_transaction(bencher: &mut Bencher) {
|
||||
let mint = Mint::new(100_000_000);
|
||||
let bank = Bank::new(&mint);
|
||||
|
||||
// Create transactions between unrelated parties.
|
||||
let transactions: Vec<_> = (0..4096)
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
// Seed the 'from' account.
|
||||
let rando0 = Keypair::new();
|
||||
let tx = Transaction::new(&mint.keypair(), rando0.pubkey(), 10_000, mint.last_id());
|
||||
assert!(bank.process_transaction(&tx).is_ok());
|
||||
|
||||
// Seed the 'to' account and a cell for its signature.
|
||||
let last_id = hash(&serialize(&i).unwrap()); // Unique hash
|
||||
bank.register_entry_id(&last_id);
|
||||
|
||||
let rando1 = Keypair::new();
|
||||
let tx = Transaction::new(&rando0, rando1.pubkey(), 1, last_id);
|
||||
assert!(bank.process_transaction(&tx).is_ok());
|
||||
|
||||
// Finally, return the transaction to the benchmark.
|
||||
tx
|
||||
}).collect();
|
||||
|
||||
bencher.iter(|| {
|
||||
// Since benchmarker runs this multiple times, we need to clear the signatures.
|
||||
bank.clear_signatures();
|
||||
let results = bank.process_transactions(transactions.clone());
|
||||
assert!(results.iter().all(Result::is_ok));
|
||||
})
|
||||
}
|
206
benches/banking_stage.rs
Normal file
206
benches/banking_stage.rs
Normal file
@ -0,0 +1,206 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use rayon::prelude::*;
|
||||
use solana::bank::Bank;
|
||||
use solana::banking_stage::BankingStage;
|
||||
use solana::mint::Mint;
|
||||
use solana::packet::{to_packets_chunked, PacketRecycler};
|
||||
use solana::record_stage::Signal;
|
||||
use solana::signature::{Keypair, KeypairUtil};
|
||||
use solana::transaction::Transaction;
|
||||
use std::iter;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::Arc;
|
||||
use test::Bencher;
|
||||
|
||||
// use self::test::Bencher;
|
||||
// use bank::{Bank, MAX_ENTRY_IDS};
|
||||
// use bincode::serialize;
|
||||
// use hash::hash;
|
||||
// use mint::Mint;
|
||||
// use rayon::prelude::*;
|
||||
// use signature::{Keypair, KeypairUtil};
|
||||
// use std::collections::HashSet;
|
||||
// use std::time::Instant;
|
||||
// use transaction::Transaction;
|
||||
//
|
||||
// fn bench_process_transactions(_bencher: &mut Bencher) {
|
||||
// let mint = Mint::new(100_000_000);
|
||||
// let bank = Bank::new(&mint);
|
||||
// // Create transactions between unrelated parties.
|
||||
// let txs = 100_000;
|
||||
// let last_ids: Mutex<HashSet<Hash>> = Mutex::new(HashSet::new());
|
||||
// let transactions: Vec<_> = (0..txs)
|
||||
// .into_par_iter()
|
||||
// .map(|i| {
|
||||
// // Seed the 'to' account and a cell for its signature.
|
||||
// let dummy_id = i % (MAX_ENTRY_IDS as i32);
|
||||
// let last_id = hash(&serialize(&dummy_id).unwrap()); // Semi-unique hash
|
||||
// {
|
||||
// let mut last_ids = last_ids.lock().unwrap();
|
||||
// if !last_ids.contains(&last_id) {
|
||||
// last_ids.insert(last_id);
|
||||
// bank.register_entry_id(&last_id);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Seed the 'from' account.
|
||||
// let rando0 = Keypair::new();
|
||||
// let tx = Transaction::new(&mint.keypair(), rando0.pubkey(), 1_000, last_id);
|
||||
// bank.process_transaction(&tx).unwrap();
|
||||
//
|
||||
// let rando1 = Keypair::new();
|
||||
// let tx = Transaction::new(&rando0, rando1.pubkey(), 2, last_id);
|
||||
// bank.process_transaction(&tx).unwrap();
|
||||
//
|
||||
// // Finally, return a transaction that's unique
|
||||
// Transaction::new(&rando0, rando1.pubkey(), 1, last_id)
|
||||
// })
|
||||
// .collect();
|
||||
//
|
||||
// let banking_stage = EventProcessor::new(bank, &mint.last_id(), None);
|
||||
//
|
||||
// let now = Instant::now();
|
||||
// assert!(banking_stage.process_transactions(transactions).is_ok());
|
||||
// let duration = now.elapsed();
|
||||
// let sec = duration.as_secs() as f64 + duration.subsec_nanos() as f64 / 1_000_000_000.0;
|
||||
// let tps = txs as f64 / sec;
|
||||
//
|
||||
// // Ensure that all transactions were successfully logged.
|
||||
// drop(banking_stage.historian_input);
|
||||
// let entries: Vec<Entry> = banking_stage.output.lock().unwrap().iter().collect();
|
||||
// assert_eq!(entries.len(), 1);
|
||||
// assert_eq!(entries[0].transactions.len(), txs as usize);
|
||||
//
|
||||
// println!("{} tps", tps);
|
||||
// }
|
||||
|
||||
fn check_txs(receiver: &Receiver<Signal>, ref_tx_count: usize) {
|
||||
let mut total = 0;
|
||||
loop {
|
||||
let signal = receiver.recv().unwrap();
|
||||
if let Signal::Transactions(transactions) = signal {
|
||||
total += transactions.len();
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
assert_eq!(total, ref_tx_count);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
let tx = 10_000_usize;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let mint = Mint::new(mint_total);
|
||||
let num_dst_accounts = 8 * 1024;
|
||||
let num_src_accounts = 8 * 1024;
|
||||
|
||||
let srckeys: Vec<_> = (0..num_src_accounts).map(|_| Keypair::new()).collect();
|
||||
let dstkeys: Vec<_> = (0..num_dst_accounts)
|
||||
.map(|_| Keypair::new().pubkey())
|
||||
.collect();
|
||||
|
||||
let transactions: Vec<_> = (0..tx)
|
||||
.map(|i| {
|
||||
Transaction::new(
|
||||
&srckeys[i % num_src_accounts],
|
||||
dstkeys[i % num_dst_accounts],
|
||||
i as i64,
|
||||
mint.last_id(),
|
||||
)
|
||||
}).collect();
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let (signal_sender, signal_receiver) = channel();
|
||||
let packet_recycler = PacketRecycler::default();
|
||||
|
||||
let setup_transactions: Vec<_> = (0..num_src_accounts)
|
||||
.map(|i| {
|
||||
Transaction::new(
|
||||
&mint.keypair(),
|
||||
srckeys[i].pubkey(),
|
||||
mint_total / num_src_accounts as i64,
|
||||
mint.last_id(),
|
||||
)
|
||||
}).collect();
|
||||
|
||||
bencher.iter(move || {
|
||||
let bank = Arc::new(Bank::new(&mint));
|
||||
|
||||
let verified_setup: Vec<_> =
|
||||
to_packets_chunked(&packet_recycler, &setup_transactions.clone(), tx)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = (*x).read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
}).collect();
|
||||
|
||||
verified_sender.send(verified_setup).unwrap();
|
||||
BankingStage::process_packets(&bank, &verified_receiver, &signal_sender, &packet_recycler)
|
||||
.unwrap();
|
||||
|
||||
check_txs(&signal_receiver, num_src_accounts);
|
||||
|
||||
let verified: Vec<_> = to_packets_chunked(&packet_recycler, &transactions.clone(), 192)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = (*x).read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
}).collect();
|
||||
|
||||
verified_sender.send(verified).unwrap();
|
||||
BankingStage::process_packets(&bank, &verified_receiver, &signal_sender, &packet_recycler)
|
||||
.unwrap();
|
||||
|
||||
check_txs(&signal_receiver, tx);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_single_from(bencher: &mut Bencher) {
|
||||
let tx = 10_000_usize;
|
||||
let mint = Mint::new(1_000_000_000_000);
|
||||
let mut pubkeys = Vec::new();
|
||||
let num_keys = 8;
|
||||
for _ in 0..num_keys {
|
||||
pubkeys.push(Keypair::new().pubkey());
|
||||
}
|
||||
|
||||
let transactions: Vec<_> = (0..tx)
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
Transaction::new(
|
||||
&mint.keypair(),
|
||||
pubkeys[i % num_keys],
|
||||
i as i64,
|
||||
mint.last_id(),
|
||||
)
|
||||
}).collect();
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let (signal_sender, signal_receiver) = channel();
|
||||
let packet_recycler = PacketRecycler::default();
|
||||
|
||||
bencher.iter(move || {
|
||||
let bank = Arc::new(Bank::new(&mint));
|
||||
let verified: Vec<_> = to_packets_chunked(&packet_recycler, &transactions.clone(), tx)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = (*x).read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
}).collect();
|
||||
verified_sender.send(verified).unwrap();
|
||||
BankingStage::process_packets(&bank, &verified_receiver, &signal_sender, &packet_recycler)
|
||||
.unwrap();
|
||||
|
||||
check_txs(&signal_receiver, tx);
|
||||
});
|
||||
}
|
26
benches/ledger.rs
Normal file
26
benches/ledger.rs
Normal file
@ -0,0 +1,26 @@
|
||||
#![feature(test)]
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use solana::hash::{hash, Hash};
|
||||
use solana::ledger::{next_entries, reconstruct_entries_from_blobs, Block};
|
||||
use solana::packet::BlobRecycler;
|
||||
use solana::signature::{Keypair, KeypairUtil};
|
||||
use solana::transaction::Transaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
|
||||
let zero = Hash::default();
|
||||
let one = hash(&zero.as_ref());
|
||||
let keypair = Keypair::new();
|
||||
let tx0 = Transaction::new(&keypair, keypair.pubkey(), 1, one);
|
||||
let transactions = vec![tx0; 10];
|
||||
let entries = next_entries(&zero, 1, transactions);
|
||||
|
||||
let blob_recycler = BlobRecycler::default();
|
||||
bencher.iter(|| {
|
||||
let blobs = entries.to_blobs(&blob_recycler);
|
||||
assert_eq!(reconstruct_entries_from_blobs(blobs).unwrap(), entries);
|
||||
});
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana::signature::GenKeys;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
24
benches/sigverify.rs
Normal file
24
benches/sigverify.rs
Normal file
@ -0,0 +1,24 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::{to_packets, PacketRecycler};
|
||||
use solana::sigverify;
|
||||
use solana::transaction::test_tx;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify(bencher: &mut Bencher) {
|
||||
let tx = test_tx();
|
||||
|
||||
// generate packet vector
|
||||
let packet_recycler = PacketRecycler::default();
|
||||
let batches = to_packets(&packet_recycler, &vec![tx; 128]);
|
||||
|
||||
// verify packets
|
||||
bencher.iter(|| {
|
||||
let _ans = sigverify::ed25519_verify(&batches);
|
||||
})
|
||||
}
|
1
book/.gitattributes
vendored
1
book/.gitattributes
vendored
@ -1 +0,0 @@
|
||||
theme/highlight.js binary
|
@ -1,26 +0,0 @@
|
||||
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
|
||||
```
|
@ -1,19 +0,0 @@
|
||||
+------------------------------------------------------------------+
|
||||
| |
|
||||
| +-----------------+ Neighborhood 0 +-----------------+ |
|
||||
| | +--------------------->+ | |
|
||||
| | Validator 1 | | Validator 2 | |
|
||||
| | +<---------------------+ | |
|
||||
| +--------+-+------+ +------+-+--------+ |
|
||||
| | | | | |
|
||||
| | +-----------------------------+ | | |
|
||||
| | +------------------------+------+ | |
|
||||
| | | | | |
|
||||
+------------------------------------------------------------------+
|
||||
| | | |
|
||||
v v v v
|
||||
+---------+------+---+ +-+--------+---------+
|
||||
| | | |
|
||||
| Neighborhood 1 | | Neighborhood 2 |
|
||||
| | | |
|
||||
+--------------------+ +--------------------+
|
@ -1,25 +0,0 @@
|
||||
+---------------------------------------------------------------------------------------------------------+
|
||||
| Neighborhood Above |
|
||||
| |
|
||||
| +----------------+ +----------------+ +----------------+ +----------------+ |
|
||||
| | +------>+ +------>+ +------>+ | |
|
||||
| | Neighbor 1 | | Neighbor 2 | | Neighbor 3 | | Neighbor 4 | |
|
||||
| | +<------+ +<------+ +<------+ | |
|
||||
| +--+-------------+ +--+-------------+ +-----+----------+ +--+-------------+ |
|
||||
| | | | | |
|
||||
+---------------------------------------------------------------------------------------------------------+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+---------------------------------------------------------------------------------------------------------+
|
||||
| | | Neighborhood Below | | |
|
||||
| v v v v |
|
||||
| +--+-------------+ +--+-------------+ +-----+----------+ +--+-------------+ |
|
||||
| | +------>+ +------>+ +------>+ | |
|
||||
| | Neighbor 1 | | Neighbor 2 | | Neighbor 3 | | Neighbor 4 | |
|
||||
| | +<------+ +<------+ +<------+ | |
|
||||
| +----------------+ +----------------+ +----------------+ +----------------+ |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------------+
|
@ -1,15 +0,0 @@
|
||||
+--------------+
|
||||
| |
|
||||
+------------+ Leader +------------+
|
||||
| | | |
|
||||
| +--------------+ |
|
||||
v v
|
||||
+------------+----------------------------------------+------------+
|
||||
| |
|
||||
| +-----------------+ Neighborhood 0 +-----------------+ |
|
||||
| | +--------------------->+ | |
|
||||
| | Validator 1 | | Validator 2 | |
|
||||
| | +<---------------------+ | |
|
||||
| +-----------------+ +-----------------+ |
|
||||
| |
|
||||
+------------------------------------------------------------------+
|
@ -1,18 +0,0 @@
|
||||
+--------------------+
|
||||
| |
|
||||
+--------+ Neighborhood 0 +----------+
|
||||
| | | |
|
||||
| +--------------------+ |
|
||||
v v
|
||||
+---------+----------+ +----------+---------+
|
||||
| | | |
|
||||
| Neighborhood 1 | | Neighborhood 2 |
|
||||
| | | |
|
||||
+---+-----+----------+ +----------+-----+---+
|
||||
| | | |
|
||||
v v v v
|
||||
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
|
||||
| | | | | | | |
|
||||
| Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
|
||||
| | | | | | | |
|
||||
+--------------------+ +--------------------+ +--------------------+ +--------------------+
|
@ -1,13 +0,0 @@
|
||||
validator action
|
||||
+----+ ----------------
|
||||
| | L1 | E1
|
||||
| +----+ / \ vote(E1)
|
||||
| | L2 | E2 x
|
||||
| +----+ / \ / \ vote(E2)
|
||||
time | | L3 | E3 x E3' x
|
||||
| +----+ / \ / \ / \ / \ slash(E3)
|
||||
| | L4 | x x E4 x x x x x
|
||||
| +----+ | | | | | | | | vote(E4)
|
||||
v | L5 | xx xx xx E5 xx xx xx xx
|
||||
+----+ hang on to E4 and E5 for more...
|
||||
|
@ -1,9 +0,0 @@
|
||||
1
|
||||
|
|
||||
2
|
||||
/|
|
||||
/ |
|
||||
| |
|
||||
| 4
|
||||
|
|
||||
5
|
@ -1,11 +0,0 @@
|
||||
1
|
||||
|
|
||||
3
|
||||
|\
|
||||
| \
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
6 |
|
||||
|
|
||||
7
|
@ -1,13 +0,0 @@
|
||||
1
|
||||
|\
|
||||
2 \
|
||||
/| |
|
||||
/ | 3
|
||||
| | |\
|
||||
| 4 | \
|
||||
| | |
|
||||
5 | |
|
||||
| |
|
||||
6 |
|
||||
|
|
||||
7
|
@ -1,30 +0,0 @@
|
||||
msc {
|
||||
hscale="2.2";
|
||||
VoteSigner,
|
||||
Validator,
|
||||
Cluster,
|
||||
StakerX,
|
||||
StakerY;
|
||||
|
||||
|||;
|
||||
Validator box Validator [label="boot.."];
|
||||
|
||||
VoteSigner <:> Validator [label="register\n\n(optional)"];
|
||||
Validator => Cluster [label="VoteState::Initialize(VoteSigner)"];
|
||||
StakerX => Cluster [label="StakeState::Delegate(Validator)"];
|
||||
StakerY => Cluster [label="StakeState::Delegate(Validator)"];
|
||||
|
||||
|||;
|
||||
Validator box Cluster [label="\nvalidate\n"];
|
||||
Validator => VoteSigner [label="sign(vote)"];
|
||||
VoteSigner >> Validator [label="signed vote"];
|
||||
|
||||
Validator => Cluster [label="gossip(vote)"];
|
||||
...;
|
||||
... ;
|
||||
Validator abox Validator [label="\nmax\nlockout\n"];
|
||||
|||;
|
||||
StakerX => Cluster [label="StakeState::RedeemCredits()"];
|
||||
StakerY => Cluster [label="StakeState::RedeemCredits()"] ;
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
.------------. .-----------. .---------------. .--------------. .-----------------------.
|
||||
| PoH verify +---> | sigverify +--->| lock accounts +--->| validate fee +--->| allocate new accounts +--->
|
||||
| TVU | `-----------` `---------------` `--------------` `-----------------------`
|
||||
`------------`
|
||||
|
||||
.---------------. .---------. .------------. .-----------------. .-----------------.
|
||||
--->| load accounts +--->| execute +--->| PoH record +--->| commit accounts +-->| unlock accounts |
|
||||
`---------------` `---------` | TPU | `-----------------` `-----------------`
|
||||
`------------`
|
||||
|
@ -1,20 +0,0 @@
|
||||
|
||||
.----------------------------------------.
|
||||
| Solana Runtime |
|
||||
| |
|
||||
| .------------. .------------. |
|
||||
| | | | | |
|
||||
.-------->| Verifier +-->| Accounts | |
|
||||
| | | | | | |
|
||||
.----------. | | `------------` `------------` |
|
||||
| +--------` | ^ |
|
||||
| Client | | LoadAccounts | |
|
||||
| +--------. | .----------------` |
|
||||
`----------` | | | |
|
||||
| | .------+-----. .-------------. |
|
||||
| | | | | | |
|
||||
`-------->| Loader +-->| Interpreter | |
|
||||
| | | | | |
|
||||
| `------------` `-------------` |
|
||||
| |
|
||||
`----------------------------------------`
|
@ -1,18 +0,0 @@
|
||||
+------------+
|
||||
| Bank-Merkle|
|
||||
+------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-----------------+ +-------------+
|
||||
| Bank-Diff-Merkle| | Block-Merkle|
|
||||
+-----------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+------+ +--------------------------+
|
||||
| Hash | | Previous Bank-Diff-Merkle|
|
||||
+------+ +--------------------------+
|
||||
^ ^
|
||||
/ \
|
||||
+---------------+ +---------------+
|
||||
| Hash(Account1)| | Hash(Account2)|
|
||||
+---------------+ +---------------+
|
@ -1,19 +0,0 @@
|
||||
+---------------+
|
||||
| Block-Merkle |
|
||||
+---------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------------+ +-------------+
|
||||
| Entry-Merkle| | Entry-Merkle|
|
||||
+-------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------+ +-------+
|
||||
| Hash | | Hash |
|
||||
+-------+ +-------+
|
||||
^ ^ ^ ^
|
||||
/ | | \
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
| Hash(T1, status)| | Hash(T2, status)| | Hash(T3, status)| | 0 |
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
|
@ -1,19 +0,0 @@
|
||||
|
||||
.-------------.
|
||||
| PoH Service |
|
||||
`--------+----`
|
||||
^ |
|
||||
.------------------------------|----|--------------------.
|
||||
| TPU | v |
|
||||
| .-------. .-----------. .-+-------. .-----------. | .------------.
|
||||
.---------. | | Fetch | | SigVerify | | Banking | | Broadcast | | | Downstream |
|
||||
| Clients |--->| Stage |->| Stage |->| Stage |->| Stage |---->| Validators |
|
||||
`---------` | | | | | | | | | | | |
|
||||
| `-------` `-----------` `----+----` `-----------` | `------------`
|
||||
| | |
|
||||
`---------------------------------|----------------------`
|
||||
|
|
||||
v
|
||||
.------.
|
||||
| Bank |
|
||||
`------`
|
@ -1,22 +0,0 @@
|
||||
.--------.
|
||||
| Leader |
|
||||
`--------`
|
||||
^
|
||||
|
|
||||
.------------------------------------|--------------------.
|
||||
| TVU | |
|
||||
| | |
|
||||
| .-------. .------------. .----+---. .---------. |
|
||||
.------------. | | Shred | | Retransmit | | Replay | | Storage | |
|
||||
| Upstream +----->| Fetch +-->| Stage +-->| Stage +-->| Stage | |
|
||||
| Validators | | | Stage | | | | | | | |
|
||||
`------------` | `-------` `----+-------` `----+---` `---------` |
|
||||
| ^ | | |
|
||||
| | | | |
|
||||
`--------|----------|----------------|--------------------`
|
||||
| | |
|
||||
| V v
|
||||
.+-----------. .------.
|
||||
| Gossip | | Bank |
|
||||
| Service | `------`
|
||||
`------------`
|
@ -1,60 +0,0 @@
|
||||
|
||||
.------------.
|
||||
| Upstream |
|
||||
| Validators |
|
||||
`----+-------`
|
||||
|
|
||||
|
|
||||
.-----------------------------------.
|
||||
| Validator | |
|
||||
| v |
|
||||
| .-----------. .------------. |
|
||||
.--------. | | Fetch | | Repair | |
|
||||
| Client +---->| Stage | | Stage | |
|
||||
`--------` | `---+-------` `----+-------` |
|
||||
| | | |
|
||||
| v v |
|
||||
| .-----------. .------------. |
|
||||
| | TPU |<-->| Blockstore | |
|
||||
| | | | | |
|
||||
| `-----------` `----+-------` |
|
||||
| | |
|
||||
| v |
|
||||
| .------------. |
|
||||
| | Multicast | |
|
||||
| | Stage | |
|
||||
| `----+-------` |
|
||||
| | |
|
||||
`-----------------------------------`
|
||||
|
|
||||
v
|
||||
.------------.
|
||||
| Downstream |
|
||||
| Validators |
|
||||
`------------`
|
||||
|
||||
|
||||
|
||||
.------------.
|
||||
| PoH |
|
||||
| Service |
|
||||
`-------+----`
|
||||
^ |
|
||||
| |
|
||||
.-----------------------------------.
|
||||
| TPU | | |
|
||||
| | v |
|
||||
.-------. | .-----------. .---+--------. | .------------.
|
||||
| Fetch +---->| SigVerify +--->| Banking |<--->| Blockstore |
|
||||
| Stage | | | Stage | | Stage | | | |
|
||||
`-------` | `-----------` `-----+------` | `------------`
|
||||
| | |
|
||||
| | |
|
||||
`-----------------------------------`
|
||||
|
|
||||
v
|
||||
.------------.
|
||||
| Banktree |
|
||||
| |
|
||||
`------------`
|
||||
|
@ -1,30 +0,0 @@
|
||||
.---------------------------------------.
|
||||
| Validator |
|
||||
| |
|
||||
.--------. | .-------------------. |
|
||||
| |---->| | |
|
||||
| Client | | | JSON RPC Service | |
|
||||
| |<----| | |
|
||||
`----+---` | `-------------------` |
|
||||
| | ^ |
|
||||
| | | .----------------. | .------------------.
|
||||
| | | | Gossip Service |<-----------| Validators |
|
||||
| | | `----------------` | | |
|
||||
| | | ^ | | |
|
||||
| | | | | | .------------. |
|
||||
| | .---+---. .----+---. .------------. | | | | |
|
||||
| | | Bank |<-+ Replay | | ShredFetch |<------+ Upstream | |
|
||||
| | | Forks | | Stage | | Stage | | | | Validators | |
|
||||
| | `-------` `--------` `--+---------` | | | | |
|
||||
| | ^ ^ | | | `------------` |
|
||||
| | | | v | | |
|
||||
| | | .--+--------. | | |
|
||||
| | | | Blocktree | | | |
|
||||
| | | `-----------` | | .------------. |
|
||||
| | | ^ | | | | |
|
||||
| | | | | | | Downstream | |
|
||||
| | .--+--. .-------+---. | | | Validators | |
|
||||
`-------->| TPU +---->| Broadcast +---------------->| | |
|
||||
| `-----` | Stage | | | `------------` |
|
||||
| `-----------` | `------------------`
|
||||
`---------------------------------------`
|
@ -1,10 +0,0 @@
|
||||
[book]
|
||||
title = "Solana: Blockchain Rebuilt for Scale"
|
||||
authors = ["The Solana Team"]
|
||||
|
||||
[build]
|
||||
build-dir = "html"
|
||||
create-missing = false
|
||||
|
||||
[output.html]
|
||||
theme = "theme"
|
@ -1,34 +0,0 @@
|
||||
#!/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"
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)" test
|
@ -1,43 +0,0 @@
|
||||
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
|
Binary file not shown.
Before Width: | Height: | Size: 542 KiB |
Binary file not shown.
Before Width: | Height: | Size: 256 KiB |
Binary file not shown.
Before Width: | Height: | Size: 269 KiB |
@ -1,90 +0,0 @@
|
||||
# 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)
|
||||
* [Durable Transaction Nonces](offline-signing/durable-nonce.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)
|
@ -1,177 +0,0 @@
|
||||
# solana CLI
|
||||
|
||||
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
|
||||
|
||||
## Examples
|
||||
|
||||
### Get Pubkey
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana address
|
||||
|
||||
// Return
|
||||
<PUBKEY>
|
||||
```
|
||||
|
||||
### Airdrop SOL/Lamports
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana airdrop 2
|
||||
|
||||
// Return
|
||||
"2.00000000 SOL"
|
||||
|
||||
// Command
|
||||
$ solana airdrop 123 --lamports
|
||||
|
||||
// Return
|
||||
"123 lamports"
|
||||
```
|
||||
|
||||
### Get Balance
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana balance
|
||||
|
||||
// Return
|
||||
"3.00050001 SOL"
|
||||
```
|
||||
|
||||
### Confirm Transaction
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana confirm <TX_SIGNATURE>
|
||||
|
||||
// Return
|
||||
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
|
||||
```
|
||||
|
||||
### Deploy program
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana deploy <PATH>
|
||||
|
||||
// Return
|
||||
<PROGRAM_ID>
|
||||
```
|
||||
|
||||
### Unconditional Immediate Transfer
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
### Post-Dated Transfer
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
_`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key_
|
||||
|
||||
### Authorized Transfer
|
||||
|
||||
A third party must send a signature to unlock the lamports.
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
### Post-Dated and Authorized Transfer
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
### Multiple Witnesses
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
### Cancelable Transfer
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--cancelable
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
### Cancel Transfer
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana cancel <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
### Send Signature
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana send-signature <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
### Indicate Elapsed Time
|
||||
|
||||
Use the current system time:
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
Or specify some other arbitrary timestamp:
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
## Usage
|
@ -1,4 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
The following sections contain API references material you may find useful when developing applications utilizing a Solana cluster.
|
||||
|
@ -1,28 +0,0 @@
|
||||
# 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
@ -1,38 +0,0 @@
|
||||
# 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
|
||||
|
@ -1,4 +0,0 @@
|
||||
# JavaScript API
|
||||
|
||||
See [solana-web3](https://solana-labs.github.io/solana-web3.js/).
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,62 +0,0 @@
|
||||
# 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).
|
@ -1,83 +0,0 @@
|
||||
# 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](tower-bft.md).
|
||||
|
||||
## Current Design
|
||||
|
||||
To start voting, a validator first registers an account to which it will send
|
||||
its votes. It then sends votes to that account. The vote contains the tick
|
||||
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.
|
||||
|
||||
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
|
||||
it has observed an arbitrary block at some height.
|
||||
|
||||
* 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.
|
||||
|
||||
## Proposed Design
|
||||
|
||||
### No Cross-block State Initially
|
||||
|
||||
At the moment a block is produced, the leader shall add a NewBlock transaction
|
||||
to the ledger with a number of tokens that represents the validation reward.
|
||||
It is effectively an incremental multisig transaction that sends tokens from
|
||||
the mining pool to the validators. The account should allocate just enough
|
||||
space to collect the votes required to achieve a supermajority. When a
|
||||
validator observes the NewBlock transaction, it has the option to submit a vote
|
||||
that includes a hash of its ledger state (the bank state). Once the account has
|
||||
sufficient votes, the vote program should disperse the tokens to the
|
||||
validators, which causes the account to be deleted.
|
||||
|
||||
#### Logging Confirmation Time
|
||||
|
||||
The bank will need to be aware of the vote program. After each transaction, it
|
||||
should check if it is a vote transaction and if so, check the state of that
|
||||
account. If the transaction caused the supermajority to be achieved, it should
|
||||
log the time since the NewBlock transaction was submitted.
|
||||
|
||||
### Finality and Payouts
|
||||
|
||||
[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.
|
||||
|
||||
## Challenges
|
||||
|
||||
### On-chain voting
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
Similar in spirit, if the leader caches stakes at the time of NewBlock, the
|
||||
vote program doesn't need to interact with the bank when it processes votes. If
|
||||
we don't, then we have the option to allow stakes to float until a vote is
|
||||
submitted. A validator could conceivably reference its own staking account, but
|
||||
that'd be the current account value instead of the account value of the most
|
||||
recently finalized bank state. The bank currently doesn't offer a means to
|
||||
reference accounts from particular points in time.
|
||||
|
||||
### Voting Implications on Previous Blocks
|
||||
|
||||
Does a vote on one height imply a vote on all blocks of lower heights of
|
||||
that fork? If it does, we'll need a way to lookup the accounts of all
|
||||
blocks that haven't yet reached supermajority. If not, the validator could
|
||||
send votes to all blocks explicitly to get the block rewards.
|
@ -1,40 +0,0 @@
|
||||
# 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 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 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 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 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 and replicated by archiver nodes, effectively appending them to the ledger.
|
||||
|
||||
## Confirming Transactions
|
||||
|
||||
A Solana cluster is capable of subsecond _confirmation_ for up to 150 nodes with plans to scale up to hundreds of thousands of nodes. Once fully implemented, confirmation times are expected to increase only with the logarithm of the number of validators, where the logarithm's base is very high. If the base is one thousand, for example, it means that for the first thousand nodes, confirmation will be the duration of three network hops plus the time it takes the slowest validator of a supermajority to vote. For the next million nodes, confirmation increases by only one network hop.
|
||||
|
||||
Solana defines confirmation as the duration of time from when the leader timestamps a new entry to the moment when it recognizes a supermajority of ledger votes.
|
||||
|
||||
A gossip network is much too slow to achieve subsecond confirmation once the network grows beyond a certain size. The time it takes to send messages to all nodes is proportional to the square of the number of nodes. If a blockchain wants to achieve low confirmation and attempts to do it using a gossip network, it will be forced to centralize to just a handful of nodes.
|
||||
|
||||
Scalable confirmation can be achieved using the follow combination of techniques:
|
||||
|
||||
1. Timestamp transactions with a VDF sample and sign the timestamp.
|
||||
2. Split the transactions into batches, send each to separate nodes and have
|
||||
|
||||
each node share its batch with its peers.
|
||||
|
||||
3. Repeat the previous step recursively until all nodes have all batches.
|
||||
|
||||
Solana rotates leaders at fixed intervals, called _slots_. Each leader may only produce entries during its allotted slot. The leader therefore timestamps transactions so that validators may lookup the public key of the designated leader. The leader then signs the timestamp so that a validator may verify the signature, proving the signer is owner of the designated leader's public key.
|
||||
|
||||
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 _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.
|
@ -1,80 +0,0 @@
|
||||
# Fork Generation
|
||||
|
||||
The chapter describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
|
||||
|
||||
## Overview
|
||||
|
||||
Nodes take turns being leader and generating the PoH that encodes state changes. The cluster can tolerate loss of connection to any leader by synthesizing what the leader _**would**_ have generated had it been connected but not ingesting any state changes. The possible number of forks is thereby limited to a "there/not-there" skip list of forks that may arise on leader rotation slot boundaries. At any given slot, only a single leader's transactions will be accepted.
|
||||
|
||||
## Message Flow
|
||||
|
||||
1. Transactions are ingested by the current leader.
|
||||
2. Leader filters valid transactions.
|
||||
3. Leader executes valid transactions updating its state.
|
||||
4. Leader packages transactions into entries based off its current PoH slot.
|
||||
5. Leader transmits the entries to validator nodes \(in signed shreds\) 1. The PoH stream includes ticks; empty entries that indicate liveness of
|
||||
|
||||
the leader and the passage of time on the cluster.
|
||||
|
||||
1. A leader's stream begins with the tick entries necessary complete the PoH
|
||||
|
||||
back to the leaders most recently observed prior leader slot.
|
||||
|
||||
6. Validators retransmit entries to peers in their set and to further
|
||||
|
||||
downstream nodes.
|
||||
|
||||
7. Validators validate the transactions and execute them on their state.
|
||||
8. Validators compute the hash of the state.
|
||||
9. At specific times, i.e. specific PoH tick counts, validators transmit votes
|
||||
|
||||
to the leader.
|
||||
|
||||
1. Votes are signatures of the hash of the computed state at that PoH tick
|
||||
|
||||
count
|
||||
|
||||
2. Votes are also propagated via gossip
|
||||
|
||||
10. Leader executes the votes as any other transaction and broadcasts them to
|
||||
|
||||
the cluster.
|
||||
|
||||
11. Validators observe their votes and all the votes from the cluster.
|
||||
|
||||
## Partitions, Forks
|
||||
|
||||
Forks can arise at PoH tick counts that correspond to a vote. The next leader may not have observed the last vote slot and may start their slot with generated virtual PoH entries. These empty ticks are generated by all nodes in the cluster at a cluster-configured rate for hashes/per/tick `Z`.
|
||||
|
||||
There are only two possible versions of the PoH during a voting slot: PoH with `T` ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the cluster can derive from the last tick in the previous slot.
|
||||
|
||||
Validators can ignore forks at other points \(e.g. from the wrong leader\), or slash the leader responsible for the fork.
|
||||
|
||||
Validators vote based on a greedy choice to maximize their reward described in [Tower BFT](../implemented-proposals/tower-bft.md).
|
||||
|
||||
### Validator's View
|
||||
|
||||
#### Time Progression
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
#### Time Division
|
||||
|
||||
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 |
|
||||
|
||||
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.
|
||||
|
||||
This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints.
|
||||
|
||||
### Leader's View
|
||||
|
||||
When a new leader begins a slot, it must first transmit any PoH \(ticks\) required to link the new slot with the most recently observed and voted slot. The fork the leader proposes would link the current slot to a previous fork that the leader has voted on with virtual ticks.
|
@ -1,97 +0,0 @@
|
||||
# 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.
|
||||
|
||||
Each validator selects the expected leader using the same algorithm, described below. When the validator receives a new signed ledger entry, it can be certain that entry was produced by the expected leader. The order of slots which each leader is assigned a slot is called a _leader schedule_.
|
||||
|
||||
## Leader Schedule Rotation
|
||||
|
||||
A validator rejects blocks that are not signed by the _slot leader_. The list of identities of all slot leaders is called a _leader schedule_. The leader schedule is recomputed locally and periodically. It assigns slot leaders for a duration of time called an _epoch_. The schedule must be computed far in advance of the slots it assigns, such that the ledger state it uses to compute the schedule is finalized. That duration is called the _leader schedule offset_. Solana sets the offset to the duration of slots until the next epoch. That is, the leader schedule for an epoch is calculated from the ledger state at the start of the previous epoch. The offset of one epoch is fairly arbitrary and assumed to be sufficiently long such that all validators will have finalized their ledger state before the next schedule is generated. A cluster may choose to shorten the offset to reduce the time between stake changes and leader schedule updates.
|
||||
|
||||
While operating without partitions lasting longer than an epoch, the schedule only needs to be generated when the root fork crosses the epoch boundary. Since the schedule is for the next epoch, any new stakes committed to the root fork will not be active until the next epoch. The block used for generating the leader schedule is the first block to cross the epoch boundary.
|
||||
|
||||
Without a partition lasting longer than an epoch, the cluster will work as follows:
|
||||
|
||||
1. A validator continuously updates its own root fork as it votes.
|
||||
2. The validator updates its leader schedule each time the slot height crosses an epoch boundary.
|
||||
|
||||
For example:
|
||||
|
||||
The epoch duration is 100 slots. The root fork is updated from fork computed at slot height 99 to a fork computed at slot height 102. Forks with slots at height 100,101 were skipped because of failures. The new leader schedule is computed using fork at slot height 102. It is active from slot 200 until it is updated again.
|
||||
|
||||
No inconsistency can exist because every validator that is voting with the cluster has skipped 100 and 101 when its root passes 102. All validators, regardless of voting pattern, would be committing to a root that is either 102, or a descendant of 102.
|
||||
|
||||
### Leader Schedule Rotation with Epoch Sized Partitions.
|
||||
|
||||
The duration of the leader schedule offset has a direct relationship to the likelihood of a cluster having an inconsistent view of the correct leader schedule.
|
||||
|
||||
Consider the following scenario:
|
||||
|
||||
Two partitions that are generating half of the blocks each. Neither is coming to a definitive supermajority fork. Both will cross epoch 100 and 200 without actually committing to a root and therefore a cluster wide commitment to a new leader schedule.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
After observing the cluster for a sufficient amount of time, the leader schedule offset can be selected based on the median partition duration and its standard deviation. For example, an offset longer then the median partition duration plus six standard deviations would reduce the likelihood of an inconsistent ledger schedule in the cluster to 1 in 1 million.
|
||||
|
||||
## Leader Schedule Generation at Genesis
|
||||
|
||||
The genesis config declares the first leader for the first epoch. This leader ends up scheduled for the first two epochs because the leader schedule is also generated at slot 0 for the next epoch. The length of the first two epochs can be specified in the genesis config as well. The minimum length of the first epochs must be greater than or equal to the maximum rollback depth as defined in [Tower BFT](../implemented-proposals/tower-bft.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
Leader schedule is generated using a predefined seed. The process is as follows:
|
||||
|
||||
1. Periodically use the PoH tick height \(a monotonically increasing counter\) to
|
||||
|
||||
seed a stable pseudo-random algorithm.
|
||||
|
||||
2. At that height, sample the bank for all the staked accounts with leader
|
||||
|
||||
identities that have voted within a cluster-configured number of ticks. The
|
||||
|
||||
sample is called the _active set_.
|
||||
|
||||
3. Sort the active set by stake weight.
|
||||
4. Use the random seed to select nodes weighted by stake to create a
|
||||
|
||||
stake-weighted ordering.
|
||||
|
||||
5. This ordering becomes valid after a cluster-configured number of ticks.
|
||||
|
||||
## Schedule Attack Vectors
|
||||
|
||||
### Seed
|
||||
|
||||
The seed that is selected is predictable but unbiasable. There is no grinding attack to influence its outcome.
|
||||
|
||||
### Active Set
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### Staking
|
||||
|
||||
Leaders can censor new staking transactions or refuse to validate blocks with new stakes. This attack is similar to censorship of validator votes.
|
||||
|
||||
### Validator operational key loss
|
||||
|
||||
Leaders and validators are expected to use ephemeral keys for operation, and stake owners authorize the validators to do work with their stake via delegation.
|
||||
|
||||
The cluster should be able to recover from the loss of all the ephemeral keys used by leaders and validators, which could occur through a common software vulnerability shared by all the nodes. Stake owners should be able to vote directly co-sign a validator vote even though the stake is currently delegated to a validator.
|
||||
|
||||
## Appending Entries
|
||||
|
||||
The lifetime of a leader schedule is called an _epoch_. The epoch is split into _slots_, where each slot has a duration of `T` PoH ticks.
|
||||
|
||||
A leader transmits entries during its slot. After `T` ticks, all the validators switch to the next scheduled leader. Validators must ignore entries sent outside a leader's assigned slot.
|
||||
|
||||
All `T` ticks must be observed by the next leader for it to build its own entries on. If entries are not observed \(leader is down\) or entries are invalid \(leader is buggy or malicious\), the next leader must produce ticks to fill the previous leader's slot. Note that the next leader should do repair requests in parallel, and postpone sending ticks until it is confident other validators also failed to observe the previous leader's entries. If a leader incorrectly builds on its own ticks, the leader following it must replace all its ticks.
|
@ -1,269 +0,0 @@
|
||||
# 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.
|
||||
|
||||
The basic idea to Proof of Replication is encrypting a dataset with a public symmetric key using CBC encryption, then hash the encrypted dataset. The main problem with the naive approach is that a dishonest storage node can stream the encryption and delete the data as it's hashed. The simple solution is to periodically regenerate the hash based on a signed PoH value. This ensures that all the data is present during the generation of the proof and it also requires validators to have the entirety of the encrypted data present for verification of every proof of every identity. So the space required to validate is `number_of_proofs * data_size`
|
||||
|
||||
## Optimization with PoH
|
||||
|
||||
Our improvement on this approach is to randomly sample the encrypted segments faster than it takes to encrypt, and record the hash of those samples into the PoH ledger. Thus the segments stay in the exact same order for every PoRep and verification can stream the data and verify all the proofs in a single batch. This way we can verify multiple proofs concurrently, each one on its own CUDA core. The total space required for verification is `1_ledger_segment + 2_cbc_blocks * number_of_identities` with core count equal to `number_of_identities`. We use a 64-byte chacha CBC block size.
|
||||
|
||||
## Network
|
||||
|
||||
Validators for PoRep are the same validators that are verifying transactions. If an archiver can prove that a validator verified a fake PoRep, then the validator will not receive a reward for that storage epoch.
|
||||
|
||||
Archivers are specialized _light clients_. They download a part of the ledger \(a.k.a Segment\) and store it, and provide PoReps of storing the ledger. For each verified PoRep archivers earn a reward of sol from the mining pool.
|
||||
|
||||
## Constraints
|
||||
|
||||
We have the following constraints:
|
||||
|
||||
* 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
|
||||
|
||||
many identities at once should be batched with as many proofs for those
|
||||
|
||||
identities verified concurrently for the same dataset.
|
||||
|
||||
* 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
|
||||
|
||||
rewarded. The validator can run a benchmark whenever its hardware configuration
|
||||
|
||||
changes to determine what rate it can validate storage proofs.
|
||||
|
||||
## Validation and Replication Protocol
|
||||
|
||||
### Constants
|
||||
|
||||
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
|
||||
|
||||
regenerate their encryption keys and select a new dataset to store.
|
||||
|
||||
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
|
||||
|
||||
mining proof claim has to contain to be valid for a reward.
|
||||
|
||||
5. NUM\_STORAGE\_SAMPLES: Number of samples required for a storage mining
|
||||
|
||||
proof.
|
||||
|
||||
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
|
||||
|
||||
a "turn" of the PoRep game.
|
||||
|
||||
### Validator behavior
|
||||
|
||||
1. Validators join the network and begin looking for archiver accounts at each
|
||||
|
||||
storage epoch/turn boundary.
|
||||
|
||||
2. Every turn, Validators sign the PoH value at the boundary and use that signature
|
||||
|
||||
to randomly pick proofs to verify from each storage account found in the turn boundary.
|
||||
|
||||
This signed value is also submitted to the validator's storage account and will be used by
|
||||
|
||||
archivers at a later stage to cross-verify.
|
||||
|
||||
3. Every `NUM_SLOTS_PER_TURN` slots the validator advertises the PoH value. This is value
|
||||
|
||||
is also served to Archivers via RPC interfaces.
|
||||
|
||||
4. For a given turn N, all validations get locked out until turn N+3 \(a gap of 2 turn/epoch\).
|
||||
|
||||
At which point all validations during that turn are available for reward collection.
|
||||
|
||||
5. Any incorrect validations will be marked during the turn in between.
|
||||
|
||||
### Archiver behavior
|
||||
|
||||
1. Since an archiver is somewhat of a light client and not downloading all the
|
||||
|
||||
ledger data, they have to rely on other validators and archivers for information.
|
||||
|
||||
Any given validator may or may not be malicious and give incorrect information, although
|
||||
|
||||
there are not any obvious attack vectors that this could accomplish besides having the
|
||||
|
||||
archiver do extra wasted work. For many of the operations there are a number of options
|
||||
|
||||
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
|
||||
|
||||
the information itself \(assuming the slot is recent enough\)
|
||||
|
||||
* \(e\) archiver can subscribe to an abbreviated transaction stream to
|
||||
|
||||
generate the information itself \(assuming the slot is recent enough\)
|
||||
|
||||
2. An archiver obtains the PoH hash corresponding to the last turn with its slot.
|
||||
3. The archiver signs the PoH hash with its keypair. That signature is the
|
||||
|
||||
seed used to pick the segment to replicate and also the encryption key. The
|
||||
|
||||
archiver mods the signature with the slot to get which segment to
|
||||
|
||||
replicate.
|
||||
|
||||
4. The archiver retrives the ledger by asking peer validators and
|
||||
|
||||
archivers. See 6.5.
|
||||
|
||||
5. The archiver then encrypts that segment with the key with chacha algorithm
|
||||
|
||||
in CBC mode with `NUM_CHACHA_ROUNDS` of encryption.
|
||||
|
||||
6. The archiver initializes a chacha rng with the a signed recent PoH value as
|
||||
|
||||
the seed.
|
||||
|
||||
7. The archiver generates `NUM_STORAGE_SAMPLES` samples in the range of the
|
||||
|
||||
entry size and samples the encrypted segment with sha256 for 32-bytes at each
|
||||
|
||||
offset value. Sampling the state should be faster than generating the encrypted
|
||||
|
||||
segment.
|
||||
|
||||
8. The archiver sends a PoRep proof transaction which contains its sha state
|
||||
|
||||
at the end of the sampling operation, its seed and the samples it used to the
|
||||
|
||||
current leader and it is put onto the ledger.
|
||||
|
||||
9. During a given turn the archiver should submit many proofs for the same segment
|
||||
|
||||
and based on the `RATIO_OF_FAKE_PROOFS` some of those proofs must be fake.
|
||||
|
||||
10. As the PoRep game enters the next turn, the archiver must submit a
|
||||
|
||||
transaction with the mask of which proofs were fake during the last turn. This
|
||||
|
||||
transaction will define the rewards for both archivers and validators.
|
||||
|
||||
11. Finally for a turn N, as the PoRep game enters turn N + 3, archiver's proofs for
|
||||
|
||||
turn N will be counted towards their rewards.
|
||||
|
||||
### The PoRep Game
|
||||
|
||||
The Proof of Replication game has 4 primary stages. For each "turn" multiple PoRep games can be in progress but each in a different stage.
|
||||
|
||||
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
|
||||
2. Proof verification stage
|
||||
* 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
|
||||
4. Reward collection stage
|
||||
* 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.
|
||||
|
||||
### Finding who has a given block of ledger
|
||||
|
||||
1. Validators monitor the turns in the PoRep game and look at the rooted bank
|
||||
|
||||
at turn boundaries for any proofs.
|
||||
|
||||
2. Validators maintain a map of ledger segments and corresponding archiver public keys.
|
||||
|
||||
The map is updated when a Validator processes an archiver's proofs for a segment.
|
||||
|
||||
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\).
|
||||
|
||||
The clients can then send repair requests to the archiver to retrieve segments.
|
||||
|
||||
3. Validators would need to invalidate this list every N turns.
|
||||
|
||||
## Sybil attacks
|
||||
|
||||
For any random seed, we force everyone to use a signature that is derived from a PoH hash at the turn boundary. Everyone uses the same count, so the same PoH hash is signed by every participant. The signatures are then each cryptographically tied to the keypair, which prevents a leader from grinding on the resulting value for more than 1 identity.
|
||||
|
||||
Since there are many more client identities then encryption identities, we need to split the reward for multiple clients, and prevent Sybil attacks from generating many clients to acquire the same block of data. To remain BFT we want to avoid a single human entity from storing all the replications of a single chunk of the ledger.
|
||||
|
||||
Our solution to this is to force the clients to continue using the same identity. If the first round is used to acquire the same block for many client identities, the second round for the same client identities will force a redistribution of the signatures, and therefore PoRep identities and blocks. Thus to get a reward for archivers need to store the first block for free and the network can reward long lived client identities more than new ones.
|
||||
|
||||
## Validator attacks
|
||||
|
||||
* 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
|
||||
|
||||
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
|
||||
|
||||
from a signature from an archiver, since the validator does not know the
|
||||
|
||||
private key used to generate the encryption key, it cannot be the generator of
|
||||
|
||||
the proof.
|
||||
|
||||
## Reward incentives
|
||||
|
||||
Fake proofs are easy to generate but difficult to verify. For this reason, PoRep proof transactions generated by archivers may require a higher fee than a normal transaction to represent the computational cost required by validators.
|
||||
|
||||
Some percentage of fake proofs are also necessary to receive a reward from storage mining.
|
||||
|
||||
## Notes
|
||||
|
||||
* 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
|
||||
|
||||
use the signatures as the seed
|
||||
|
||||
* 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
|
||||
|
||||
they can prove are fake.
|
||||
|
||||
* 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
|
||||
|
||||
as incentive for storing the ledger. They can only validate proofs if they
|
||||
|
||||
are storing that slice of the ledger.
|
||||
|
@ -1,34 +0,0 @@
|
||||
# Managing Forks
|
||||
|
||||
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.
|
||||
|
||||
## Active Forks
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
The following sequences are _active forks_:
|
||||
|
||||
* {4, 2, 1}
|
||||
* {5, 2, 1}
|
||||
* {6, 3, 1}
|
||||
* {7, 3, 1}
|
||||
|
||||
## Pruning and Squashing
|
||||
|
||||
A validator may vote on any checkpoint in the tree. In the diagram above, that's every node except the leaves of the tree. After voting, the validator prunes nodes that fork from a distance farther than the rollback depth and then takes the opportunity to minimize its memory usage by squashing any nodes it can into the root.
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
The new root is 2, and any active forks that are not descendants from 2 are pruned.
|
||||
|
||||
Alternatively, a vote on 6:
|
||||
|
||||

|
||||
|
||||
The tree remains with a root of 1, since the active fork starting at 6 is only 2 checkpoints from the root.
|
@ -1,24 +0,0 @@
|
||||
# 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\).
|
||||
|
||||
Each cluster node maintains various counters that are incremented on certain events. These counters are periodically uploaded to a cloud based database. Solana's metrics dashboard fetches these counters, and computes the performance metrics and displays it on the dashboard.
|
||||
|
||||
## TPS
|
||||
|
||||
Each node's bank runtime maintains a count of transactions that it has processed. The dashboard first calculates the median count of transactions across all metrics enabled nodes in the cluster. The median cluster transaction count is then averaged over a 2 second period and displayed in the TPS time series graph. The dashboard also shows the Mean TPS, Max TPS and Total Transaction Count stats which are all calculated from the median transaction count.
|
||||
|
||||
## Confirmation Time
|
||||
|
||||
Each validator node maintains a list of active ledger forks that are visible to the node. A fork is considered to be frozen when the node has received and processed all entries corresponding to the fork. A fork is considered to be confirmed when it receives cumulative super majority vote, and when one of its children forks is frozen.
|
||||
|
||||
The node assigns a timestamp to every new fork, and computes the time it took to confirm the fork. This time is reflected as validator confirmation time in performance metrics. The performance dashboard displays the average of each validator node's confirmation time as a time series graph.
|
||||
|
||||
## Hardware setup
|
||||
|
||||
The validator software is deployed to GCP n1-standard-16 instances with 1TB pd-ssd disk, and 2x Nvidia V100 GPUs. These are deployed in the us-west-1 region.
|
||||
|
||||
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.
|
||||
|
@ -1,231 +0,0 @@
|
||||
# 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](../proposals/staking-rewards.md). This chapter, on the other hand, describes the underlying mechanics of its implementation.
|
||||
|
||||
## Basic Design
|
||||
|
||||
The general idea is that the validator owns a Vote account. The Vote account tracks validator votes, counts validator generated credits, and provides any additional validator specific state. The Vote account is not aware of any stakes delegated to it and has no staking weight.
|
||||
|
||||
A separate Stake account \(created by a staker\) names a Vote account to which the stake is delegated. Rewards generated are proportional to the amount of lamports staked. The Stake account is owned by the staker only. Some portion of the lamports stored in this account are the stake.
|
||||
|
||||
## Passive Delegation
|
||||
|
||||
Any number of Stake accounts can delegate to a single Vote account without an interactive action from the identity controlling the Vote account or submitting votes to the account.
|
||||
|
||||
The total stake allocated to a Vote account can be calculated by the sum of all the Stake accounts that have the Vote account pubkey as the `StakeState::Stake::voter_pubkey`.
|
||||
|
||||
## Vote and Stake accounts
|
||||
|
||||
The rewards process is split into two on-chain programs. The Vote program solves the problem of making stakes slashable. The Stake program acts as custodian of the rewards pool and provides for passive delegation. The Stake program is responsible for paying rewards to staker and voter when shown that a staker's delegate has participated in validating the ledger.
|
||||
|
||||
### VoteState
|
||||
|
||||
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
|
||||
|
||||
```text
|
||||
address and the authorized vote signer
|
||||
```
|
||||
|
||||
### VoteInstruction::Initialize\(VoteInit\)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
|
||||
`VoteInit` carries the new vote account's `node_pubkey`, `authorized_voter`, `authorized_withdrawer`, and `commission`
|
||||
|
||||
other VoteState members defaulted
|
||||
|
||||
### VoteInstruction::Authorize\(Pubkey, VoteAuthorize\)
|
||||
|
||||
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
|
||||
|
||||
`VoteState::authorized_voter` or `authorized_withdrawer` is set to to `Pubkey`.
|
||||
|
||||
### VoteInstruction::Vote\(Vote\)
|
||||
|
||||
* `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.
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of four forms, StakeState::Uninitialized, StakeState::Initialized, StakeState::Stake, and StakeState::RewardsPool. Only the first three forms are used in staking, but only StakeState::Stake is interesting. All RewardsPools are created at genesis.
|
||||
|
||||
### StakeState::Stake
|
||||
|
||||
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
|
||||
|
||||
```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
|
||||
|
||||
To avoid a single network wide lock or contention in redemption, 256 RewardsPools are part of genesis under pre-determined keys, each with std::u64::MAX credits to be able to satisfy redemptions according to point value.
|
||||
|
||||
The Stakes and the RewardsPool are accounts that are owned by the same `Stake` program.
|
||||
|
||||
### StakeInstruction::DelegateStake
|
||||
|
||||
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 - 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`.
|
||||
|
||||
* `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
|
||||
|
||||
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.
|
||||
|
||||
### StakeInstruction::Withdraw\(u64\)
|
||||
|
||||
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
|
||||
|
||||
## 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.
|
||||
|
||||
## Example Callflow
|
||||
|
||||

|
||||
|
||||
## 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](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](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.
|
||||
|
||||
At the end of each epoch, the total number of points earned during the epoch is summed and used to divide the rewards portion of epoch inflation to arrive at a point value. This value is recorded in the bank in a [sysvar](../terminology.md#sysvar) that maps epochs to point values.
|
||||
|
||||
During redemption, the stake program counts the points earned by the stake for each epoch, multiplies that by the epoch's point value, and transfers lamports in that amount from a rewards account into the stake and vote accounts according to the vote account's commission setting.
|
||||
|
||||
### Economics
|
||||
|
||||
Point value for an epoch depends on aggregate network participation. If participation in an epoch drops off, point values are higher for those that do participate.
|
||||
|
||||
### Earning credits
|
||||
|
||||
Validators earn one vote credit for every correct vote that exceeds maximum lockout, i.e. every time the validator's vote account retires a slot from its lockout list, making that vote a root for the node.
|
||||
|
||||
Stakers who have delegated to that validator earn points in proportion to their stake. Points earned is the product of vote credits and stake.
|
||||
|
||||
### Stake warmup, cooldown, withdrawal
|
||||
|
||||
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` \(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.
|
||||
|
||||
Cooldown works the same way. Once a stake is deactivated, some part of it is considered "effective", and also "deactivating". As the stake cools down, it continues to earn rewards and be exposed to slashing, but it also becomes available for withdrawal.
|
||||
|
||||
Bootstrap stakes are not subject to warmup.
|
||||
|
||||
Rewards are paid against the "effective" portion of the stake for that epoch.
|
||||
|
||||
#### Warmup example
|
||||
|
||||
Consider the situation of a single stake of 1,000 activated at epoch N, with network warmup rate of 20%, and a quiescent total network stake at epoch N of 2,000.
|
||||
|
||||
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 |
|
||||
|
||||
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 |
|
||||
|
||||
### Withdrawal
|
||||
|
||||
Only lamports in excess of effective+activating stake may be withdrawn at any time. This means that during warmup, effectively no stake can be withdrawn. During cooldown, any tokens in excess of effective stake may be withdrawn \(activating == 0\). Because earned rewards are automatically added to stake, withdrawal is generally only possible after deactivation.
|
||||
|
||||
### 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.
|
@ -1,27 +0,0 @@
|
||||
# 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.
|
||||
|
||||
Solana takes a very different approach, which it calls _Proof of History_ or _PoH_. Leader nodes "timestamp" blocks with cryptographic proofs that some duration of time has passed since the last proof. All data hashed into the proof most certainly have occurred before the proof was generated. The node then shares the new block with validator nodes, which are able to verify those proofs. The blocks can arrive at validators in any order or even could be replayed years later. With such reliable synchronization guarantees, Solana is able to break blocks into smaller batches of transactions called _entries_. Entries are streamed to validators in realtime, before any notion of block consensus.
|
||||
|
||||
Solana technically never sends a _block_, but uses the term to describe the sequence of entries that validators vote on to achieve _confirmation_. In that way, Solana's confirmation times can be compared apples to apples to block-based systems. The current implementation sets block time to 800ms.
|
||||
|
||||
What's happening under the hood is that entries are streamed to validators as quickly as a leader node can batch a set of valid transactions into an entry. Validators process those entries long before it is time to vote on their validity. By processing the transactions optimistically, there is effectively no delay between the time the last entry is received and the time when the node can vote. In the event consensus is **not** achieved, a node simply rolls back its state. This optimisic processing technique was introduced in 1981 and called [Optimistic Concurrency Control](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.65.4735). It can be applied to blockchain architecture where a cluster votes on a hash that represents the full ledger up to some _block height_. In Solana, it is implemented trivially using the last entry's PoH hash.
|
||||
|
||||
## Relationship to VDFs
|
||||
|
||||
The Proof of History technique was first described for use in blockchain by Solana in November of 2017. In June of the following year, a similar technique was described at Stanford and called a [verifiable delay function](https://eprint.iacr.org/2018/601.pdf) or _VDF_.
|
||||
|
||||
A desirable property of a VDF is that verification time is very fast. Solana's approach to verifying its delay function is proportional to the time it took to create it. Split over a 4000 core GPU, it is sufficiently fast for Solana's needs, but if you asked the authors of the paper cited above, they might tell you \([and have](https://github.com/solana-labs/solana/issues/388)\) that Solana's approach is algorithmically slow and it shouldn't be called a VDF. We argue the term VDF should represent the category of verifiable delay functions and not just the subset with certain performance characteristics. Until that's resolved, Solana will likely continue using the term PoH for its application-specific VDF.
|
||||
|
||||
Another difference between PoH and VDFs is that a VDF is used only for tracking duration. PoH's hash chain, on the other hand, includes hashes of any data the application observed. That data is a double-edged sword. On one side, the data "proves history" - that the data most certainly existed before hashes after it. On the side, it means the application can manipulate the hash chain by changing _when_ the data is hashed. The PoH chain therefore does not serve as a good source of randomness whereas a VDF without that data could. Solana's [leader rotation algorithm](synchronization.md#leader-rotation), for example, is derived only from the VDF _height_ and not its hash at that height.
|
||||
|
||||
## 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 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)
|
||||
|
@ -1,96 +0,0 @@
|
||||
# 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.
|
||||
|
||||
During its slot, the leader node distributes shreds between the validator nodes in the first neighborhood \(layer 0\). Each validator shares its data within its neighborhood, but also retransmits the shreds to one node in some neighborhoods in the next layer \(layer 1\). The layer-1 nodes each share their data with their neighborhood peers, and retransmit to nodes in the next layer, etc, until all nodes in the cluster have received all the shreds.
|
||||
|
||||
## Neighborhood Assignment - Weighted Selection
|
||||
|
||||
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 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
|
||||
|
||||
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT` nodes. If this layer 0 is smaller than the number of nodes in the cluster, then the data plane fanout mechanism adds layers below. Subsequent layers follow these constraints to determine layer-capacity: Each neighborhood contains `DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes. The number of nodes in each additional layer grows by a factor of fanout.
|
||||
|
||||
As mentioned above, each node in a layer only has to broadcast its shreds to its neighbors and to exactly 1 node in some next-layer neighborhoods, instead of to every TVU peer in the cluster. A good way to think about this is, layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout" neighborhoods, each with fanout nodes and layer-2 will have `fanout * number of nodes in layer-1` and so on.
|
||||
|
||||
This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
|
||||
|
||||

|
||||
|
||||
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
|
||||
|
||||

|
||||
|
||||
### Configuration Values
|
||||
|
||||
`DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent layers grow by a factor of `DATA_PLANE_FANOUT`. The number of nodes in a neighborhood is equal to the fanout value. Neighborhoods will fill to capacity before new ones are added, i.e if a neighborhood isn't full, it _must_ be the last one.
|
||||
|
||||
Currently, configuration is set when the cluster is launched. In the future, these parameters may be hosted on-chain, allowing modification on the fly as the cluster sizes change.
|
||||
|
||||
## Calcuating the required FEC rate
|
||||
|
||||
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
|
||||
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,
|
||||
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
|
||||
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)`
|
||||
|
||||
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.
|
||||
|
||||
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`
|
||||
|
||||
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`
|
||||
|
||||
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`
|
||||
|
||||
## 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.
|
||||
|
||||

|
@ -1,67 +0,0 @@
|
||||
# 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.
|
||||
|
||||
The validator votes on its chosen fork by submitting a transaction that uses an asymmetric key to sign the result of its validation work. Other entities can verify this signature using the validator's public key. If the validator's key is used to sign incorrect data \(e.g. votes on multiple forks of the ledger\), the node's stake or its resources could be compromised.
|
||||
|
||||
Solana addresses this risk by splitting off a separate _vote signer_ service that evaluates each vote to ensure it does not violate a slashing condition.
|
||||
|
||||
## Validators, Vote Signers, and Stakeholders
|
||||
|
||||
When a validator receives multiple blocks for the same slot, it tracks all possible forks until it can determine a "best" one. A validator selects the best fork by submitting a vote to it, using a vote signer to minimize the possibility of its vote inadvertently violating a consensus rule and getting a stake slashed.
|
||||
|
||||
A vote signer evaluates the vote proposed by the validator and signs the vote only if it does not violate a slashing condition. A vote signer only needs to maintain minimal state regarding the votes it signed and the votes signed by the rest of the cluster. It doesn't need to process a full set of transactions.
|
||||
|
||||
A stakeholder is an identity that has control of the staked capital. The stakeholder can delegate its stake to the vote signer. Once a stake is delegated, the vote signer votes represent the voting weight of all the delegated stakes, and produce rewards for all the delegated stakes.
|
||||
|
||||
Currently, there is a 1:1 relationship between validators and vote signers, and stakeholders delegate their entire stake to a single vote signer.
|
||||
|
||||
## Signing service
|
||||
|
||||
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
|
||||
|
||||
verified
|
||||
|
||||
* 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
|
||||
|
||||
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
|
||||
|
||||
verified
|
||||
|
||||
* The service verifies the voting data
|
||||
* The service returns a signature for the transaction
|
||||
|
||||
## Validator voting
|
||||
|
||||
A validator node, at startup, creates a new vote account and registers it with the cluster by submitting a new "vote register" transaction. The other nodes on the cluster process this transaction and include the new validator in the active set. Subsequently, the validator submits a "new vote" transaction signed with the validator's voting private key on each voting event.
|
||||
|
||||
### Configuration
|
||||
|
||||
The validator node is configured with the signing service's network endpoint \(IP/Port\).
|
||||
|
||||
### Registration
|
||||
|
||||
At startup, the validator registers itself with its signing service using JSON RPC. The RPC call returns the voting public key for the validator node. The validator creates a new "vote register" transaction including this public key, and submits it to the cluster.
|
||||
|
||||
### Vote Collection
|
||||
|
||||
The validator looks up the votes submitted by all the nodes in the cluster for the last voting period. This information is submitted to the signing service with a new vote signing request.
|
||||
|
||||
### 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.
|
||||
|
@ -1,18 +0,0 @@
|
||||
## 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.
|
||||
|
||||
|
||||
|
@ -1,132 +0,0 @@
|
||||
# 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.
|
||||
|
||||
For all four variations, you'd need the latest Rust toolchain and the Solana source code:
|
||||
|
||||
First, install Rust's package manager Cargo.
|
||||
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
```
|
||||
|
||||
Now checkout the code from github:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana
|
||||
```
|
||||
|
||||
The demo code is sometimes broken between releases as we add new low-level features, so if this is your first time running the demo, you'll improve your odds of success if you check out the [latest release](https://github.com/solana-labs/solana/releases) before proceeding:
|
||||
|
||||
```bash
|
||||
$ TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
|
||||
$ git checkout $TAG
|
||||
```
|
||||
|
||||
### Configuration Setup
|
||||
|
||||
Ensure important programs such as the vote program are built before any nodes are started. Note that we are using the release build here for good performance.
|
||||
If you want the debug build, use just `cargo build` and omit the `NDEBUG=1` part of the command.
|
||||
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
The network is initialized with a genesis ledger generated by running the following script.
|
||||
|
||||
```bash
|
||||
$ NDEBUG=1 ./multinode-demo/setup.sh
|
||||
```
|
||||
|
||||
### Drone
|
||||
|
||||
In order for the validators and clients to work, we'll need to spin up a faucet to give out some test tokens. The faucet delivers Milton Friedman-style "air drops" \(free tokens to requesting clients\) to be used in test transactions.
|
||||
|
||||
Start the faucet with:
|
||||
|
||||
```bash
|
||||
$ 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 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 leader in a separate shell:
|
||||
|
||||
```bash
|
||||
$ 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.
|
||||
|
||||
### Multinode Testnet
|
||||
|
||||
To run a multinode testnet, after starting a leader node, spin up some additional validators in separate shells:
|
||||
|
||||
```bash
|
||||
$ NDEBUG=1 ./multinode-demo/validator-x.sh
|
||||
```
|
||||
|
||||
To run a performance-enhanced validator on Linux, [CUDA 10.0](https://developer.nvidia.com/cuda-downloads) must be installed on your system:
|
||||
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
|
||||
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/validator.sh
|
||||
```
|
||||
|
||||
### Testnet Client Demo
|
||||
|
||||
Now that your singlenode or multinode testnet is up and running let's send it some transactions!
|
||||
|
||||
In a separate shell start the client:
|
||||
|
||||
```bash
|
||||
$ NDEBUG=1 ./multinode-demo/bench-tps.sh # runs against localhost by default
|
||||
```
|
||||
|
||||
What just happened? The client demo spins up several threads to send 500,000 transactions to the testnet as quickly as it can. The client then pings the testnet periodically to see how many transactions it processed in that time. Take note that the demo intentionally floods the network with UDP packets, such that the network will almost certainly drop a bunch of them. This ensures the testnet has an opportunity to reach 710k TPS. The client demo completes after it has convinced itself the testnet won't process any additional transactions. You should see several TPS measurements printed to the screen. In the multinode variation, you'll see TPS measurements for each validator node as well.
|
||||
|
||||
### 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.
|
||||
|
||||
For example
|
||||
|
||||
* 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:
|
||||
|
||||
```bash
|
||||
$ export RUST_LOG=solana_bpf_loader=trace
|
||||
```
|
||||
|
||||
Generally we are using `debug` for infrequent debug messages, `trace` for potentially frequent messages and `info` for performance-related logging.
|
||||
|
||||
You can also attach to a running process with GDB. The leader's process is named _solana-validator_:
|
||||
|
||||
```bash
|
||||
$ sudo gdb
|
||||
attach <PID>
|
||||
set logging on
|
||||
thread apply all bt
|
||||
```
|
||||
|
||||
This will dump all the threads stack traces into gdb.txt
|
||||
|
||||
## 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 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 [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)
|
@ -1,7 +0,0 @@
|
||||
# Testnet Participation
|
||||
|
||||
Participate in our testnet:
|
||||
|
||||
* [Running a Validator](../running-validator/)
|
||||
* [Running an Archiver](../running-archiver.md)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user