Compare commits
44 Commits
v0.18.0-pr
...
v0.11
Author | SHA1 | Date | |
---|---|---|---|
|
81f8368bba | ||
|
297166e550 | ||
|
7fff610cae | ||
|
5f3bf853c6 | ||
|
fc34c1370b | ||
|
09410fd5c5 | ||
|
bfff18ac70 | ||
|
7239efe1e7 | ||
|
334a7d9502 | ||
|
bc2d37015d | ||
|
d3fcedb179 | ||
|
f1a77abffb | ||
|
7acd771271 | ||
|
9c2d58660b | ||
|
a907ed2e33 | ||
|
9d3c426510 | ||
|
b0bcc8355f | ||
|
cf99e626c8 | ||
|
6db61759e0 | ||
|
aae08bdae3 | ||
|
5ff22921eb | ||
|
10012be03e | ||
|
17def9fbf5 | ||
|
b5a03d011f | ||
|
2d39c4257a | ||
|
8fb86c9fa7 | ||
|
fc623756df | ||
|
d92bd0de27 | ||
|
c818c20399 | ||
|
ed41547f64 | ||
|
36398bc3f3 | ||
|
fa0e1ad356 | ||
|
f56c5dacca | ||
|
80e0da132a | ||
|
f89debdfa6 | ||
|
16f7e46fce | ||
|
3a039c8007 | ||
|
56d5324837 | ||
|
d3bf0fc707 | ||
|
f9d8a1d6c0 | ||
|
70559253ee | ||
|
9c61abe468 | ||
|
970954ac3b | ||
|
39d821ead8 |
@@ -1,40 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
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
|
14
.buildkite/env/secrets.ejson
vendored
14
.buildkite/env/secrets.ejson
vendored
@@ -1,14 +1,10 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:ks2/ElgxwgxqgmFcxTHANNLmj23YH74h:U4uzRONRfiQyqy6HrPQ/e7OnBUY4HkW37R0iekkF3KJ9UGnHqT1UvwgVbDqLahtDIJ4rWw==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:lKMh3aLW+jyRrfS/c7yvkpB+TaPhXqLq:j0v27EbaPgwRdHZAbsM0FlAnt3r9ScQrFbWJYOAZtM3qestEiByTlKpZ0eyF/823]",
|
||||
"GITHUB_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:Ll78c3jGpYqnTwR7HJq3mNNUC7pOv9Lu:GrInO2r8MjmP5c54szkyygdsrW5KQYkDgJQUVyFEPyG8SWfchyM9Gur8RV0a+cdwuxNkHLi4U2M=]",
|
||||
"INFLUX_DATABASE": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:IlH/ZLTXv3SwlY3TVyAPCX2KzLRY6iG3:gGmUGSU/kCfR/mTwKONaUC/X]",
|
||||
"INFLUX_PASSWORD": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:o2qm95GU4VrrcC4OU06jjPvCwKZy/CZF:OW2ga3kLOQJvaDEdGRJ+gn3L2ckFm8AJZtv9wj/GeUIKDH2A4uBPTHsAH9PMe6zujpuHGk3qbeg=]",
|
||||
"INFLUX_USERNAME": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:yDWW/uIHsJqOTDYskZoSx3pzoB1vztWY:2z31oTA3g0Xs9fCczGNJRcx8xf/hFCed]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:RqRaHlYUvGPNFJa6gmciaYM3tRJTURUH:q78/3GTHCN3Uqx9z4nOBjPZcO1lOazNoB/mdhGRDFsnAqVd2hU8zbKkqLrZfLlGqyD8WQOFuw5oTJR9qWg6L9LcOyj3pGL8jWF2yjgZxdtNMXnkbSrCWLooWBBLT61jYQnEwg73gT8ld3Q8EVv3T+MeSMu6FnPz+0+bqQCAGgfqksP4hsUAJGzgZu+i0tNOdlT7fxnh5KJK/yFM/CKgN2sRwEjukA9hXsffyB61g2zqzTDJxCUDLbCVrCkA/bfUk7Of/t0W5t0nK1H3oyGZEc/lRMauCknDBka3Gz11dVss2QT19WQNh0u7bHVaT/U4lepX1j9Zv]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wFDl3INEnA3EQDHRX40avqGe1OMoJxyy:6ncCRVRTIRuYI5o/gayeuWCudWvmKNYr8KEHAWeTq34a5bdcKInBdKhjmjX+wLHqsEwQ5gcyhcxy4Ri2mbuN6AHazfZOZlubQkGlyUOAIYO5D5jkbyIh40DAtjVzo1MD/0HsW9zdGOzqUKp5xJJeDsbR4F153jbxa7fvwF90Q4UQjYFTKAtExEmHtDGSJG48ToVwTabTV/OnISMIggDZBviIv2QWHvXgK07b2mUj34rHJywEDGN1nj5rITTDdUeRcB1x4BAMOe94kTFPSTaj/OszvYlGECt8rkKFqbm092qL+XLfiBaImqe/WJHRCnAj6Don]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wAh+dBuZopv6vruVOYegUcq/aBnbksT1:qIJfCfDvDWiqicMOkmbJs/0n7UJLKNmgMQaKzeQ8J7Q60YpXbtWzKVW3tS6lzlgf64m3MrPXyo1C+mWh6jkjsb18T/OfggZy1ZHM4AcsOC6/ldUkV5YtuxUQuAmd5jCuV/R7iuYY8Z66AcfAevlb+bnLpgIifdA8fh/IktOo58nZUQwZDdppAacmftsLc6Frn5Er6A6+EXpxK1nmnlmLJ4AJztqlh6X0r+JvE2O7qeoZUXrIegnkxo7Aay7I/dd8zdYpp7ICSiTEtfVN/xNIu/5QmTRU7gWoz7cPl9epq4aiEALzPOzb6KVOiRcsOg+TlFvLQ71Ik5o=]"
|
||||
"CODECOV_TOKEN": "EJ[1:Kqnm+k1Z4p8nr7GqMczXnzh6azTk39tj3bAbCKPitUc=:EzVa4Gpj2Qn5OhZQlVfGFchuROgupvnW:CbWc6sNh1GCrAbrncxDjW00zUAD/Sa+ccg7CFSz8Ua6LnCYnSddTBxJWcJEbEs0MrjuZRQ==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:Kqnm+k1Z4p8nr7GqMczXnzh6azTk39tj3bAbCKPitUc=:qF7QrUM8j+19mptcE1YS71CqmrCM13Ah:TZCatJeT1egCHiufE6cGFC1VsdJkKaaqV6QKWkEsMPBKvOAdaZbbVz9Kl+lGnIsF]",
|
||||
"INFLUX_DATABASE": "EJ[1:Kqnm+k1Z4p8nr7GqMczXnzh6azTk39tj3bAbCKPitUc=:PetD/4c/EbkQmFEcK21g3cBBAPwFqHEw:wvYmDZRajy2WngVFs9AlwyHk]",
|
||||
"INFLUX_USERNAME": "EJ[1:Kqnm+k1Z4p8nr7GqMczXnzh6azTk39tj3bAbCKPitUc=:WcnqZdmDFtJJ01Zu5LbeGgbYGfRzBdFc:a7c5zDDtCOu5L1Qd2NKkxT6kljyBcbck]",
|
||||
"INFLUX_PASSWORD": "EJ[1:Kqnm+k1Z4p8nr7GqMczXnzh6azTk39tj3bAbCKPitUc=:LIZgP9Tp9yE9OlpV8iogmLOI7iW7SiU3:x0nYdT1A6sxu+O+MMLIN19d2t6rrK1qJ3+HnoWG3PDodsXjz06YJWQKU/mx6saqH+QbGtGV5mk0=]"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -10,8 +10,6 @@
|
||||
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"
|
||||
)
|
||||
|
||||
#
|
||||
|
@@ -14,18 +14,14 @@ export PS4="++"
|
||||
(
|
||||
set -x
|
||||
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
|
||||
MAX_CACHE_SIZE=18 # gigabytes
|
||||
|
||||
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 10 ]]; then
|
||||
echo "$d has gotten too large, removing it"
|
||||
rm -rf "$d"
|
||||
fi
|
||||
else
|
||||
echo "--- $d not present"
|
||||
fi
|
||||
|
||||
mkdir -p "$d"/target
|
||||
|
@@ -10,13 +10,7 @@
|
||||
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
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
fi
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
|
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
|
||||
|
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: 30
|
||||
|
||||
# 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.
|
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,17 +1,15 @@
|
||||
/target/
|
||||
/book/html/
|
||||
/book/src/img/
|
||||
/book/src/tests.ok
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
/solana-metrics/
|
||||
/solana-metrics.tar.bz2
|
||||
/target/
|
||||
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# node config that is rsynced
|
||||
/config/
|
||||
# node config that remains local
|
||||
/config-local/
|
||||
|
||||
# log files
|
||||
*.log
|
||||
@@ -20,4 +18,3 @@ log-*.txt
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
77
.mergify.yml
77
.mergify.yml
@@ -1,77 +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.16 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.16
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.16
|
||||
- name: v0.17 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.17
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.17
|
||||
- name: v0.18 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.18
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.18
|
||||
- name: v0.19 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.19
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.19
|
||||
- name: v0.20 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.20
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.20
|
||||
- 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
|
44
.travis.yml
44
.travis.yml
@@ -1,44 +0,0 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
cache: cargo
|
||||
rust:
|
||||
- 1.37.0
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
- test $rust_stable = $TRAVIS_RUST_VERSION # Update .travis.yml rust version above when this fails
|
||||
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- ci/publish-tarball.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||
|
||||
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
|
@@ -8,56 +8,6 @@ don't agree with a convention, submit a PR patching this document and let's disc
|
||||
the PR is accepted, *all* code should be updated as soon as possible to reflect the new
|
||||
conventions.
|
||||
|
||||
Pull Requests
|
||||
---
|
||||
|
||||
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 cherrypick it. Open a PR to start the review
|
||||
process and then jump back to your original branch to keep making progress. Once the commit
|
||||
is merged, you can use git-rebase to purge it from your original branch.
|
||||
|
||||
```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.
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
---
|
||||
|
||||
@@ -96,23 +46,24 @@ understood. Avoid introducing new 3-letter terms, which can be confused with 3-l
|
||||
[Terms currently in use](book/src/terminology.md)
|
||||
|
||||
|
||||
Design Proposals
|
||||
Proposing architectural changes
|
||||
---
|
||||
|
||||
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://solana-labs.github.io/book-edge/proposals.html) chapter.
|
||||
Here's the full process:
|
||||
change the architecture, you'll need to at least propose a change the content
|
||||
under the [Proposed
|
||||
Changes](https://solana-labs.github.io/solana/proposals.html) 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.
|
||||
1. Propose to a change to the architecture 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 the editor and 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.
|
||||
editor and 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
|
||||
need for tweaks to the architecture, 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.
|
||||
4. Once the implementation is complete, the editor will then work to integrate
|
||||
the document into the book.
|
||||
|
5125
Cargo.lock
generated
5125
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
154
Cargo.toml
154
Cargo.toml
@@ -1,61 +1,117 @@
|
||||
[package]
|
||||
name = "solana"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.11.1"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
|
||||
|
||||
[features]
|
||||
bpf_c = ["solana-bpfloader/bpf_c"]
|
||||
chacha = []
|
||||
cuda = []
|
||||
erasure = []
|
||||
ipv6 = []
|
||||
test = []
|
||||
unstable = []
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.0.0"
|
||||
bs58 = "0.2.0"
|
||||
bv = { version = "0.10.0", features = ["serde"] }
|
||||
byteorder = "1.2.1"
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
hashbrown = "0.1.7"
|
||||
indexmap = "1.0"
|
||||
itertools = "0.8.0"
|
||||
libc = "0.2.45"
|
||||
log = "0.4.2"
|
||||
nix = "0.12.0"
|
||||
rand = "0.6.1"
|
||||
rand_chacha = "0.1.0"
|
||||
rayon = "1.0.0"
|
||||
reqwest = "0.9.0"
|
||||
ring = "0.13.2"
|
||||
rocksdb = "0.10.1"
|
||||
serde = "1.0.82"
|
||||
serde_derive = "1.0.82"
|
||||
serde_json = "1.0.10"
|
||||
solana-bpfloader = { path = "programs/native/bpf_loader", version = "0.11.1" }
|
||||
solana-drone = { path = "drone", version = "0.11.1" }
|
||||
solana-jsonrpc-core = "0.4.0"
|
||||
solana-jsonrpc-http-server = "0.4.0"
|
||||
solana-jsonrpc-macros = "0.4.0"
|
||||
solana-jsonrpc-pubsub = "0.4.0"
|
||||
solana-jsonrpc-ws-server = "0.4.0"
|
||||
solana-logger = { path = "logger", version = "0.11.1" }
|
||||
solana-metrics = { path = "metrics", version = "0.11.1" }
|
||||
solana-native-loader = { path = "programs/native/native_loader", version = "0.11.1" }
|
||||
solana-netutil = { path = "netutil", version = "0.11.1" }
|
||||
solana-sdk = { path = "sdk", version = "0.11.1" }
|
||||
solana-system-program = { path = "programs/native/system", version = "0.11.1" }
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.1.1"
|
||||
matches = "0.1.6"
|
||||
|
||||
[[bench]]
|
||||
name = "bank"
|
||||
|
||||
[[bench]]
|
||||
name = "banking_stage"
|
||||
|
||||
[[bench]]
|
||||
name = "db_ledger"
|
||||
|
||||
[[bench]]
|
||||
name = "ledger"
|
||||
|
||||
[[bench]]
|
||||
name = "signature"
|
||||
|
||||
[[bench]]
|
||||
name = "sigverify"
|
||||
|
||||
[[bench]]
|
||||
required-features = ["chacha"]
|
||||
name = "chacha"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"bench-exchange",
|
||||
".",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"fullnode",
|
||||
"fullnode-config",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"local_cluster",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"programs/bpf",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"programs/config_tests",
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"upload-perf",
|
||||
"validator-info",
|
||||
"utils/netutil",
|
||||
"utils/fixed_buf",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf/rust/noop",
|
||||
"programs/native/bpf_loader",
|
||||
"programs/native/budget",
|
||||
"programs/native/erc20",
|
||||
"programs/native/lua_loader",
|
||||
"programs/native/native_loader",
|
||||
"programs/native/noop",
|
||||
"programs/native/storage",
|
||||
"programs/native/system",
|
||||
"programs/native/vote",
|
||||
"replicator",
|
||||
"sdk",
|
||||
"upload-perf",
|
||||
"vote-signer",
|
||||
"wallet",
|
||||
]
|
||||
|
113
README.md
113
README.md
@@ -30,40 +30,6 @@ Before you jump into the code, review the online book [Solana: Blockchain Rebuil
|
||||
|
||||
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
|
||||
Developing
|
||||
===
|
||||
|
||||
@@ -75,10 +41,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.37.0, please update it:
|
||||
If your rustc version is lower than 1.31.0, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@@ -100,12 +66,7 @@ $ cd solana
|
||||
Build
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
Then to run a minimal local cluster
|
||||
```bash
|
||||
$ ./run.sh
|
||||
$ cargo build --all
|
||||
```
|
||||
|
||||
Testing
|
||||
@@ -114,28 +75,40 @@ Testing
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
$ cargo test
|
||||
$ cargo test --all
|
||||
```
|
||||
|
||||
To emulate all the tests that will run on a Pull Request, run:
|
||||
|
||||
```bash
|
||||
$ ./ci/run-local.sh
|
||||
```
|
||||
|
||||
Local Testnet
|
||||
---
|
||||
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://solana-labs.github.io/book/getting-started.html).
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://solana-labs.github.io/solana/getting-started.html).
|
||||
|
||||
Remote Testnets
|
||||
---
|
||||
|
||||
We maintain several testnets:
|
||||
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com, with an https proxy for web apps at api.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
|
||||
* `testnet-perf` - permissioned stable testnet running a 24/7 soak test
|
||||
* `testnet-beta-perf` - permissioned beta channel testnet running a multi-hour soak test weekday mornings
|
||||
* `testnet-edge-perf` - permissioned edge channel testnet running a multi-hour soak test weekday mornings
|
||||
|
||||
## Deploy process
|
||||
|
||||
They are deployed with the `ci/testnet-manager.sh` script through a list of [scheduled
|
||||
buildkite jobs](https://buildkite.com/solana-labs/testnet-management/settings/schedules).
|
||||
Each testnet can be manually manipulated from buildkite as well.
|
||||
Each testnet can be manually manipulated from buildkite as well. The `-perf`
|
||||
testnets use a release tarball while the non`-perf` builds use the snap build
|
||||
(we've observed that the snap build runs slower than a tarball but this has yet
|
||||
to be root caused).
|
||||
|
||||
## How do I reset the testnet?
|
||||
Manually trigger the [testnet-management](https://buildkite.com/solana-labs/testnet-management) pipeline
|
||||
@@ -156,52 +129,10 @@ can run your own testnet using the scripts in the `net/` directory.
|
||||
Edit `ci/testnet-manager.sh`
|
||||
|
||||
|
||||
## Metrics Server Maintenance
|
||||
Sometimes the dashboard becomes unresponsive. This happens due to glitch in the metrics server.
|
||||
The current solution is to reset the metrics server. Use the following steps.
|
||||
|
||||
1. The server is hosted in a GCP VM instance. Check if the VM instance is down by trying to SSH
|
||||
into it from the GCP console. The name of the VM is ```metrics-solana-com```.
|
||||
2. If the VM is inaccessible, reset it from the GCP console.
|
||||
3. Once VM is up (or, was already up), the metrics services can be restarted from build automation.
|
||||
1. Navigate to https://buildkite.com/solana-labs/metrics-dot-solana-dot-com in your web browser
|
||||
2. Click on ```New Build```
|
||||
3. This will show a pop up dialog. Click on ```options``` drop down.
|
||||
4. Type in ```FORCE_START=true``` in ```Environment Variables``` text box.
|
||||
5. Click ```Create Build```
|
||||
6. This will restart the metrics services, and the dashboards should be accessible afterwards.
|
||||
|
||||
## Debugging Testnet
|
||||
Testnet may exhibit different symptoms of failures. Primary statistics to check are
|
||||
1. Rise in Confirmation Time
|
||||
2. Nodes are not voting
|
||||
3. Panics, and OOM notifications
|
||||
|
||||
Check the following if there are any signs of failure.
|
||||
1. Did testnet deployment fail?
|
||||
1. View buildkite logs for the last deployment: https://buildkite.com/solana-labs/testnet-management
|
||||
2. Use the relevant branch
|
||||
3. If the deployment failed, look at the build logs. The build artifacts for each remote node is uploaded.
|
||||
It's a good first step to triage from these logs.
|
||||
2. You may have to log into remote node if the deployment succeeded, but something failed during runtime.
|
||||
1. Get the private key for the testnet deployment from ```metrics-solana-com``` GCP instance.
|
||||
2. SSH into ```metrics-solana-com``` using GCP console and do the following.
|
||||
```bash
|
||||
sudo bash
|
||||
cd ~buildkite-agent/.ssh
|
||||
ls
|
||||
```
|
||||
3. Copy the relevant private key to your local machine
|
||||
4. Find the public IP address of the AWS instance for the remote node using AWS console
|
||||
5. ```ssh -i <private key file> ubuntu@<ip address of remote node>```
|
||||
6. The logs are in ```~solana\solana``` folder
|
||||
|
||||
|
||||
Benchmarking
|
||||
---
|
||||
|
||||
First install the nightly build of rustc. `cargo bench` requires use of the
|
||||
unstable features only available in the nightly build.
|
||||
First install the nightly build of rustc. `cargo bench` requires unstable features:
|
||||
|
||||
```bash
|
||||
$ rustup install nightly
|
||||
@@ -210,7 +141,7 @@ $ rustup install nightly
|
||||
Run the benchmarks:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly bench
|
||||
$ cargo +nightly bench --features="unstable"
|
||||
```
|
||||
|
||||
Release Process
|
||||
@@ -240,3 +171,5 @@ problem is solved by this code?" On the other hand, if a test does fail and you
|
||||
better way to solve the same problem, a Pull Request with your solution would most certainly be
|
||||
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
|
||||
send us that patch!
|
||||
|
||||
|
||||
|
122
RELEASE.md
122
RELEASE.md
@@ -43,7 +43,8 @@ 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
|
||||
branch, and cause that *X.Y.Z* release to be shipped to https://crates.io,
|
||||
https://snapcraft.io/, and elsewhere.
|
||||
|
||||
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
|
||||
@@ -61,116 +62,29 @@ There are three release channels that map to branches as follows:
|
||||
|
||||
## Release Steps
|
||||
|
||||
### Creating a new branch from master
|
||||
### Changing channels
|
||||
|
||||
When cutting a new channel branch these pre-steps are required:
|
||||
|
||||
#### Create the new branch
|
||||
1. Pick your branch point for release on master.
|
||||
1. Create the branch. 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. Note the Cargo.toml in the repo root directory does not contain a version. Look at any other Cargo.toml file.
|
||||
1. Create a new branch and push this branch to the solana repository.
|
||||
1. `git checkout -b <branchname>`
|
||||
1. `git push -u origin <branchname>`
|
||||
2. Create the branch. 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".
|
||||
4. Push the new branch to the solana repository
|
||||
3. Update Cargo.toml on master to the next semantic version (e.g. 0.9.0 -> 0.10.0) by running `./scripts/increment-cargo-version.sh`.
|
||||
5. Land your Cargo.toml change as a master PR.
|
||||
|
||||
#### Update master with the next version
|
||||
At this point, ci/channel-info.sh should show your freshly cut release branch as "BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
|
||||
1. After the new branch has been created and pushed, update Cargo.toml on **master** to the next semantic version (e.g. 0.9.0 -> 0.10.0)
|
||||
by running `./scripts/increment-cargo-version.sh`, then rebuild with
|
||||
`cargo build` to cause a refresh of `Cargo.lock`.
|
||||
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
|
||||
master branch
|
||||
|
||||
At this point, `ci/channel-info.sh` should show your freshly cut release branch as
|
||||
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
|
||||
### Update documentation
|
||||
|
||||
Document the new recommended version by updating
|
||||
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
|
||||
in book/src/testnet-participation.md on the release (beta) branch.
|
||||
|
||||
### Make the Release
|
||||
### Updating channels (i.e. "making a release")
|
||||
|
||||
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
|
||||
1. Go [there ;)](https://github.com/solana-labs/solana/releases).
|
||||
1. Click "Draft new release". The release tag must exactly match the `version`
|
||||
field in `/Cargo.toml` prefixed by `v` (ie, `<branchname>.X`).
|
||||
1. If the Cargo.toml verion field is **0.12.3**, then the release tag must be **v0.12.3**
|
||||
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)
|
||||
and fill it in.
|
||||
1. Test the release by generating a tag using semver's rules. First try at a
|
||||
release should be `<branchname>.X-rc.0`.
|
||||
1. Verify release automation:
|
||||
2. Click "Draft new release". The release tag must exactly match the `version` field in `/Cargo.toml` prefixed by `v` (ie, `<branchname>.X`).
|
||||
3. If the first major release on the branch (e.g. v0.8.0), paste in [this template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md) and fill it in.
|
||||
4. Test the release by generating a tag using semver's rules. First try at a release should be `<branchname>.X-rc.0`.
|
||||
5. Verify release automation:
|
||||
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
|
||||
1. Once the release has been made, update Cargo.toml on the release branch to the next
|
||||
semantic version (e.g. 0.9.0 -> 0.9.1) by running
|
||||
`./scripts/increment-cargo-version.sh patch`, then rebuild with `cargo
|
||||
build` to cause a refresh of `Cargo.lock`.
|
||||
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
|
||||
release branch.
|
||||
2. ...
|
||||
6. After testnet deployment, verify that testnets are running correct software. http://metrics.solana.com should show testnet running on a hash from your newly created branch.
|
||||
7. Once the release has been made, update Cargo.toml on release to the next semantic version (e.g. 0.9.0 -> 0.9.1) by running `./scripts/increment-cargo-version.sh patch`.
|
||||
|
||||
### 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
|
||||
|
4
bench-exchange/.gitignore
vendored
4
bench-exchange/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@@ -1,43 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.18.0-pre2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.4"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.99"
|
||||
serde_derive = "1.0.99"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana-core = { path = "../core", version = "0.18.0-pre2" }
|
||||
solana-local-cluster = { path = "../local_cluster", version = "0.18.0-pre2" }
|
||||
solana-client = { path = "../client", version = "0.18.0-pre2" }
|
||||
solana-drone = { path = "../drone", version = "0.18.0-pre2" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.18.0-pre2" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.18.0-pre2" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.18.0-pre2" }
|
||||
solana-netutil = { path = "../utils/netutil", version = "0.18.0-pre2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.18.0-pre2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre2" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.0"
|
||||
|
||||
[features]
|
||||
cuda = ["solana-core/cuda"]
|
||||
|
@@ -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 4 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,218 +0,0 @@
|
||||
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub drone_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)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_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>() -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_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("drone")
|
||||
.short("d")
|
||||
.long("drone")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the drone; 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_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse drone address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair(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,87 +0,0 @@
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate solana_exchange_program;
|
||||
|
||||
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().get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
drone_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, _replicators) =
|
||||
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,
|
||||
&drone_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_api::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))
|
||||
}
|
||||
}
|
2
bench-streamer/.gitignore
vendored
2
bench-streamer/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@@ -2,17 +2,16 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.18.0-pre2"
|
||||
version = "0.11.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-core = { path = "../core", version = "0.18.0-pre2" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre2" }
|
||||
solana-netutil = { path = "../utils/netutil", version = "0.18.0-pre2" }
|
||||
clap = "2.32.0"
|
||||
solana = { path = "..", version = "0.11.1" }
|
||||
solana-logger = { path = "../logger", version = "0.11.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.11.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana-core/cuda"]
|
||||
|
||||
cuda = []
|
||||
|
@@ -1,8 +1,7 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana_core::packet::PacketsRecycler;
|
||||
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana_core::result::Result;
|
||||
use solana_core::streamer::{receiver, PacketReceiver};
|
||||
use clap::{App, Arg};
|
||||
use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana::result::Result;
|
||||
use solana::streamer::{receiver, PacketReceiver};
|
||||
use std::cmp::max;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
@@ -15,19 +14,19 @@ use std::time::SystemTime;
|
||||
|
||||
fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut msgs = Packets::default();
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in msgs.packets.iter_mut() {
|
||||
let msgs = SharedPackets::default();
|
||||
let msgs_ = msgs.clone();
|
||||
msgs.write().unwrap().packets.resize(10, Packet::default());
|
||||
for w in &mut msgs.write().unwrap().packets {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
}
|
||||
let msgs = Arc::new(msgs);
|
||||
spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
let mut num = 0;
|
||||
for p in &msgs.packets {
|
||||
for p in &msgs_.read().unwrap().packets {
|
||||
let a = p.meta.addr();
|
||||
assert!(p.meta.size < BLOB_SIZE);
|
||||
send.send_to(&p.data[..p.meta.size], &a).unwrap();
|
||||
@@ -44,7 +43,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
}
|
||||
let timer = Duration::new(1, 0);
|
||||
if let Ok(msgs) = r.recv_timeout(timer) {
|
||||
rvs.fetch_add(msgs.packets.len(), Ordering::Relaxed);
|
||||
rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -52,9 +51,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
fn main() -> Result<()> {
|
||||
let mut num_sockets = 1usize;
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
let matches = App::new("solana-bench-streamer")
|
||||
.arg(
|
||||
Arg::with_name("num-recv-sockets")
|
||||
.long("num-recv-sockets")
|
||||
@@ -75,7 +72,6 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_netutil::bind_to(port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@@ -87,10 +83,9 @@ fn main() -> Result<()> {
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
&exit,
|
||||
exit.clone(),
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
"bench-streamer",
|
||||
));
|
||||
}
|
||||
|
||||
|
4
bench-tps/.gitignore
vendored
4
bench-tps/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@@ -2,34 +2,20 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.18.0-pre2"
|
||||
version = "0.11.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.99"
|
||||
serde_derive = "1.0.99"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana-core = { path = "../core", version = "0.18.0-pre2" }
|
||||
solana-local-cluster = { path = "../local_cluster", version = "0.18.0-pre2" }
|
||||
solana-client = { path = "../client", version = "0.18.0-pre2" }
|
||||
solana-drone = { path = "../drone", version = "0.18.0-pre2" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.18.0-pre2" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.18.0-pre2" }
|
||||
solana-measure = { path = "../measure", version = "0.18.0-pre2" }
|
||||
solana-netutil = { path = "../utils/netutil", version = "0.18.0-pre2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.18.0-pre2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre2" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.18.0-pre2" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.18.0-pre2" }
|
||||
clap = "2.32.0"
|
||||
rayon = "1.0.3"
|
||||
serde_json = "1.0.10"
|
||||
solana = { path = "..", version = "0.11.1" }
|
||||
solana-drone = { path = "../drone", version = "0.11.1" }
|
||||
solana-logger = { path = "../logger", version = "0.11.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.11.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.11.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana-core/cuda"]
|
||||
|
||||
cuda = []
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,61 +2,52 @@ use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
|
||||
use clap::{crate_version, App, Arg, ArgMatches};
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
|
||||
/// Holds the configuration for a single run of the benchmark
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub network_addr: SocketAddr,
|
||||
pub drone_addr: SocketAddr,
|
||||
pub id: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub tx_count: 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 use_move: bool,
|
||||
pub reject_extra_nodes: bool,
|
||||
pub converge_only: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
|
||||
id: Keypair::new(),
|
||||
threads: 4,
|
||||
num_nodes: 1,
|
||||
duration: Duration::new(std::u64::MAX, 0),
|
||||
tx_count: 500_000,
|
||||
thread_batch_sleep_ms: 0,
|
||||
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,
|
||||
use_move: false,
|
||||
reject_extra_nodes: false,
|
||||
converge_only: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines and builds the CLI args for a run of the benchmark
|
||||
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new(crate_name!()).about(crate_description!())
|
||||
App::new("solana-bench-tps")
|
||||
.version(crate_version!())
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
Arg::with_name("network")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.long("network")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
|
||||
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("drone")
|
||||
@@ -64,7 +55,7 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.long("drone")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Location of the drone; defaults to entrypoint:DRONE_PORT"),
|
||||
.help("Location of the drone; defaults to network:DRONE_PORT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
@@ -82,6 +73,11 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("reject-extra-nodes")
|
||||
.long("reject-extra-nodes")
|
||||
.help("Require exactly `num-nodes` on convergence. Appropriate only for internal networks"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.short("t")
|
||||
@@ -97,16 +93,16 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("converge-only")
|
||||
.long("converge-only")
|
||||
.help("Exit immediately after converging"),
|
||||
)
|
||||
.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("tx_count")
|
||||
.long("tx_count")
|
||||
@@ -114,38 +110,6 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
.help("Number of transactions to send per batch")
|
||||
)
|
||||
.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",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a clap `ArgMatches` structure into a `Config`
|
||||
@@ -156,15 +120,15 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
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_netutil::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
if let Some(addr) = matches.value_of("network") {
|
||||
args.network_addr = addr.parse().unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse network: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(addr) = matches.value_of("drone") {
|
||||
args.drone_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
args.drone_addr = addr.parse().unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse drone address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
@@ -194,31 +158,9 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
args.tx_count = s.to_string().parse().expect("can't parse tx_account");
|
||||
}
|
||||
|
||||
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.converge_only = matches.is_present("converge-only");
|
||||
args.reject_extra_nodes = matches.is_present("reject-extra-nodes");
|
||||
|
||||
args
|
||||
}
|
||||
|
@@ -1,136 +1,304 @@
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate solana_move_loader_program;
|
||||
|
||||
mod bench;
|
||||
mod cli;
|
||||
|
||||
use crate::bench::{
|
||||
do_bench_tps, generate_and_fund_keypairs, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
|
||||
};
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use solana::client::mk_client;
|
||||
use solana::cluster_info::{ClusterInfo, NodeInfo};
|
||||
use solana::gossip_service::GossipService;
|
||||
|
||||
/// 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;
|
||||
use solana::service::Service;
|
||||
use solana::signature::GenKeys;
|
||||
use solana::thin_client::poll_gossip_for_leader;
|
||||
use solana_metrics;
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::thread::Builder;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::bench::*;
|
||||
|
||||
/// Creates a cluster and waits for the network to converge, returning the peers, leader, and gossip service
|
||||
/// # Arguments
|
||||
/// `leader` - the input leader node
|
||||
/// `exit_signal` - atomic bool used to signal early exit to cluster
|
||||
/// `num_nodes` - the number of nodes
|
||||
/// # Panics
|
||||
/// Panics if the spy node `RwLock` somehow ends up unreadable
|
||||
fn converge(
|
||||
leader: &NodeInfo,
|
||||
exit_signal: &Arc<AtomicBool>,
|
||||
num_nodes: usize,
|
||||
) -> (Vec<NodeInfo>, Option<NodeInfo>, GossipService) {
|
||||
//lets spy on the network
|
||||
let (node, gossip_socket) = ClusterInfo::spy_node();
|
||||
let mut spy_cluster_info = ClusterInfo::new(node);
|
||||
spy_cluster_info.insert_info(leader.clone());
|
||||
spy_cluster_info.set_leader(leader.id);
|
||||
let spy_ref = Arc::new(RwLock::new(spy_cluster_info));
|
||||
let gossip_service = GossipService::new(&spy_ref, None, gossip_socket, exit_signal.clone());
|
||||
let mut v: Vec<NodeInfo> = vec![];
|
||||
// wait for the network to converge, 30 seconds should be plenty
|
||||
for _ in 0..30 {
|
||||
{
|
||||
let spy_ref = spy_ref.read().unwrap();
|
||||
|
||||
println!("{}", spy_ref.node_info_trace());
|
||||
|
||||
if spy_ref.leader_data().is_some() {
|
||||
v = spy_ref.rpc_peers();
|
||||
if v.len() >= num_nodes {
|
||||
println!("CONVERGED!");
|
||||
break;
|
||||
} else {
|
||||
println!(
|
||||
"{} node(s) discovered (looking for {} or more)",
|
||||
v.len(),
|
||||
num_nodes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
sleep(Duration::new(1, 0));
|
||||
}
|
||||
let leader = spy_ref.read().unwrap().leader_data().cloned();
|
||||
(v, leader, gossip_service)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_filter("solana=info");
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cfg = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
network_addr: network,
|
||||
drone_addr,
|
||||
id,
|
||||
threads,
|
||||
num_nodes,
|
||||
duration,
|
||||
tx_count,
|
||||
thread_batch_sleep_ms,
|
||||
sustained,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
} = cli_config;
|
||||
reject_extra_nodes,
|
||||
converge_only,
|
||||
} = cfg;
|
||||
|
||||
if write_to_client_file {
|
||||
let (keypairs, _) = generate_keypairs(&id, tx_count as u64 * 2);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee = FeeCalculator::new(target_lamports_per_signature).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(),
|
||||
num_lamports_per_account,
|
||||
);
|
||||
});
|
||||
println!("Looking for leader at {:?}", network);
|
||||
let leader = poll_gossip_for_leader(network, None).expect("unable to find leader on network");
|
||||
|
||||
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;
|
||||
}
|
||||
let exit_signal = Arc::new(AtomicBool::new(false));
|
||||
let (nodes, leader, gossip_service) = converge(&leader, &exit_signal, num_nodes);
|
||||
|
||||
println!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
if nodes.len() < num_nodes {
|
||||
println!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
if reject_extra_nodes && nodes.len() > num_nodes {
|
||||
println!(
|
||||
"Error: Extra nodes discovered. Expecting exactly {}",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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();
|
||||
if leader.is_none() {
|
||||
println!("no leader");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
|
||||
let mut keypairs = vec![];
|
||||
let mut last_balance = 0;
|
||||
if converge_only {
|
||||
return;
|
||||
}
|
||||
|
||||
accounts.into_iter().for_each(|(keypair, balance)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = balance;
|
||||
});
|
||||
// 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(drone_addr),
|
||||
&id,
|
||||
tx_count,
|
||||
NUM_LAMPORTS_PER_ACCOUNT,
|
||||
use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
exit(1);
|
||||
let leader = leader.unwrap();
|
||||
|
||||
println!("leader RPC is at {} {}", leader.rpc, leader.id);
|
||||
let mut client = mk_client(&leader);
|
||||
let mut barrier_client = mk_client(&leader);
|
||||
|
||||
let mut seed = [0u8; 32];
|
||||
seed.copy_from_slice(&id.public_key_bytes()[..32]);
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
|
||||
println!("Creating {} keypairs...", tx_count * 2);
|
||||
let mut total_keys = 0;
|
||||
let mut target = tx_count * 2;
|
||||
while target > 0 {
|
||||
total_keys += target;
|
||||
target /= MAX_SPENDS_PER_TX;
|
||||
}
|
||||
let gen_keypairs = rnd.gen_n_keypairs(total_keys as u64);
|
||||
let barrier_id = rnd.gen_n_keypairs(1).pop().unwrap();
|
||||
|
||||
println!("Get tokens...");
|
||||
let num_tokens_per_account = 20;
|
||||
|
||||
// Sample the first keypair, see if it has tokens, if so then resume
|
||||
// to avoid token loss
|
||||
let keypair0_balance = client
|
||||
.poll_get_balance(&gen_keypairs.last().unwrap().pubkey())
|
||||
.unwrap_or(0);
|
||||
|
||||
if num_tokens_per_account > keypair0_balance {
|
||||
let extra = num_tokens_per_account - keypair0_balance;
|
||||
let total = extra * (gen_keypairs.len() as u64);
|
||||
airdrop_tokens(&mut client, &drone_addr, &id, total);
|
||||
println!("adding more tokens {}", extra);
|
||||
fund_keys(&mut client, &id, &gen_keypairs, extra);
|
||||
}
|
||||
let start = gen_keypairs.len() - (tx_count * 2) as usize;
|
||||
let keypairs = &gen_keypairs[start..];
|
||||
airdrop_tokens(&mut barrier_client, &drone_addr, &barrier_id, 1);
|
||||
|
||||
println!("Get last ID...");
|
||||
let mut last_id = client.get_last_id();
|
||||
println!("Got last ID {:?}", last_id);
|
||||
|
||||
let first_tx_count = client.transaction_count();
|
||||
println!("Initial transaction count {}", first_tx_count);
|
||||
|
||||
// Setup a thread per validator to sample every period
|
||||
// collect the max transaction rate and total tx count seen
|
||||
let maxes = Arc::new(RwLock::new(Vec::new()));
|
||||
let sample_period = 1; // in seconds
|
||||
println!("Sampling TPS every {} second...", sample_period);
|
||||
let v_threads: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let maxes = maxes.clone();
|
||||
Builder::new()
|
||||
.name("solana-client-sample".to_string())
|
||||
.spawn(move || {
|
||||
sample_tx_count(&exit_signal, &maxes, first_tx_count, &v, sample_period);
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
};
|
||||
.collect();
|
||||
|
||||
let config = Config {
|
||||
id,
|
||||
threads,
|
||||
thread_batch_sleep_ms,
|
||||
duration,
|
||||
tx_count,
|
||||
sustained,
|
||||
use_move,
|
||||
};
|
||||
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
|
||||
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
let shared_tx_active_thread_count = Arc::new(AtomicIsize::new(0));
|
||||
let total_tx_sent_count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let s_threads: Vec<_> = (0..threads)
|
||||
.map(|_| {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let shared_txs = shared_txs.clone();
|
||||
let leader = leader.clone();
|
||||
let shared_tx_active_thread_count = shared_tx_active_thread_count.clone();
|
||||
let total_tx_sent_count = total_tx_sent_count.clone();
|
||||
Builder::new()
|
||||
.name("solana-client-sender".to_string())
|
||||
.spawn(move || {
|
||||
do_tx_transfers(
|
||||
&exit_signal,
|
||||
&shared_txs,
|
||||
&leader,
|
||||
&shared_tx_active_thread_count,
|
||||
&total_tx_sent_count,
|
||||
);
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// generate and send transactions for the specified duration
|
||||
let start = Instant::now();
|
||||
let mut reclaim_tokens_back_to_source_account = false;
|
||||
let mut i = keypair0_balance;
|
||||
while start.elapsed() < duration {
|
||||
let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0);
|
||||
metrics_submit_token_balance(balance);
|
||||
|
||||
// ping-pong between source and destination accounts for each loop iteration
|
||||
// this seems to be faster than trying to determine the balance of individual
|
||||
// accounts
|
||||
let len = tx_count as usize;
|
||||
generate_txs(
|
||||
&shared_txs,
|
||||
&keypairs[..len],
|
||||
&keypairs[len..],
|
||||
threads,
|
||||
reclaim_tokens_back_to_source_account,
|
||||
&leader,
|
||||
);
|
||||
// In sustained mode overlap the transfers with generation
|
||||
// this has higher average performance but lower peak performance
|
||||
// in tested environments.
|
||||
if !sustained {
|
||||
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 {
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
// It's not feasible (would take too much time) to confirm each of the `tx_count / 2`
|
||||
// transactions sent by `generate_txs()` so instead send and confirm a single transaction
|
||||
// to validate the network is still functional.
|
||||
send_barrier_transaction(&mut barrier_client, &mut last_id, &barrier_id);
|
||||
|
||||
i += 1;
|
||||
if should_switch_directions(num_tokens_per_account, i) {
|
||||
reclaim_tokens_back_to_source_account = !reclaim_tokens_back_to_source_account;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the sampling threads so it will collect the stats
|
||||
exit_signal.store(true, Ordering::Relaxed);
|
||||
|
||||
println!("Waiting for validator threads...");
|
||||
for t in v_threads {
|
||||
if let Err(err) = t.join() {
|
||||
println!(" join() failed with: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// join the tx send threads
|
||||
println!("Waiting for transmit threads...");
|
||||
for t in s_threads {
|
||||
if let Err(err) = t.join() {
|
||||
println!(" join() failed with: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0);
|
||||
metrics_submit_token_balance(balance);
|
||||
|
||||
compute_and_report_stats(
|
||||
&maxes,
|
||||
sample_period,
|
||||
&start.elapsed(),
|
||||
total_tx_sent_count.load(Ordering::Relaxed),
|
||||
);
|
||||
|
||||
// join the cluster_info client threads
|
||||
gossip_service.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_switch_directions() {
|
||||
assert_eq!(should_switch_directions(20, 0), false);
|
||||
assert_eq!(should_switch_directions(20, 1), false);
|
||||
assert_eq!(should_switch_directions(20, 14), false);
|
||||
assert_eq!(should_switch_directions(20, 15), true);
|
||||
assert_eq!(should_switch_directions(20, 16), false);
|
||||
assert_eq!(should_switch_directions(20, 19), false);
|
||||
assert_eq!(should_switch_directions(20, 20), true);
|
||||
assert_eq!(should_switch_directions(20, 21), false);
|
||||
assert_eq!(should_switch_directions(20, 99), false);
|
||||
assert_eq!(should_switch_directions(20, 100), true);
|
||||
assert_eq!(should_switch_directions(20, 101), false);
|
||||
}
|
||||
}
|
||||
|
57
benches/bank.rs
Normal file
57
benches/bank.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana::bank::*;
|
||||
use solana::mint::Mint;
|
||||
use solana::status_deque::MAX_ENTRY_IDS;
|
||||
use solana_sdk::hash::hash;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use solana_sdk::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_iter()
|
||||
.map(|_| {
|
||||
// Seed the 'from' account.
|
||||
let rando0 = Keypair::new();
|
||||
let tx = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
rando0.pubkey(),
|
||||
10_000,
|
||||
bank.last_id(),
|
||||
0,
|
||||
);
|
||||
assert_eq!(bank.process_transaction(&tx), Ok(()));
|
||||
|
||||
// Seed the 'to' account and a cell for its signature.
|
||||
let rando1 = Keypair::new();
|
||||
let tx = Transaction::system_move(&rando0, rando1.pubkey(), 1, bank.last_id(), 0);
|
||||
assert_eq!(bank.process_transaction(&tx), Ok(()));
|
||||
|
||||
// Finally, return the transaction to the benchmark.
|
||||
tx
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut id = bank.last_id();
|
||||
|
||||
for _ in 0..(MAX_ENTRY_IDS - 1) {
|
||||
bank.register_tick(&id);
|
||||
id = hash(&id.as_ref())
|
||||
}
|
||||
|
||||
bencher.iter(|| {
|
||||
// Since benchmarker runs this multiple times, we need to clear the signatures.
|
||||
bank.clear_signatures();
|
||||
let results = bank.process_transactions(&transactions);
|
||||
assert!(results.iter().all(Result::is_ok));
|
||||
})
|
||||
}
|
232
benches/banking_stage.rs
Normal file
232
benches/banking_stage.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana::bank::Bank;
|
||||
use solana::banking_stage::{BankingStage, NUM_THREADS};
|
||||
use solana::entry::Entry;
|
||||
use solana::mint::Mint;
|
||||
use solana::packet::to_packets_chunked;
|
||||
use solana::status_deque::MAX_ENTRY_IDS;
|
||||
use solana_sdk::hash::hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::iter;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use test::Bencher;
|
||||
|
||||
fn check_txs(receiver: &Receiver<Vec<Entry>>, ref_tx_count: usize) {
|
||||
let mut total = 0;
|
||||
loop {
|
||||
let entries = receiver.recv_timeout(Duration::new(1, 0));
|
||||
if let Ok(entries) = entries {
|
||||
for entry in &entries {
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(total, ref_tx_count);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
let txes = 1000 * NUM_THREADS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let mint = Mint::new(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&mint));
|
||||
let dummy_leader_id = Keypair::new().pubkey();
|
||||
let dummy = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
mint.keypair().pubkey(),
|
||||
1,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
let transactions: Vec<_> = (0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||
new.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect();
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
tx.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
assert!(x.is_ok());
|
||||
});
|
||||
//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 verified: Vec<_> = to_packets_chunked(&transactions.clone(), 192)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
let (_stage, signal_receiver) = BankingStage::new(
|
||||
&bank,
|
||||
verified_receiver,
|
||||
Default::default(),
|
||||
&mint.last_id(),
|
||||
None,
|
||||
dummy_leader_id,
|
||||
);
|
||||
|
||||
let mut id = mint.last_id();
|
||||
for _ in 0..MAX_ENTRY_IDS {
|
||||
id = hash(&id.as_ref());
|
||||
bank.register_tick(&id);
|
||||
}
|
||||
|
||||
bencher.iter(move || {
|
||||
// make sure the tx last id is still registered
|
||||
if bank.count_valid_ids(&[mint.last_id()]).len() == 0 {
|
||||
bank.register_tick(&mint.last_id());
|
||||
}
|
||||
for v in verified.chunks(verified.len() / NUM_THREADS) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver, txes);
|
||||
bank.clear_signatures();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
||||
let progs = 4;
|
||||
let txes = 1000 * NUM_THREADS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let mint = Mint::new(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&mint));
|
||||
let dummy_leader_id = Keypair::new().pubkey();
|
||||
let dummy = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
mint.keypair().pubkey(),
|
||||
1,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
let transactions: Vec<_> = (0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let from: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
new.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||
new.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||
let prog = new.instructions[0].clone();
|
||||
for i in 1..progs {
|
||||
//generate programs that spend to random keys
|
||||
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
let to_key = Pubkey::new(&to[0..32]);
|
||||
new.account_keys.push(to_key);
|
||||
assert_eq!(new.account_keys.len(), i + 2);
|
||||
new.instructions.push(prog.clone());
|
||||
assert_eq!(new.instructions.len(), i + 1);
|
||||
new.instructions[i].accounts[1] = 1 + i as u8;
|
||||
assert_eq!(new.key(i, 1), Some(&to_key));
|
||||
assert_eq!(
|
||||
new.account_keys[new.instructions[i].accounts[1] as usize],
|
||||
to_key
|
||||
);
|
||||
}
|
||||
assert_eq!(new.instructions.len(), progs);
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect();
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
tx.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
assert!(bank.process_transaction(&fund).is_ok());
|
||||
});
|
||||
//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 verified: Vec<_> = to_packets_chunked(&transactions.clone(), 96)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
let (_stage, signal_receiver) = BankingStage::new(
|
||||
&bank,
|
||||
verified_receiver,
|
||||
Default::default(),
|
||||
&mint.last_id(),
|
||||
None,
|
||||
dummy_leader_id,
|
||||
);
|
||||
|
||||
let mut id = mint.last_id();
|
||||
for _ in 0..MAX_ENTRY_IDS {
|
||||
id = hash(&id.as_ref());
|
||||
bank.register_tick(&id);
|
||||
}
|
||||
|
||||
bencher.iter(move || {
|
||||
// make sure the transactions are still valid
|
||||
if bank.count_valid_ids(&[mint.last_id()]).len() == 0 {
|
||||
bank.register_tick(&mint.last_id());
|
||||
}
|
||||
for v in verified.chunks(verified.len() / NUM_THREADS) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver, txes);
|
||||
bank.clear_signatures();
|
||||
});
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
//#![feature(test)]
|
||||
//
|
||||
//extern crate solana_core;
|
||||
//extern crate solana;
|
||||
//extern crate test;
|
||||
//
|
||||
//use solana_core::chacha::chacha_cbc_encrypt_files;
|
||||
//use solana::chacha::chacha_cbc_encrypt_files;
|
||||
//use std::fs::remove_file;
|
||||
//use std::fs::File;
|
||||
//use std::io::Write;
|
199
benches/db_ledger.rs
Normal file
199
benches/db_ledger.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
#![feature(test)]
|
||||
use rand;
|
||||
|
||||
extern crate test;
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rocksdb::{Options, DB};
|
||||
use solana::db_ledger::{DataCf, DbLedger, LedgerColumnFamilyRaw};
|
||||
use solana::ledger::{get_tmp_ledger_path, make_large_test_entries, make_tiny_test_entries, Block};
|
||||
use solana::packet::{Blob, BLOB_HEADER_SIZE};
|
||||
use test::Bencher;
|
||||
|
||||
// Given some blobs and a ledger at ledger_path, benchmark writing the blobs to the ledger
|
||||
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut [&mut Blob], ledger_path: &str) {
|
||||
let db_ledger =
|
||||
DbLedger::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
let slot = 0;
|
||||
let num_blobs = blobs.len();
|
||||
bench.iter(move || {
|
||||
for blob in blobs.iter_mut() {
|
||||
let index = blob.index().unwrap();
|
||||
let key = DataCf::key(slot, index);
|
||||
let size = blob.size().unwrap();
|
||||
db_ledger
|
||||
.data_cf
|
||||
.put(&db_ledger.db, &key, &blob.data[..BLOB_HEADER_SIZE + size])
|
||||
.unwrap();
|
||||
blob.set_index(index + num_blobs as u64).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
DB::destroy(&Options::default(), &ledger_path)
|
||||
.expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
// Insert some blobs into the ledger in preparation for read benchmarks
|
||||
fn setup_read_bench(
|
||||
db_ledger: &mut DbLedger,
|
||||
num_small_blobs: u64,
|
||||
num_large_blobs: u64,
|
||||
slot: u64,
|
||||
) {
|
||||
// Make some big and small entries
|
||||
let mut entries = make_large_test_entries(num_large_blobs as usize);
|
||||
entries.extend(make_tiny_test_entries(num_small_blobs as usize));
|
||||
|
||||
// Convert the entries to blobs, write the blobs to the ledger
|
||||
let shared_blobs = entries.to_blobs();
|
||||
for b in shared_blobs.iter() {
|
||||
b.write().unwrap().set_slot(slot).unwrap();
|
||||
}
|
||||
db_ledger
|
||||
.write_shared_blobs(&shared_blobs)
|
||||
.expect("Expectd successful insertion of blobs into ledger");
|
||||
}
|
||||
|
||||
// Write small blobs to the ledger
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_write_small(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_write_small");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_tiny_test_entries(num_entries);
|
||||
let shared_blobs = entries.to_blobs();
|
||||
let mut blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.write().unwrap()).collect();
|
||||
let mut blobs: Vec<&mut Blob> = blob_locks.iter_mut().map(|b| &mut **b).collect();
|
||||
bench_write_blobs(bench, &mut blobs, &ledger_path);
|
||||
}
|
||||
|
||||
// Write big blobs to the ledger
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_write_big(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_write_big");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_tiny_test_entries(num_entries);
|
||||
let shared_blobs = entries.to_blobs();
|
||||
let mut blob_locks: Vec<_> = shared_blobs.iter().map(|b| b.write().unwrap()).collect();
|
||||
let mut blobs: Vec<&mut Blob> = blob_locks.iter_mut().map(|b| &mut **b).collect();
|
||||
bench_write_blobs(bench, &mut blobs, &ledger_path);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_read_sequential(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_read_sequential");
|
||||
let mut db_ledger =
|
||||
DbLedger::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
// Insert some big and small blobs into the ledger
|
||||
let num_small_blobs = 32 * 1024;
|
||||
let num_large_blobs = 32 * 1024;
|
||||
let total_blobs = num_small_blobs + num_large_blobs;
|
||||
let slot = 0;
|
||||
setup_read_bench(&mut db_ledger, num_small_blobs, num_large_blobs, slot);
|
||||
|
||||
let num_reads = total_blobs / 15;
|
||||
let mut rng = rand::thread_rng();
|
||||
bench.iter(move || {
|
||||
// Generate random starting point in the range [0, total_blobs - 1], read num_reads blobs sequentially
|
||||
let start_index = rng.gen_range(0, num_small_blobs + num_large_blobs);
|
||||
for i in start_index..start_index + num_reads {
|
||||
let _ =
|
||||
db_ledger
|
||||
.data_cf
|
||||
.get_by_slot_index(&db_ledger.db, slot, i as u64 % total_blobs);
|
||||
}
|
||||
});
|
||||
|
||||
DB::destroy(&Options::default(), &ledger_path)
|
||||
.expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_read_random(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_read_random");
|
||||
let mut db_ledger =
|
||||
DbLedger::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
// Insert some big and small blobs into the ledger
|
||||
let num_small_blobs = 32 * 1024;
|
||||
let num_large_blobs = 32 * 1024;
|
||||
let total_blobs = num_small_blobs + num_large_blobs;
|
||||
let slot = 0;
|
||||
setup_read_bench(&mut db_ledger, num_small_blobs, num_large_blobs, slot);
|
||||
|
||||
let num_reads = total_blobs / 15;
|
||||
|
||||
// Generate a num_reads sized random sample of indexes in range [0, total_blobs - 1],
|
||||
// simulating random reads
|
||||
let mut rng = rand::thread_rng();
|
||||
let indexes: Vec<usize> = (0..num_reads)
|
||||
.map(|_| rng.gen_range(0, total_blobs) as usize)
|
||||
.collect();
|
||||
bench.iter(move || {
|
||||
for i in indexes.iter() {
|
||||
let _ = db_ledger
|
||||
.data_cf
|
||||
.get_by_slot_index(&db_ledger.db, slot, *i as u64);
|
||||
}
|
||||
});
|
||||
|
||||
DB::destroy(&Options::default(), &ledger_path)
|
||||
.expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_insert_data_blob_small(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_insert_data_blob_small");
|
||||
let db_ledger =
|
||||
DbLedger::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_tiny_test_entries(num_entries);
|
||||
let mut shared_blobs = entries.to_blobs();
|
||||
shared_blobs.shuffle(&mut thread_rng());
|
||||
|
||||
bench.iter(move || {
|
||||
for blob in shared_blobs.iter_mut() {
|
||||
let index = blob.read().unwrap().index().unwrap();
|
||||
db_ledger.write_shared_blobs(vec![blob.clone()]).unwrap();
|
||||
blob.write()
|
||||
.unwrap()
|
||||
.set_index(index + num_entries as u64)
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
DB::destroy(&Options::default(), &ledger_path)
|
||||
.expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_insert_data_blob_big(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path("bench_insert_data_blob_big");
|
||||
let db_ledger =
|
||||
DbLedger::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_large_test_entries(num_entries);
|
||||
let mut shared_blobs = entries.to_blobs();
|
||||
shared_blobs.shuffle(&mut thread_rng());
|
||||
|
||||
bench.iter(move || {
|
||||
for blob in shared_blobs.iter_mut() {
|
||||
let index = blob.read().unwrap().index().unwrap();
|
||||
db_ledger.write_shared_blobs(vec![blob.clone()]).unwrap();
|
||||
blob.write()
|
||||
.unwrap()
|
||||
.set_index(index + num_entries as u64)
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
DB::destroy(&Options::default(), &ledger_path)
|
||||
.expect("Expected successful database destruction");
|
||||
}
|
@@ -2,10 +2,12 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana_core::entry::{next_entries, reconstruct_entries_from_blobs, EntrySlice};
|
||||
use solana::entry::reconstruct_entries_from_blobs;
|
||||
use solana::ledger::{next_entries, Block};
|
||||
use solana_sdk::hash::{hash, Hash};
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
@@ -13,7 +15,7 @@ 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 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, one);
|
||||
let tx0 = Transaction::system_move(&keypair, keypair.pubkey(), 1, one, 0);
|
||||
let transactions = vec![tx0; 10];
|
||||
let entries = next_entries(&zero, 1, transactions);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana::signature::GenKeys;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
21
benches/sigverify.rs
Normal file
21
benches/sigverify.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::to_packets;
|
||||
use solana::sigverify;
|
||||
use solana::test_tx::test_tx;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify(bencher: &mut Bencher) {
|
||||
let tx = test_tx();
|
||||
|
||||
// generate packet vector
|
||||
let batches = to_packets(&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,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 +1,25 @@
|
||||
+--------------------+
|
||||
| |
|
||||
+--------+ Neighborhood 0 +----------+
|
||||
| | | |
|
||||
| +--------------------+ |
|
||||
v v
|
||||
+---------+----------+ +----------+---------+
|
||||
| | | |
|
||||
| Neighborhood 1 | | Neighborhood 2 |
|
||||
| | | |
|
||||
+---+-----+----------+ +----------+-----+---+
|
||||
| | | |
|
||||
v v v v
|
||||
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
|
||||
| | | | | | | |
|
||||
| Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
|
||||
| | | | | | | |
|
||||
+--------------------+ +--------------------+ +--------------------+ +--------------------+
|
||||
.-------------.
|
||||
| |
|
||||
.-------------+ Leader +══════════════╗
|
||||
| | | ║
|
||||
| `-------------` ║
|
||||
v v
|
||||
.-------------. .-------------.
|
||||
| +--------------------------->| |
|
||||
.----+ Validator 1 | | Validator 2 +═══╗
|
||||
| | |<═══════════════════════════+ | ║
|
||||
| `------+------` `------+------` ║
|
||||
| | ║ ║
|
||||
| `------------------------------. ║ ║
|
||||
| | ║ ║
|
||||
| ╔════════════════════════════════╝ ║
|
||||
| ║ | ║
|
||||
V v V v
|
||||
.-------------. .-------------. .-------------. .-------------.
|
||||
| | | | | | | |
|
||||
| Validator 3 +------>| Validator 4 +══════>| Validator 5 +------>| Validator 6 |
|
||||
| | | | | | | |
|
||||
`-------------` `-------------` `-------------` `------+------`
|
||||
^ ║
|
||||
║ ║
|
||||
╚═════════════════════════════════════════════════════════════════╝
|
||||
|
@@ -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
|
27
book/art/fullnode.bob
Normal file
27
book/art/fullnode.bob
Normal file
@@ -0,0 +1,27 @@
|
||||
.---------------------------.
|
||||
| Fullnode |
|
||||
| |
|
||||
.--------. | .------------------. |
|
||||
| |---->| | |
|
||||
| Client | | | JSON RPC Service | |
|
||||
| |<----| | |
|
||||
`----+---` | `------------------` |
|
||||
| | ^ | .------------------.
|
||||
| | | .----------------. | | Validators |
|
||||
| | | | Gossip Service +----->| |
|
||||
| | | `--------+-------` | | .------------. |
|
||||
| | | ^ | | | | | |
|
||||
| | | | v | | | Upstream | |
|
||||
| | .--+---. .-+---. | | | Validators | |
|
||||
| | | Bank |<--| TVU |<--------------+ | |
|
||||
| | `------` `-----` | | `------------` |
|
||||
| | ^ | | |
|
||||
| | | | | .------------. |
|
||||
| | .--+--. .-----------. | | | | |
|
||||
`-------->| TPU +-->| Broadcast +--------->| Downstream | |
|
||||
| `-----` | Service | | | | Validators | |
|
||||
| `-----------` | | | | |
|
||||
| | | `------------` |
|
||||
`---------------------------` | |
|
||||
`------------------`
|
||||
|
@@ -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 +1,9 @@
|
||||
.------------. .-----------. .---------------. .--------------. .-----------------------.
|
||||
| PoH verify +---> | sigverify +--->| lock accounts +--->| validate fee +--->| allocate new accounts +--->
|
||||
| TVU | `-----------` `---------------` `--------------` `-----------------------`
|
||||
`------------`
|
||||
|
||||
.---------------. .---------. .------------. .-----------------. .-----------------.
|
||||
--->| load accounts +--->| execute +--->| PoH record +--->| commit accounts +-->| unlock accounts |
|
||||
`---------------` `---------` | TPU | `-----------------` `-----------------`
|
||||
`------------`
|
||||
.-----------. .-------------. .--------------. .--------------------.
|
||||
| sigverify +--->| lock memory +--->| validate fee +--->| allocate accounts +--->
|
||||
`-----------` `-------------` `--------------` `--------------------`
|
||||
|
||||
.------------. .---------. .--------------. .--------------.
|
||||
--->| load data +--->| execute +--->| commit data +-->|unlock memory |
|
||||
`------------` `---------` `--------------` `--------------`
|
||||
|
||||
|
@@ -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,17 +1,16 @@
|
||||
|
||||
.-------------.
|
||||
| PoH Service |
|
||||
`--------+----`
|
||||
^ |
|
||||
.------------------------------|----|--------------------.
|
||||
| TPU | v |
|
||||
| .-------. .-----------. .-+-------. .-----------. | .------------.
|
||||
.---------. | | Fetch | | SigVerify | | Banking | | Broadcast | | | Downstream |
|
||||
| Clients |--->| Stage |->| Stage |->| Stage |->| Stage |---->| Validators |
|
||||
`---------` | | | | | | | | | | | |
|
||||
| `-------` `-----------` `----+----` `-----------` | `------------`
|
||||
| | |
|
||||
`---------------------------------|----------------------`
|
||||
.------------------------------------------------------.
|
||||
| TPU .-------------. |
|
||||
| | PoH Service | |
|
||||
| `--------+----` |
|
||||
| ^ | |
|
||||
| | v |
|
||||
| .-------. .-----------. .-+-------. .--------. | .------------.
|
||||
.---------. | | Fetch | | SigVerify | | Banking | | Ledger | | | Broadcast |
|
||||
| Clients |--->| Stage |->| Stage |->| Stage |-->| Write +---->| Service |
|
||||
`---------` | | | | | | | | Stage | | | |
|
||||
| `-------` `-----------` `----+----` `--------` | `------------`
|
||||
| | |
|
||||
`---------------------------------|--------------------`
|
||||
|
|
||||
v
|
||||
.------.
|
||||
|
@@ -3,17 +3,17 @@
|
||||
`--------`
|
||||
^
|
||||
|
|
||||
.------------------------------------|--------------------.
|
||||
| TVU | |
|
||||
| | |
|
||||
| .-------. .------------. .----+---. .---------. |
|
||||
.------------. | | Blob | | Retransmit | | Replay | | Storage | |
|
||||
| Upstream +----->| Fetch +-->| Stage +-->| Stage +-->| Stage | |
|
||||
| Validators | | | Stage | | | | | | | |
|
||||
`------------` | `-------` `----+-------` `----+---` `---------` |
|
||||
| ^ | | |
|
||||
| | | | |
|
||||
`--------|----------|----------------|--------------------`
|
||||
.------------------------------------|---------------------------------.
|
||||
| TVU | |
|
||||
| | |
|
||||
| .-------. .------------. .----+---. .--------. .---------. |
|
||||
.------------. | | Blob | | Retransmit | | Replay | | Ledger | | Storage | |
|
||||
| Upstream +----->| Fetch |-->| Stage |-->| Stage |-->| Write |-->| Stage | |
|
||||
| Validators | | | Stage | | | | | | Stage | | | |
|
||||
`------------` | `-------` `----+-------` `----+---` `--------` `---------` |
|
||||
| ^ | | |
|
||||
| | | | |
|
||||
`--------|----------|----------------|---------------------------------`
|
||||
| | |
|
||||
| V v
|
||||
.+-----------. .------.
|
||||
|
@@ -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 | | BlobFetch |<------+ Upstream | |
|
||||
| | | Forks | | Stage | | Stage | | | | Validators | |
|
||||
| | `-------` `--------` `--+--------` | | | | |
|
||||
| | ^ ^ | | | `------------` |
|
||||
| | | | v | | |
|
||||
| | | .--+--------. | | |
|
||||
| | | | Blocktree | | | |
|
||||
| | | `-----------` | | .------------. |
|
||||
| | | ^ | | | | |
|
||||
| | | | | | | Downstream | |
|
||||
| | .--+--. .-------+---. | | | Validators | |
|
||||
`-------->| TPU +---->| Broadcast +--------------->| | |
|
||||
| `-----` | Stage | | | `------------` |
|
||||
| `-----------` | `------------------`
|
||||
`--------------------------------------`
|
@@ -3,4 +3,16 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)" test
|
||||
cargo_install_unless() {
|
||||
declare crate=$1
|
||||
shift
|
||||
|
||||
"$@" > /dev/null 2>&1 || \
|
||||
cargo install "$crate"
|
||||
}
|
||||
|
||||
export PATH=$CARGO_HOME/bin:$PATH
|
||||
cargo_install_unless mdbook mdbook --help
|
||||
cargo_install_unless svgbob_cli svgbob --help
|
||||
|
||||
make -j"$(nproc)"
|
||||
|
@@ -1,17 +1,13 @@
|
||||
BOB_SRCS=$(wildcard art/*.bob)
|
||||
MSC_SRCS=$(wildcard art/*.msc)
|
||||
MD_SRCS=$(wildcard src/*.md)
|
||||
|
||||
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
|
||||
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg)
|
||||
|
||||
TARGET=html/index.html
|
||||
TEST_STAMP=src/tests.ok
|
||||
all: html/index.html
|
||||
|
||||
all: $(TARGET)
|
||||
test: src/tests.ok
|
||||
|
||||
test: $(TEST_STAMP)
|
||||
|
||||
open: $(TEST_STAMP)
|
||||
open: all
|
||||
mdbook build --open
|
||||
|
||||
watch: $(SVG_IMGS)
|
||||
@@ -21,19 +17,15 @@ src/img/%.svg: art/%.bob
|
||||
@mkdir -p $(@D)
|
||||
svgbob < $< > $@
|
||||
|
||||
src/img/%.svg: art/%.msc
|
||||
@mkdir -p $(@D)
|
||||
mscgen -T svg -i $< -o $@
|
||||
|
||||
src/%.md: %.md
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
$(TEST_STAMP): $(TARGET)
|
||||
src/tests.ok: $(SVG_IMGS) $(MD_SRCS)
|
||||
mdbook test
|
||||
touch $@
|
||||
|
||||
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
|
||||
html/index.html: src/tests.ok
|
||||
mdbook build
|
||||
|
||||
clean:
|
||||
|
@@ -5,8 +5,7 @@
|
||||
- [Terminology](terminology.md)
|
||||
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Testnet Participation](testnet-participation.md)
|
||||
- [Example Client: Web Wallet](webwallet.md)
|
||||
- [Example: Web Wallet](webwallet.md)
|
||||
|
||||
- [Programming Model](programs.md)
|
||||
- [Example: Tic-Tac-Toe](tictactoe.md)
|
||||
@@ -16,76 +15,23 @@
|
||||
- [Synchronization](synchronization.md)
|
||||
- [Leader Rotation](leader-rotation.md)
|
||||
- [Fork Generation](fork-generation.md)
|
||||
- [Managing Forks](managing-forks.md)
|
||||
- [Turbine Block Propagation](turbine-block-propagation.md)
|
||||
- [Ledger Replication](ledger-replication.md)
|
||||
- [Secure Vote Signing](vote-signing.md)
|
||||
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
|
||||
- [Performance Metrics](performance-metrics.md)
|
||||
|
||||
- [Anatomy of a Validator](validator.md)
|
||||
- [Anatomy of a Fullnode](fullnode.md)
|
||||
- [TPU](tpu.md)
|
||||
- [TVU](tvu.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Gossip Service](gossip.md)
|
||||
- [The Runtime](runtime.md)
|
||||
|
||||
- [Anatomy of a Transaction](transaction.md)
|
||||
- [Proposed Architectural Changes](proposals.md)
|
||||
- [Ledger Replication](ledger-replication.md)
|
||||
- [Secure Enclave](enclave.md)
|
||||
- [Staking Rewards](staking-rewards.md)
|
||||
- [Fork Selection](fork-selection.md)
|
||||
- [Entry Tree](entry-tree.md)
|
||||
|
||||
- [Running a Validator](running-validator.md)
|
||||
- [Hardware Requirements](validator-hardware.md)
|
||||
- [Choosing a Testnet](validator-testnet.md)
|
||||
- [Installing the Validator Software](validator-software.md)
|
||||
- [Starting a Validator](validator-start.md)
|
||||
- [Staking](validator-stake.md)
|
||||
- [Monitoring a Validator](validator-monitor.md)
|
||||
- [Publishing Validator Info](validator-info.md)
|
||||
- [Troubleshooting](validator-troubleshoot.md)
|
||||
- [FAQ](validator-faq.md)
|
||||
## Appendix
|
||||
|
||||
- [Running a Replicator](running-replicator.md)
|
||||
|
||||
- [API Reference](api-reference.md)
|
||||
- [Transaction](transaction-api.md)
|
||||
- [Instruction](instruction-api.md)
|
||||
- [Blockstreamer](blockstreamer.md)
|
||||
- [Appendix](appendix.md)
|
||||
- [JSON RPC API](jsonrpc-api.md)
|
||||
- [JavaScript API](javascript-api.md)
|
||||
- [solana CLI](cli.md)
|
||||
|
||||
- [Accepted Design Proposals](proposals.md)
|
||||
- [Ledger Replication](ledger-replication-to-implement.md)
|
||||
- [Secure Vote Signing](vote-signing-to-implement.md)
|
||||
- [Staking Rewards](staking-rewards.md)
|
||||
- [Cluster Economics](ed_overview.md)
|
||||
- [Validation-client Economics](ed_validation_client_economics.md)
|
||||
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
|
||||
- [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md)
|
||||
- [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md)
|
||||
- [Validation Stake Delegation](ed_vce_validation_stake_delegation.md)
|
||||
- [Replication-client Economics](ed_replication_client_economics.md)
|
||||
- [Storage-replication Rewards](ed_rce_storage_replication_rewards.md)
|
||||
- [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md)
|
||||
- [Economic Sustainability](ed_economic_sustainability.md)
|
||||
- [Attack Vectors](ed_attack_vectors.md)
|
||||
- [Economic Design MVP](ed_mvp.md)
|
||||
- [References](ed_references.md)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Validator](validator-proposal.md)
|
||||
- [Simple Payment and State Verification](simple-payment-and-state-verification.md)
|
||||
- [Cross-Program Invocation](cross-program-invocation.md)
|
||||
|
||||
- [Implemented Design Proposals](implemented-proposals.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Cluster Software Installation and Updates](installer.md)
|
||||
- [Deterministic Transaction Fees](transaction-fees.md)
|
||||
- [Tower BFT](tower-bft.md)
|
||||
- [Leader-to-Leader Transition](leader-leader-transition.md)
|
||||
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
||||
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
|
||||
- [Persistent Account Storage](persistent-account-storage.md)
|
||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||
- [Repair Service](repair-service.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Embedding the Move Langauge](embedding-move.md)
|
||||
- [solana-wallet CLI](wallet.md)
|
||||
|
@@ -1,4 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
The following sections contain API references material you may find useful
|
||||
when developing applications utilizing a Solana cluster.
|
4
book/src/appendix.md
Normal file
4
book/src/appendix.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Appendix
|
||||
|
||||
The following sections contain reference material you may find useful in your
|
||||
Solana journey.
|
@@ -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 fullnode code. The fullnode 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,37 +0,0 @@
|
||||
# Blockstreamer
|
||||
|
||||
Solana supports a node type called an *blockstreamer*. This fullnode 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
|
||||
* `id`, the block id, as base-58 encoded string
|
@@ -1,102 +0,0 @@
|
||||
# Blocktree
|
||||
|
||||
After a block reaches finality, all blocks from that one on down
|
||||
to the genesis block form a linear chain with the familiar name
|
||||
blockchain. Until that point, however, the validator must maintain all
|
||||
potentially valid chains, called *forks*. The process by which forks
|
||||
naturally form as a result of leader rotation is described in
|
||||
[fork generation](fork-generation.md). The *blocktree* data structure
|
||||
described here is how a validator copes with those forks until blocks
|
||||
are finalized.
|
||||
|
||||
The blocktree allows a validator to record every blob it observes
|
||||
on the network, in any order, as long as the blob is signed by the expected
|
||||
leader for a given slot.
|
||||
|
||||
Blobs are moved to a fork-able key space the tuple of `leader slot` + `blob
|
||||
index` (within the slot). This permits the skip-list structure of the Solana
|
||||
protocol to be stored in its entirety, without a-priori choosing which fork to
|
||||
follow, which Entries to persist or when to persist them.
|
||||
|
||||
Repair requests for recent blobs are served out of RAM or recent files and out
|
||||
of deeper storage for less recent blobs, as implemented by the store backing
|
||||
Blocktree.
|
||||
|
||||
### Functionalities of Blocktree
|
||||
|
||||
1. Persistence: the Blocktree lives in the front of the nodes verification
|
||||
pipeline, right behind network receive and signature verification. If the
|
||||
blob received is consistent with the leader schedule (i.e. was signed by the
|
||||
leader for the indicated slot), it is immediately stored.
|
||||
2. Repair: repair is the same as window repair above, but able to serve any
|
||||
blob that's been received. Blocktree stores blobs with signatures,
|
||||
preserving the chain of origination.
|
||||
3. Forks: Blocktree supports random access of blobs, so can support a
|
||||
validator's need to rollback and replay from a Bank checkpoint.
|
||||
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
|
||||
ordered enumeration of entries from slot 0. The logic of the replay stage
|
||||
(i.e. dealing with forks) will have to be used for the most recent entries in
|
||||
the Blocktree.
|
||||
|
||||
### Blocktree Design
|
||||
|
||||
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated
|
||||
slot index and blob index for an entry, and the value is the entry data. Note blob indexes are zero-based for each slot (i.e. they're slot-relative).
|
||||
|
||||
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
|
||||
* `slot_index` - The index of this slot
|
||||
* `num_blocks` - The number of blocks in the slot (used for chaining to a previous slot)
|
||||
* `consumed` - The highest blob index `n`, such that for all `m < n`, there exists a blob in this slot with blob index equal to `n` (i.e. the highest consecutive blob index).
|
||||
* `received` - The highest received blob index for the slot
|
||||
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
|
||||
the ledger to find possible fork points.
|
||||
* `last_index` - The index of the blob that is flagged as the last blob for this slot. This flag on a blob will be set by the leader for a slot when they are transmitting the last blob for a slot.
|
||||
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is_rooted for each slot with the following rules. Let slot(n) be the slot with index `n`, and slot(n).is_full() is true if the slot with index `n` has all the ticks expected for that slot. Let is_rooted(n) be the statement that "the slot(n).is_rooted is true". Then:
|
||||
|
||||
is_rooted(0)
|
||||
is_rooted(n+1) iff (is_rooted(n) and slot(n).is_full()
|
||||
|
||||
3. Chaining - When a blob for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the blob). We then know that this new slot chains to slot `x - num_blocks`.
|
||||
|
||||
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.
|
||||
|
||||
5. Update notifications - The Blocktree notifies listeners when slot(n).is_rooted is flipped from false to true for any `n`.
|
||||
|
||||
### Blocktree APIs
|
||||
|
||||
The Blocktree offers a subscription based API that ReplayStage uses to ask for entries it's interested in. The entries will be sent on a channel exposed by the Blocktree. These subscription API's are as follows:
|
||||
1. `fn get_slots_since(slot_indexes: &[u64]) -> Vec<SlotMeta>`: Returns new slots connecting to any element of the list `slot_indexes`.
|
||||
|
||||
2. `fn get_slot_entries(slot_index: u64, entry_start_index: usize, max_entries: Option<u64>) -> Vec<Entry>`: Returns the entry vector for the slot starting with `entry_start_index`, capping the result at `max` if `max_entries == Some(max)`, otherwise, no upper limit on the length of the return vector is imposed.
|
||||
|
||||
Note: Cumulatively, this means that the replay stage will now have to know when a slot is finished, and subscribe to the next slot it's interested in to get the next set of entries. Previously, the burden of chaining slots fell on the Blocktree.
|
||||
|
||||
### Interfacing with Bank
|
||||
|
||||
The bank exposes to replay stage:
|
||||
|
||||
1. `prev_hash`: which PoH chain it's working on as indicated by the hash of the last
|
||||
entry it processed
|
||||
2. `tick_height`: the ticks in the PoH chain currently being verified by this
|
||||
bank
|
||||
3. `votes`: a stack of records that contain:
|
||||
|
||||
1. `prev_hashes`: what anything after this vote must chain to in PoH
|
||||
2. `tick_height`: the tick height at which this vote was cast
|
||||
3. `lockout period`: how long a chain must be observed to be in the ledger to
|
||||
be able to be chained below this vote
|
||||
|
||||
Replay stage uses Blocktree APIs to find the longest chain of entries it can
|
||||
hang off a previous vote. If that chain of entries does not hang off the
|
||||
latest vote, the replay stage rolls back the bank to that vote and replays the
|
||||
chain from there.
|
||||
|
||||
### Pruning Blocktree
|
||||
|
||||
Once Blocktree entries are old enough, representing all the possible forks
|
||||
becomes less useful, perhaps even problematic for replay upon restart. Once a
|
||||
validator's votes have reached max lockout, however, any Blocktree contents
|
||||
that are not on the PoH chain for that vote for can be pruned, expunged.
|
||||
|
||||
Replicator nodes will be responsible for storing really old ledger contents,
|
||||
and validators need only persist their bank periodically.
|
@@ -1,122 +0,0 @@
|
||||
# Cluster Test Framework
|
||||
|
||||
This document proposes the Cluster Test Framework (CTF). CTF is a test harness
|
||||
that allows tests to execute against a local, in-process cluster or a
|
||||
deployed cluster.
|
||||
|
||||
## Motivation
|
||||
|
||||
The goal of CTF is to provide a framework for writing tests independent of where
|
||||
and how the cluster is deployed. Regressions can be captured in these tests and
|
||||
the tests can be run against deployed clusters to verify the deployment. The
|
||||
focus of these tests should be on cluster stability, consensus, fault tolerance,
|
||||
API stability.
|
||||
|
||||
Tests should verify a single bug or scenario, and should be written with the
|
||||
least amount of internal plumbing exposed to the test.
|
||||
|
||||
## Design Overview
|
||||
|
||||
Tests are provided an entry point, which is a `contact_info::ContactInfo`
|
||||
structure, and a keypair that has already been funded.
|
||||
|
||||
Each node in the cluster is configured with a `fullnode::ValidatorConfig` at boot
|
||||
time. At boot time this configuration specifies any extra cluster configuration
|
||||
required for the test. The cluster should boot with the configuration when it
|
||||
is run in-process or in a data center.
|
||||
|
||||
Once booted, the test will discover the cluster through a gossip entry point and
|
||||
configure any runtime behaviors via fullnode RPC.
|
||||
|
||||
## Test Interface
|
||||
|
||||
Each CTF test starts with an opaque entry point and a funded keypair. The test
|
||||
should not depend on how the cluster is deployed, and should be able to exercise
|
||||
all the cluster functionality through the publicly available interfaces.
|
||||
|
||||
```rust,ignore
|
||||
use crate::contact_info::ContactInfo;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
pub fn test_this_behavior(
|
||||
entry_point_info: &ContactInfo,
|
||||
funding_keypair: &Keypair,
|
||||
num_nodes: usize,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Cluster Discovery
|
||||
|
||||
At test start, the cluster has already been established and is fully connected.
|
||||
The test can discover most of the available nodes over a few second.
|
||||
|
||||
```rust,ignore
|
||||
use crate::gossip_service::discover_nodes;
|
||||
|
||||
// Discover the cluster over a few seconds.
|
||||
let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
|
||||
```
|
||||
|
||||
## Cluster Configuration
|
||||
|
||||
To enable specific scenarios, the cluster needs to be booted with special
|
||||
configurations. These configurations can be captured in
|
||||
`fullnode::ValidatorConfig`.
|
||||
|
||||
For example:
|
||||
|
||||
```rust,ignore
|
||||
let mut validator_config = ValidatorConfig::default();
|
||||
validator_config.rpc_config.enable_fullnode_exit = true;
|
||||
let local = LocalCluster::new_with_config(
|
||||
num_nodes,
|
||||
10_000,
|
||||
100,
|
||||
&validator_config
|
||||
);
|
||||
```
|
||||
|
||||
## How to design a new test
|
||||
|
||||
For example, there is a bug that shows that the cluster fails when it is flooded
|
||||
with invalid advertised gossip nodes. Our gossip library and protocol may
|
||||
change, but the cluster still needs to stay resilient to floods of invalid
|
||||
advertised gossip nodes.
|
||||
|
||||
Configure the RPC service:
|
||||
|
||||
```rust,ignore
|
||||
let mut validator_config = ValidatorConfig::default();
|
||||
validator_config.rpc_config.enable_rpc_gossip_push = true;
|
||||
validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
|
||||
```
|
||||
|
||||
Wire the RPCs and write a new test:
|
||||
|
||||
```rust,ignore
|
||||
pub fn test_large_invalid_gossip_nodes(
|
||||
entry_point_info: &ContactInfo,
|
||||
funding_keypair: &Keypair,
|
||||
num_nodes: usize,
|
||||
) {
|
||||
let cluster = discover_nodes(&entry_point_info, num_nodes);
|
||||
|
||||
// Poison the cluster.
|
||||
let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
for _ in 0..(num_nodes * 100) {
|
||||
client.gossip_push(
|
||||
cluster_info::invalid_contact_info()
|
||||
);
|
||||
}
|
||||
sleep(Durration::from_millis(1000));
|
||||
|
||||
// Force refresh of the active set.
|
||||
for node in &cluster {
|
||||
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
client.gossip_refresh_active_set();
|
||||
}
|
||||
|
||||
// Verify that spends still work.
|
||||
verify_spends(&cluster);
|
||||
}
|
||||
```
|
@@ -28,7 +28,7 @@ its copy.
|
||||
|
||||
## Joining a Cluster
|
||||
|
||||
Validators and replicators enter the cluster via registration messages sent to
|
||||
Fullnodes and replicators 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
|
||||
@@ -96,5 +96,4 @@ 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](data-plane-fanout.md) section.
|
||||
size. We call the technique *data plane fanout*, but it is not yet implemented.
|
||||
|
@@ -1,140 +0,0 @@
|
||||
# Credit-Only Accounts
|
||||
|
||||
This design covers the handling of credit-only and credit-debit accounts in the
|
||||
[runtime](runtime.md). Accounts already distinguish themselves as credit-only or
|
||||
credit-debit based on the program ID specified by the transaction's instruction.
|
||||
Programs must treat accounts that are not owned by them as credit-only.
|
||||
|
||||
To identify credit-only accounts by program id would require the account to be
|
||||
fetched and loaded from disk. This operation is expensive, and while it is
|
||||
occurring, the runtime would have to reject any transactions referencing the same
|
||||
account.
|
||||
|
||||
The proposal introduces a `num_readonly_accounts` field to the transaction
|
||||
structure, and removes the `program_ids` dedicated vector for program accounts.
|
||||
|
||||
This design doesn't change the runtime transaction processing rules.
|
||||
Programs still can't write or spend accounts that they do not own, but it
|
||||
allows the runtime to optimistically take the correct lock for each account
|
||||
specified in the transaction before loading the accounts from storage.
|
||||
|
||||
Accounts selected as credit-debit by the transaction can still be treated as
|
||||
credit-only by the instructions.
|
||||
|
||||
## Runtime handling
|
||||
|
||||
credit-only accounts have the following properties:
|
||||
|
||||
* Can be deposited into: Deposits can be implemented as a simple `atomic_add`.
|
||||
* read-only access to account data.
|
||||
|
||||
Instructions that debit or modify the credit-only account data will fail.
|
||||
|
||||
## Account Lock Optimizations
|
||||
|
||||
The Accounts module keeps track of current locked accounts in the runtime,
|
||||
which separates credit-only accounts from the credit-debit accounts. The credit-only
|
||||
accounts can be cached in memory and shared between all the threads executing
|
||||
transactions.
|
||||
|
||||
The current runtime can't predict whether an account is credit-only or credit-debit when
|
||||
the transaction account keys are locked at the start of the transaction
|
||||
processing pipeline. Accounts referenced by the transaction have not been
|
||||
loaded from the disk yet.
|
||||
|
||||
An ideal design would cache the credit-only accounts while they are referenced by
|
||||
any transaction moving through the runtime, and release the cache when the last
|
||||
transaction exits the runtime.
|
||||
|
||||
## Credit-only accounts and read-only account data
|
||||
|
||||
Credit-only account data can be treated as read-only. Credit-debit
|
||||
account data is treated as read-write.
|
||||
|
||||
## Transaction changes
|
||||
|
||||
To enable the possibility of caching accounts only while they are in the
|
||||
runtime, the Transaction structure should be changed in the following way:
|
||||
|
||||
* `program_ids: Vec<Pubkey>` - This vector is removed. Program keys can be
|
||||
placed at the end of the `account_keys` vector within the `num_readonly_accounts`
|
||||
number set to the number of programs.
|
||||
|
||||
* `num_readonly_accounts: u8` - The number of keys from the **end** of the
|
||||
transaction's `account_keys` array that is credit-only.
|
||||
|
||||
The following possible accounts are present in an transaction:
|
||||
|
||||
* paying account
|
||||
* RW accounts
|
||||
* R accounts
|
||||
* Program IDs
|
||||
|
||||
The paying account must be credit-debit, and program IDs must be credit-only. The
|
||||
first account in the `account_keys` array is always the account that pays for
|
||||
the transaction fee, therefore it cannot be credit-only. For these reasons the
|
||||
credit-only accounts are all grouped together at the end of the `account_keys`
|
||||
vector. Counting credit-only accounts from the end allow for the default `0`
|
||||
value to still be functionally correct, since a transaction will succeed with
|
||||
all credit-debit accounts.
|
||||
|
||||
Since accounts can only appear once in the transaction's `account_keys` array,
|
||||
an account can only be credit-only or credit-debit in a single transaction, not
|
||||
both. The runtime treats a transaction as one atomic unit of execution. If any
|
||||
instruction needs credit-debit access to an account, a copy needs to be made. The
|
||||
write lock is held for the entire time the transaction is being processed by
|
||||
the runtime.
|
||||
|
||||
## Starvation
|
||||
|
||||
Read locks for credit-only accounts can keep the runtime from executing
|
||||
transactions requesting a write lock to a credit-debit account.
|
||||
|
||||
When a request for a write lock is made while a read lock is open, the
|
||||
transaction requesting the write lock should be cached. Upon closing the read
|
||||
lock, the pending transactions can be pushed through the runtime.
|
||||
|
||||
While a pending write transaction exists, any additional read lock requests for
|
||||
that account should fail. It follows that any other write lock requests will also
|
||||
fail. Currently, clients must retransmit when a transaction fails because of
|
||||
a pending transaction. This approach would mimic that behavior as closely as
|
||||
possible while preventing write starvation.
|
||||
|
||||
## Program execution with credit-only accounts
|
||||
|
||||
Before handing off the accounts to program execution, the runtime can mark each
|
||||
account in each instruction as a credit-only account. The credit-only accounts can
|
||||
be passed as references without an extra copy. The transaction will abort on a
|
||||
write to credit-only.
|
||||
|
||||
An alternative is to detect writes to credit-only accounts and fail the
|
||||
transactions before commit.
|
||||
|
||||
## Alternative design
|
||||
|
||||
This design attempts to cache a credit-only account after loading without the use
|
||||
of a transaction-specified credit-only accounts list. Instead, the credit-only
|
||||
accounts are held in a reference-counted table inside the runtime as the
|
||||
transactions are processed.
|
||||
|
||||
1. Transaction accounts are locked.
|
||||
a. If the account is present in the ‘credit-only' table, the TX does not fail.
|
||||
The pending state for this TX is marked NeedReadLock.
|
||||
2. Transaction accounts are loaded.
|
||||
a. Transaction accounts that are credit-only increase their reference
|
||||
count in the `credit-only` table.
|
||||
b. Transaction accounts that need a write lock and are present in the
|
||||
`credit-only` table fail.
|
||||
3. Transaction accounts are unlocked.
|
||||
a. Decrement the `credit-only` lock table reference count; remove if its 0
|
||||
b. Remove from the `lock` set if the account is not in the `credit-only`
|
||||
table.
|
||||
|
||||
The downside with this approach is that if the `lock` set mutex is released
|
||||
between lock and load to allow better pipelining of transactions, a request for
|
||||
a credit-only account may fail. Therefore, this approach is not suitable for
|
||||
treating programs as credit-only accounts.
|
||||
|
||||
Holding the accounts lock mutex while fetching the account from disk would
|
||||
potentially have a significant performance hit on the runtime. Fetching from
|
||||
disk is expected to be slow, but can be parallelized between multiple disks.
|
@@ -1,111 +0,0 @@
|
||||
# Cross-Program Invocation
|
||||
|
||||
## Problem
|
||||
|
||||
In today's implementation a client can create a transaction that modifies two
|
||||
accounts, each owned by a separate on-chain program:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
token_instruction::pay(&alice_pubkey),
|
||||
acme_instruction::launch_missiles(&bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
The current implementation does not, however, allow the `acme` program to
|
||||
conveniently invoke `token` instructions on the client's behalf:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
Currently, there is no way to create instruction `pay_and_launch_missiles` that executes
|
||||
`token_instruction::pay` from the `acme` program. The workaround is to extend the
|
||||
`acme` program with the implementation of the `token` program, and create `token`
|
||||
accounts with `ACME_PROGRAM_ID`, which the `acme` program is permitted to modify.
|
||||
With that workaround, `acme` can modify token-like accounts created by the `acme`
|
||||
program, but not token accounts created by the `token` program.
|
||||
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
The goal of this design is to modify Solana's runtime such that an on-chain
|
||||
program can invoke an instruction from another program.
|
||||
|
||||
Given two on-chain programs `token` and `acme`, each implementing instructions
|
||||
`pay()` and `launch_missiles()` respectively, we would ideally like to implement
|
||||
the `acme` module with a call to a function defined in the `token` module:
|
||||
|
||||
```rust,ignore
|
||||
use token;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
token::pay(&keyed_accounts[1..])?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
The above code would require that the `token` crate be dynamically linked,
|
||||
so that a custom linker could intercept calls and validate accesses to
|
||||
`keyed_accounts`. That is, even though the client intends to modify both
|
||||
`token` and `acme` accounts, only `token` program is permitted to modify
|
||||
the `token` account, and only the `acme` program is permitted to modify
|
||||
the `acme` account.
|
||||
|
||||
Backing off from that ideal cross-program call, a slightly more
|
||||
verbose solution is to expose token's existing `process_instruction()`
|
||||
entrypoint to the acme program:
|
||||
|
||||
```rust,ignore
|
||||
use token_instruction;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
let alice_pubkey = keyed_accounts[1].key;
|
||||
let instruction = token_instruction::pay(&alice_pubkey);
|
||||
process_instruction(&instruction)?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
where `process_instruction()` is built into Solana's runtime and responsible
|
||||
for routing the given instruction to the `token` program via the instruction's
|
||||
`program_id` field. Before invoking `pay()`, the runtime must also ensure that
|
||||
`acme` didn't modify any accounts owned by `token`. It does this by calling
|
||||
`runtime::verify_instruction()` and then afterward updating all the `pre_*`
|
||||
variables to tentatively commit `acme`'s account modifications. After `pay()`
|
||||
completes, the runtime must again ensure that `token` didn't modify any
|
||||
accounts owned by `acme`. It should call `verify_instruction()` again, but this
|
||||
time with the `token` program ID. Lastly, after `pay_and_launch_missiles()`
|
||||
completes, the runtime must call `verify_instruction()` one more time, where it
|
||||
normally would, but using all updated `pre_*` variables. If executing
|
||||
`pay_and_launch_missiles()` up to `pay()` made no invalid account changes,
|
||||
`pay()` made no invalid changes, and executing from `pay()` until
|
||||
`pay_and_launch_missiles()` returns made no invalid changes, then the runtime
|
||||
can transitively assume `pay_and_launch_missiles()` as whole made no invalid
|
||||
account changes, and therefore commit all account modifications.
|
||||
|
||||
### Setting `KeyedAccount.is_signer`
|
||||
|
||||
When `process_instruction()` is invoked, the runtime must create a new
|
||||
`KeyedAccounts` parameter using the signatures from the *original* transaction
|
||||
data. Since the `token` program is immutable and existed on-chain prior to the
|
||||
`acme` program, the runtime can safely treat the transaction signature as a
|
||||
signature of a transaction with a `token` instruction. When the runtime sees
|
||||
the given instruction references `alice_pubkey`, it looks up the key in the
|
||||
transaction to see if that key corresponds to a transaction signature. In this
|
||||
case it does and so sets `KeyedAccount.is_signer`, thereby authorizing the
|
||||
`token` program to modify Alice's account.
|
@@ -10,7 +10,7 @@ client's account.
|
||||
A drone is a simple signing service. It listens for requests to sign
|
||||
*transaction data*. Once received, the drone validates the request however it
|
||||
sees fit. It may, for example, only accept transaction data with a
|
||||
`SystemInstruction::Transfer` instruction transferring only up to a certain amount
|
||||
`SystemInstruction::Move` instruction transferring only up to a certain amount
|
||||
of tokens. If the drone accepts the transaction, it returns an `Ok(Signature)`
|
||||
where `Signature` is a signature of the transaction data using the drone's
|
||||
private key. If it rejects the transaction data, it returns a `DroneError`
|
||||
@@ -46,7 +46,7 @@ desired cluster.
|
||||
|
||||
## Attack vectors
|
||||
|
||||
### Invalid recent_blockhash
|
||||
### Invalid last_id
|
||||
|
||||
The drone may prefer its airdrops only target a particular Solana cluster. To
|
||||
do that, it listens to the cluster for new entry IDs and ensure any requests
|
||||
@@ -68,15 +68,15 @@ A client may request multiple airdrops before the first has been submitted to
|
||||
the ledger. The client may do this maliciously or simply because it thinks the
|
||||
first request was dropped. The drone should not simply query the cluster to
|
||||
ensure the client has not already received an airdrop. Instead, it should use
|
||||
`recent_blockhash` to ensure the previous request is expired before signing another.
|
||||
Note that the Solana cluster will reject any transaction with a `recent_blockhash`
|
||||
`last_id` to ensure the previous request is expired before signing another.
|
||||
Note that the Solana cluster will reject any transaction with a `last_id`
|
||||
beyond a certain *age*.
|
||||
|
||||
### Denial of Service
|
||||
|
||||
If the transaction data size is smaller than the size of the returned signature
|
||||
(or descriptive error), a single client can flood the network. Considering
|
||||
that a simple `Transfer` operation requires two public keys (each 32 bytes) and a
|
||||
that a simple `Move` operation requires two public keys (each 32 bytes) and a
|
||||
`fee` field, and that the returned signature is 64 bytes (and a byte to
|
||||
indicate `Ok`), consideration for this attack may not be required.
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
## Attack Vectors
|
||||
|
||||
### Colluding validation and replication clients
|
||||
|
||||
A colluding validation-client, may take the strategy to mark PoReps from non-colluding replicator nodes as invalid as an attempt to maximize the rewards for the colluding replicator nodes. In this case, it isn’t feasible for the offended-against replicator nodes to petition the network for resolution as this would result in a network-wide vote on each offending PoRep and create too much overhead for the network to progress adequately. Also, this mitigation attempt would still be vulnerable to a >= 51% staked colluder.
|
||||
|
||||
Alternatively, transaction fees from submitted PoReps are pooled and distributed across validation-clients in proportion to the number of valid PoReps discounted by the number of invalid PoReps as voted by each validator-client. Thus invalid votes are directly dis-incentivized through this reward channel. Invalid votes that are revealed by replicator nodes as fishing PoReps, will not be discounted from the payout PoRep count.
|
||||
|
||||
Another collusion attack involves a validator-client who may take the strategy to ignore invalid PoReps from colluding replicator and vote them as valid. In this case, colluding replicator-clients would not have to store the data while still receiving rewards for validated PoReps. Additionally, colluding validator nodes would also receive rewards for validating these PoReps. To mitigate this attack, validators must randomly sample PoReps corresponding to the ledger block they are validating and because of this, there will be multiple validators that will receive the colluding replicator’s invalid submissions. These non-colluding validators will be incentivized to mark these PoReps as invalid as they have no way to determine whether the proposed invalid PoRep is actually a fishing PoRep, for which a confirmation vote would result in the validator’s stake being slashed.
|
||||
|
||||
In this case, the proportion of time a colluding pair will be successful has an upper limit determined by the % of stake of the network claimed by the colluding validator. This also sets bounds to the value of such an attack. For example, if a colluding validator controls 10% of the total validator stake, transaction fees will be lost (likely sent to mining pool) by the colluding replicator 90% of the time and so the attack vector is only profitable if the per-PoRep reward at least 90% higher than the average PoRep transaction fee. While, probabilistically, some colluding replicator-client PoReps will find their way to colluding validation-clients, the network can also monitor rates of paired (validator + replicator) discrepancies in voting patterns and censor identified colluders in these cases.
|
@@ -1,18 +0,0 @@
|
||||
## Economic Sustainability
|
||||
|
||||
Long term economic sustainability is one of the guiding principles of Solana’s economic design. While it is impossible to predict how decentralized economies will develop over time, especially economies with flexible decentralized governances, we can arrange economic components such that, under certain conditions, a sustainable economy may take shape in the long term. In the case of Solana’s network, these components take the form of the remittances and deposits into and out of the reserve ‘mining pool’.
|
||||
|
||||
The dominant remittances from the Solana mining pool are validator and replicator rewards. The deposit mechanism is a flat, protocol-specified and adjusted, % of each transaction fee.
|
||||
|
||||
The Replicator rewards are to be delivered to replicators from the mining pool after successful PoRep validation. The per-PoRep reward amount is determined as a function of the total network storage redundancy at the time of the PoRep validation and the network goal redundancy. This function is likely to take the form of a discount from a base reward to be delivered when the network has achieved and maintained its goal redundancy. An example of such a reward function is shown in **Figure 3**
|
||||
|
||||
<!--  -->
|
||||
<p style="text-align:center;"><img src="img/porep_reward.png" alt="==PoRep Reward Curve ==" width="800"/></p>
|
||||
|
||||
**Figure 3**: Example PoRep reward design as a function of global network storage redundancy.
|
||||
|
||||
In the example shown in Figure 1, multiple per PoRep base rewards are explored (as a % of Tx Fee) to be delivered when the global ledger replication redundancy meets 10X. When the global ledger replication redundancy is less than 10X, the base reward is discounted as a function of the square of the ratio of the actual ledger replication redundancy to the goal redundancy (i.e. 10X).
|
||||
|
||||
The other protocol-based remittance goes to validation-clients as a reward distributed in proportion to stake-weight for voting to validate the ledger state. The functional issuance of this reward is described in [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md) and is designed to reduce over time until validators are incentivized solely through collection of transaction fees. Therefore, in the long-run, protocol-based rewards to replication-nodes will be the only remittances from the mining pool, and will have to be countered by the portion of each non-PoRep transaction fee that is directed back into the mining pool. I.e. for a long-term self-sustaining economy, replicator-client rewards must be subsidized through a minimum fee on each non-PoRep transaction pre-allocated to the mining pool. Through this constraint, we can write the following inequality:
|
||||
|
||||
**== WIP [here](https://docs.google.com/document/d/1HBDasdkjS4Ja9wC_tIUsZPVcxGAWTuYOq9zf6xoQNps/edit?usp=sharing) ==**
|
@@ -1,12 +0,0 @@
|
||||
## Proposed MVP of Economic Design
|
||||
|
||||
The preceeding sections, outlined in the [Economic Design Overview](ed_overview.md), describe a long-term vision of a sustainable Solana economy. Of course, we don't expect the final implementation to perfectly match what has been described above. We intend to fully engage with network stakeholders throughout the implementation phases (i.e. pre-testnet, testnet, mainnet) to ensure the system supports, and is representative of, the various network participants' interests. The first step toward this goal, however, is outlining a some desired MVP economic features to be available for early pre-testnet and testnet participants. Below is a rough sketch outlining basic economic functionality from which a more complete and functional system can be developed.
|
||||
|
||||
### MVP Economic Features
|
||||
|
||||
* Faucet to deliver testnet SOLs to validators for staking and dapp development.
|
||||
* Mechanism by which validators are rewarded in proportion to their stake. Interest rate mechansism (i.e. to be determined by total % staked) to come later.
|
||||
* Ability to delegate tokens to validator nodes.
|
||||
* Replicators to receive fixed, arbitrary reward for submitting validated PoReps. Reward size mechanism (i.e. PoRep reward as a function of total ledger redundancy) to come later.
|
||||
* Pooling of replicator PoRep transaction fees and weighted distribution to validators based on PoRep verification (see [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). It will be useful to test this protection against attacks on testnet.
|
||||
* Nice-to-have: auto-delegation of replicator rewards to validator.
|
@@ -1,16 +0,0 @@
|
||||
## Economic Design Overview
|
||||
|
||||
Solana’s crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients and replication-clients. Their contributions to the network, state validation and data storage respectively, and their requisite remittance mechanisms are discussed below.
|
||||
|
||||
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are protocol-derived issuances from a protocol-defined, global inflation rate. These rewards will constitute the total reward delivered to replication clients and a portion of the total rewards for validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to join the network.
|
||||
|
||||
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be a result of a global supply inflation rate, calculated per Solana epoch and distributed amongst the active validator set. As discussed further below, the per annum inflation rate is based on a pre-determined disinflationary schedule. This provides the network with monetary supply predictability which supports long term economic stability and security.
|
||||
|
||||
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction (be it a state execution or proof-of-replication verification). A mechanism for continuous and long-term economic stability through partial burning of each transaction fee is also discussed below.
|
||||
|
||||
A high-level schematic of Solana’s crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics.md), [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). Also, the chapter titled [Validation Stake Delegation](ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rend_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. The [Replication-client Economics](ed_replication_client_economics.md) chapter will review the Solana network design for global ledger storage/redundancy and replicator-client economics ([Storage-replication rewards](ed_rce_storage_replication_rewards.md)) along with a replicator-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md). The [Economic Sustainability](ed_economic_sustainability.md) section dives deeper into Solana’s design for long-term economic sustainability and outlines the constraints and conditions for a self-sustaining economy. An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in chapter [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
|
||||
|
||||
<!--  -->
|
||||
<p style="text-align:center;"><img src="img/economic_design_infl_230719.png" alt="== Solana Economic Design Diagram ==" width="800"/></p>
|
||||
|
||||
**Figure 1**: Schematic overview of Solana economic incentive design.
|
@@ -1,5 +0,0 @@
|
||||
### Replication-client Reward Auto-delegation
|
||||
|
||||
The ability for Solana network participant’s to earn rewards by providing storage service is a unique on-boarding path that requires little hardware overhead and minimal upfront capital. It offers an avenue for individuals with extra-storage space on their home laptops or PCs to contribute to the security of the network and become integrated into the Solana economy.
|
||||
|
||||
To enhance this on-boarding ramp and facilitate further participation and investment in the Solana economy, replication-clients have the opportunity to auto-delegate their rewards to validation-clients of their choice. Much like the automatic reinvestment of stock dividends, in this scenario, a replicator-client can earn Solana tokens by providing some storage capacity to the network (i.e. via submitting valid PoReps), have the protocol-based rewards automatically assigned as delegation to a staked validator node and therefore earning interest in the validation-client reward pool.
|
@@ -1,5 +0,0 @@
|
||||
### Storage-replication Rewards
|
||||
|
||||
Replicator-clients download, encrypt and submit PoReps for ledger block sections.3 PoReps submitted to the PoH stream, and subsequently validated, function as evidence that the submitting replicator client is indeed storing the assigned ledger block sections on local hard drive space as a service to the network. Therefore, replicator clients should earn protocol rewards proportional to the amount of storage, and the number of successfully validated PoReps, that they are verifiably providing to the network.
|
||||
|
||||
Additionally, replicator clients have the opportunity to capture a portion of slashed bounties [TBD] of dishonest validator clients. This can be accomplished by a replicator client submitting a verifiably false PoRep for which a dishonest validator client receives and signs as a valid PoRep. This reward incentive is to prevent lazy validators and minimize validator-replicator collusion attacks, more on this below.
|
@@ -1,7 +0,0 @@
|
||||
## References
|
||||
|
||||
1. [https://blog.ethereum.org/2016/07/27/inflation-transaction-fees-cryptocurrency-monetary-policy/](https://blog.ethereum.org/2016/07/27/inflation-transaction-fees-cryptocurrency-monetary-policy/)
|
||||
|
||||
2. [https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281](https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281)
|
||||
|
||||
3. [https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281](https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281)
|
@@ -1,3 +0,0 @@
|
||||
## Replication-client economics
|
||||
|
||||
Replication-clients should be rewarded for providing the network with storage space. Incentivization of the set of replicators provides data security through redundancy of the historical ledger. Replication nodes are rewarded in proportion to the amount of ledger data storage provided. These rewards are captured by generating and entering Proofs of Replication (PoReps) into the PoH stream which can be validated by Validation nodes as described above in the [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md) chapter.
|
@@ -1,3 +0,0 @@
|
||||
## Validation-client Economics
|
||||
|
||||
Validator-clients are eligible to receive protocol-based (i.e. via inflation) rewards issued via stake-based annual interest rates (calculated per epoch) by providing compute (CPU+GPU) resources to validate and vote on a given PoH state. These protocol-based rewards are determined through an algorithmic disinflationary schedule as a function of total amount of circulating tokens. Additionally, these clients may earn revenue through fees via state-validation transactions and Proof-of-Replication (PoRep) transactions. For clarity, we separately describe the design and motivation of these revenue distriubutions for validation-clients below: state-validation protocol-based rewards, state-validation transaction fees and rent, and PoRep-validation transaction fees.
|
@@ -1,9 +0,0 @@
|
||||
### Replication-validation Transaction Fees
|
||||
|
||||
As previously mentioned, validator-clients will also be responsible for validating PoReps submitted into the PoH stream by replicator-clients. In this case, validators are providing compute (CPU/GPU) and light storage resources to confirm that these replication proofs could only be generated by a client that is storing the referenced PoH leger block.2
|
||||
|
||||
While replication-clients are incentivized and rewarded through protocol-based rewards schedule (see [Replication-client Economics](ed_replication_client_economics.md)), validator-clients will be incentivized to include and validate PoReps in PoH through collection of transaction fees associated with the submitted PoReps and distribution of protocol rewards proportional to the validated PoReps. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy (e.g. 10x data redundancy).
|
||||
|
||||
The validation of PoReps by validation-clients is computationally more expensive than state-validation (detail in the [Economic Sustainability](ed_economic_sustainability.md) chapter), thus the transaction fees are expected to be proportionally higher.
|
||||
|
||||
There are various attack vectors available for colluding validation and replication clients, as described in detail below in [Economic Sustainability](ed_economic_sustainability). To protect against various collusion attack vectors, for a given epoch, validator rewards are distributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of PoReps that mismatch the replicators challenge. The PoRep challenge game is described in [Ledger Replication](https://github.com/solana-labs/solana/blob/master/book/src/ledger-replication.md#the-porep-game). This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps (note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid).
|
@@ -1,40 +0,0 @@
|
||||
### State-validation protocol-based rewards
|
||||
|
||||
Validator-clients have two functional roles in the Solana network:
|
||||
|
||||
* Validate (vote) the current global state of that PoH along with any Proofs-of-Replication (see [Replication Client Economics](ed_replication_client_economics.md)) that they are eligible to validate.
|
||||
|
||||
* Be elected as ‘leader’ on a stake-weighted round-robin schedule during which time they are responsible for collecting outstanding transactions and Proofs-of-Replication and incorporating them into the PoH, thus updating the global state of the network and providing chain continuity.
|
||||
|
||||
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. Compensation for validator-clients is provided via a protocol-based annual inflation rate dispersed in proportion to the stake-weight of each validator (see below) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each transaction fee, less a protocol-specified amount that is destroyed (see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)). PoRep transaction fees are also collected by the leader client and validator PoRep rewards are distributed in proportion to the number of validated PoReps less the number of PoReps that mismatch a replicator's challenge. (see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md))
|
||||
|
||||
|
||||
The effective protocol-based annual interest rate (%) per epoch to be distributed to validation-clients is to be a function of:
|
||||
|
||||
* the current global inflation rate, derived from the pre-determined dis-inflationary issuance schedule
|
||||
|
||||
* the fraction of staked SOLs out of the current total circulating supply,
|
||||
|
||||
* the up-time/participation [% of available slots that validator had opportunity to vote on] of a given validator over the previous epoch.
|
||||
|
||||
The first factor is a function of protocol parameters only (i.e. independent of validator behavior in a given epoch) and results in a global validation reward schedule designed to incentivize early participation, provide clear montetary stability and provide optimal security in the network.
|
||||
|
||||
At any given point in time, a specific validator's interest rate can be determined based on the porportion of circulating supply that is staked by the network and the validator's uptime/activity in the previous epoch. For an illustrative example, consider a hypothetical instance of the network with an initial circulating token supply of 250MM tokens with an additional 250MM vesting over 3 years. Additionally an inflation rate is specified at network launch of 7.5%, and a disinflationary schedule of 20% decrease in inflation rate per year (the actual rates to be implemented are to be worked out during the testnet experimentation phase of mainnet launch). With these broad assumptions, the 10-year inflation rate (adjusted daily for this example) is shown in **Figure 2**, while the total circulating token supply is illustrated in **Figure 3**. Neglected in this toy-model is the inflation supression due to the portion of each transaction fee that is to be destroyed.
|
||||
|
||||
<p style="text-align:center;"><img src="img/p_ex_schedule.png" alt="drawing" width="800"/></p>
|
||||
**Figure 2:** In this example schedule, the annual inflation rate [%] reduces at around 20% per year, until it reaches the long-term, fixed, 1.5% rate.
|
||||
|
||||
<p style="text-align:center;"><img src="img/p_ex_supply.png" alt="drawing" width="800"/></p>
|
||||
**Figure 3:** The total token supply over a 10-year period, based on an initial 250MM tokens with the disinflationary inflation schedule as shown in **Figure 2**
|
||||
|
||||
Over time, the interest rate, at a fixed network staked percentage, will reduce concordant with network inflation. Validation-client interest rates are designed to be higher in the early days of the network to incentivize participation and jumpstart the network economy. As previously mentioned, the inflation rate is expected to stabalize near 1-2% which also results in a fixed, long-term, interest rate to be provided to validator-clients. This value does not represent the total interest available to validator-clients as transaction fees for both state-validation and ledger storage replication (PoReps) are not accounted for here.
|
||||
|
||||
Given these example parameters, annualized validator-specific interest rates can be determined based on the global fraction of tokens bonded as stake, as well as their uptime/activity in the previous epoch. For the purpose of this example, we assume 100% uptime for all validators and a split in interest-based rewards between validators and replicator nodes of 80%/20%. Additionally, the fraction of staked circulating supply is assummed to be constant. Based on these assumptions, an annualized validation-client interest rate schedule as a function of % circulating token supply that is staked is shown in** Figure 4**.
|
||||
|
||||
<!--  -->
|
||||
|
||||
<p style="text-align:center;"><img src="img/p_ex_interest.png" alt="drawing" width="800"/></p>
|
||||
|
||||
**Figure 4:** Shown here are example validator interest rates over time, neglecting transaction fees, segmented by fraction of total circulating supply bonded as stake.
|
||||
|
||||
This epoch-specific protocol-defined interest rate sets an upper limit of *protocol-generated* annual interest rate (not absolute total interest rate) possible to be delivered to any validator-client per epoch. The distributed interest rate per epoch is then discounted from this value based on the participation of the validator-client during the previous epoch.
|
@@ -1,20 +0,0 @@
|
||||
### State-validation Transaction Fees
|
||||
|
||||
Each transaction sent through the network, to be processed by the current leader validation-client and confirmed as a global state transaction, must contain a transaction fee. Transaction fees offer many benefits in the Solana economic design, for example they:
|
||||
|
||||
* provide unit compensation to the validator network for the CPU/GPU resources necessary to process the state transaction,
|
||||
|
||||
* reduce network spam by introducing real cost to transactions,
|
||||
|
||||
* open avenues for a transaction market to incentivize validation-client to collect and process submitted transactions in their function as leader,
|
||||
|
||||
* and provide potential long-term economic stability of the network through a protocol-captured minimum fee amount per transaction, as described below.
|
||||
|
||||
Many current blockchain economies (e.g. Bitcoin, Ethereum), rely on protocol-based rewards to support the economy in the short term, with the assumption that the revenue generated through transaction fees will support the economy in the long term, when the protocol derived rewards expire. In an attempt to create a sustainable economy through protocol-based rewards and transaction fees, a fixed portion of each transaction fee is destroyed, with the remaining fee going to the current leader processing the transaction. A scheduled global inflation rate provides a source for rewards distributed to validation-clients, through the process described above, and replication-clients, as discussed below.
|
||||
|
||||
Transaction fees are set by the network cluster based on recent historical throughput, see [Congestion Driven Fees](transaction-fees.md#congestion-driven-fees). This minimum portion of each transaction fee can be dynamically adjusted depending on historical gas usage. In this way, the protocol can use the minimum fee to target a desired hardware utilisation. By monitoring a protocol specified gas usage with respect to a desired, target usage amount, the minimum fee can be raised/lowered which should, in turn, lower/raise the actual gas usage per block until it reaches the target amount. This adjustment process can be thought of as similar to the difficulty adjustment algorithm in the Bitcoin protocol, however in this case it is adjusting the minimum transaction fee to guide the transaction processing hardware usage to a desired level.
|
||||
|
||||
As mentioned, a fixed-proportion of each transaction fee is to be destroyed. The intent of this design is to retain leader incentive to include as many transactions as possible within the leader-slot time, while providing an inflation limiting mechansim that protects against "tax evasion" attacks (i.e. side-channel fee payments)<sup>[1](ed_referenced.md)</sup>.
|
||||
|
||||
Additionally, the burnt fees can be a consideration in fork selection. In the case of a PoH fork with a malicious, censoring leader, we would expect the total fees destroyed to be less than a comparable honest fork, due to the fees lost from censoring. If the censoring leader is to compensate for these lost protocol fees, they would have to replace the burnt fees on their fork themselves, thus potentially reducing the incentive to censor in the first place.
|
||||
|
@@ -1,29 +0,0 @@
|
||||
### Validation Stake Delegation
|
||||
|
||||
Running a Solana validation-client required relatively modest upfront hardware capital investment. **Table 2** provides an example hardware configuration to support ~1M tx/s with estimated ‘off-the-shelf’ costs:
|
||||
|
||||
|Component|Example|Estimated Cost|
|
||||
|--- |--- |--- |
|
||||
|GPU|2x 2080 Ti|$2500|
|
||||
|or|4x 1080 Ti|$2800|
|
||||
|OS/Ledger Storage|Samsung 860 Evo 2TB|$370|
|
||||
|Accounts storage|2x Samsung 970 Pro M.2 512GB|$340|
|
||||
|RAM|32 Gb|$300|
|
||||
|Motherboard|AMD x399|$400|
|
||||
|CPU|AMD Threadripper 2920x|$650|
|
||||
|Case||$100|
|
||||
|Power supply|EVGA 1600W|$300|
|
||||
|Network|> 500 mbps||
|
||||
|Network (1)|Google webpass business bay area 1gbps unlimited|$5500/mo|
|
||||
|Network (2)|Hurricane Electric bay area colo 1gbps|$500/mo|
|
||||
**Table 2** example high-end hardware setup for running a Solana client.
|
||||
|
||||
Despite the low-barrier to entry as a validation-client, from a capital investment perspective, as in any developing economy, there will be much opportunity and need for trusted validation services as evidenced by node reliability, UX/UI, APIs and other software accessibility tools. Additionally, although Solana’s validator node startup costs are nominal when compared to similar networks, they may still be somewhat restrictive for some potential participants. In the spirit of developing a true decentralized, permissionless network, these interested parties still have two options to become involved in the Solana network/economy:
|
||||
|
||||
1. Delegation of previously acquired tokens with a reliable validation node to earn a portion of interest generated
|
||||
|
||||
2. Provide local storage space as a replication-client and receive rewards by submitting Proof-of-Replication (see [Replication-client Economics](ed_replication_client_economics.md)).
|
||||
|
||||
a. This participant has the additional option to directly delegate their earned storage rewards ([Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md))
|
||||
|
||||
Delegation of tokens to validation-clients, via option 1, provides a way for passive Solana token holders to become part of the active Solana economy and earn interest rates proportional to the interest rate generated by the delegated validation-client. Additionally, this feature creates a healthy validation-client market, with potential validation-client nodes competing to build reliable, transparent and profitable delegation services.
|
@@ -1,66 +0,0 @@
|
||||
# Embedding the Move Language
|
||||
|
||||
## Problem
|
||||
|
||||
Solana enables developers to write on-chain programs in general purpose
|
||||
programming languages such as C or Rust, but those programs contain
|
||||
Solana-specific mechanisms. For example, there isn't another chain that asks
|
||||
developers to create a Rust module with a `process_instruction(KeyedAccounts)`
|
||||
function. Whenever practical, Solana should offer dApp developers more portable
|
||||
options.
|
||||
|
||||
Until just recently, no popular blockchain offered a language that could expose
|
||||
the value of Solana's massively parallel [runtime](runtime.md). Solidity
|
||||
contracts, for example, do not separate references to shared data from contract
|
||||
code, and therefore need to be executed serially to ensure deterministic
|
||||
behavior. In practice we see that the most aggressively optimized EVM-based
|
||||
blockchains all seem to peak out around 1,200 TPS - a small fraction of what
|
||||
Solana can do. The Libra project, on the other hand, designed an on-chain
|
||||
programming language called Move that is more suitable for parallel execution.
|
||||
Like Solana's runtime, Move programs depend on accounts for all shared state.
|
||||
|
||||
The biggest design difference between Solana's runtime and Libra's Move VM is
|
||||
how they manage safe invocations between modules. Solana took an operating
|
||||
systems approach and Libra took the domain-specific language approach. In the
|
||||
runtime, a module must trap back into the runtime to ensure the caller's module
|
||||
did not write to data owned by the callee. Likewise, when the callee completes,
|
||||
it must again trap back to the runtime to ensure the callee did not write to
|
||||
data owned by the caller. Move, on the other hand, includes an advanced type
|
||||
system that allows these checks to be run by its bytecode verifier. Because
|
||||
Move bytecode can be verified, the cost of verification is paid just once, at
|
||||
the time the module is loaded on-chain. In the runtime, the cost is paid each
|
||||
time a transaction crosses between modules. The difference is similar in spirit
|
||||
to the difference between a dynamically-typed language like Python versus a
|
||||
statically-typed language like Java. Solana's runtime allows dApps to be
|
||||
written in general purpose programming languages, but that comes with the cost
|
||||
of runtime checks when jumping between programs.
|
||||
|
||||
This proposal attempts to define a way to embed the Move VM such that:
|
||||
|
||||
* cross-module invocations within Move do not require the runtime's
|
||||
cross-program runtime checks
|
||||
* Move programs can leverage functionality in other Solana programs and vice
|
||||
versa
|
||||
* Solana's runtime parallelism is exposed to batches of Move and non-Move
|
||||
transactions
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Move VM as a Solana loader
|
||||
|
||||
The Move VM shall be embedded as a Solana loader under the identifier
|
||||
`MOVE_PROGRAM_ID`, so that Move modules can be marked as `executable` with the
|
||||
VM as its `owner`. This will allow modules to load module dependencies, as well
|
||||
as allow for parallel execution of Move scripts.
|
||||
|
||||
All data accounts owned by Move modules must set their owners to the loader,
|
||||
`MOVE_PROGRAM_ID`. Since Move modules encapsulate their account data in the
|
||||
same way Solana programs encapsulate theirs, the Move module owner should be
|
||||
embedded in the account data. The runtime will grant write access to the Move
|
||||
VM, and Move grants access to the module accounts.
|
||||
|
||||
### Interacting with Solana programs
|
||||
|
||||
To invoke instructions in non-Move programs, Solana would need to extend the
|
||||
Move VM with a `process_instruction()` system call. It would work the same as
|
||||
`process_instruction()` Rust BPF programs.
|
@@ -1,17 +1,18 @@
|
||||
# Secure Vote Signing
|
||||
# Signing using Secure Enclave
|
||||
|
||||
This design describes additional vote signing behavior that will make the
|
||||
process more secure.
|
||||
This document defines the security mechanism of signing keys used by the
|
||||
fullnodes. Every node contains an asymmetric key that's used for signing
|
||||
and verifying the votes. The node signs the vote transactions using its private
|
||||
key. Other entities can verify the signature using the node's public key.
|
||||
|
||||
Currently, Solana implements a vote-signing service that evaluates each vote to
|
||||
ensure it does not violate a slashing condition. The service could potentially
|
||||
have different variations, depending on the hardware platform capabilities. In
|
||||
particular, it could be used in conjunction with a secure enclave (such as SGX).
|
||||
The enclave could generate an asymmetric key, exposing an API for user
|
||||
(untrusted) code to sign the vote transactions, while keeping the vote-signing
|
||||
private key in its protected memory.
|
||||
The node's stake or its resources could be compromised if its private key is
|
||||
used to sign incorrect data (e.g. voting on multiple forks of the ledger). So,
|
||||
it's important to safeguard the private key.
|
||||
|
||||
The following sections outline how this architecture would work:
|
||||
Secure Enclaves (such as SGX) provide a layer of memory and computation
|
||||
protection. An enclave can be used to generate an asymmetric key and keep the
|
||||
private key in its protected memory. It can expose an API that user (untrusted)
|
||||
code can use for signing the transactions.
|
||||
|
||||
## Message Flow
|
||||
|
||||
@@ -25,13 +26,13 @@ The following sections outline how this architecture would work:
|
||||
2. The node performs attestation of the enclave (e.g using Intel's IAS APIs)
|
||||
* The node ensures that the Secure Enclave is running on a TPM and is
|
||||
signed by a trusted party
|
||||
3. The stakeholder of the node grants ephemeral key permission to use its stake.
|
||||
This process is TBD.
|
||||
3. The owner of the node grants ephemeral key permission to use its stake. This
|
||||
process is TBD.
|
||||
4. The node's untrusted, non-enclave software calls trusted enclave software
|
||||
using its interface to sign transactions and other data.
|
||||
* In case of vote signing, the node needs to verify the PoH. The PoH
|
||||
verification is an integral part of signing. The enclave would be
|
||||
presented with some verifiable data to check before signing the vote.
|
||||
presented with some verifiable data that it'll check before signing the vote.
|
||||
* The process of generating the verifiable data in untrusted space is TBD
|
||||
|
||||
## PoH Verification
|
||||
@@ -100,8 +101,81 @@ confirming that it has observed some probability of economic finality of the
|
||||
submitted fork at a depth where an additional vote would create a lockout for
|
||||
an undesirable amount of time if that fork turns out not to be live.
|
||||
|
||||
## Signing service
|
||||
|
||||
The signing service consists of a a JSON RPC server, and a request processor.
|
||||
At startup, it starts the RPC server at a configured port and waits for
|
||||
client/validator requests. It expects the following type of requests.
|
||||
1. Register a new validator node
|
||||
* The request contains validator's identity (public key)
|
||||
* The request is signed with validator's private key
|
||||
* The service will drop the request if signature of the request cannot be
|
||||
verified
|
||||
* The service will create a new voting asymmetric key for the validator,
|
||||
and return the public key as a response
|
||||
* If a validator retries to register, it'll return the public key from the
|
||||
pre-existing keypair
|
||||
2. Sign a vote
|
||||
* The request contains voting transaction, and all verification data (as
|
||||
described in Ancestor Verification)
|
||||
* The request is signed with validator's private key
|
||||
* The service will drop the request if signature of the request cannot be
|
||||
verified
|
||||
* The service will verify the voting data
|
||||
* The service will return a signed transaction (or signature for the
|
||||
transaction)
|
||||
|
||||
The service could potentially have different variations, depending on the
|
||||
hardware platform capabilities. For example, if the hardware supports a secure
|
||||
enclave, the service can offload asymmetric key generation, and private key
|
||||
protection to the enclave. A less secure implementation of the service could
|
||||
simply carry the keypair in the process memory.
|
||||
|
||||
## Validator voting
|
||||
|
||||
A validator node, at startup, creates a new vote account and registers it with
|
||||
the cluster. This is done by submitting a new "vote register" transaction. The
|
||||
transaction contains validator's keypair, it's vote signing public key, and
|
||||
some additional information. 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 on a voting event.
|
||||
This vote is signed with validator's voting private key.
|
||||
|
||||
The validator code will change to interface with Signing service for "vote
|
||||
register" and "new vote" use cases.
|
||||
|
||||
### Configuration
|
||||
|
||||
The validator node will be configured with Signing service's network endpoint
|
||||
(IP/Port).
|
||||
|
||||
### Register
|
||||
|
||||
At startup, the validator will call Signing service using JSON RPC to register
|
||||
itself. The RPC call will return the voting public key for the validator node.
|
||||
The validator will create a new "vote register" transaction including this
|
||||
public key in it, and submit it to the cluster.
|
||||
|
||||
### Collect votes for last period
|
||||
|
||||
The validator will look up the votes submitted by all the nodes in the cluster
|
||||
for the last voting period. This information will be submitted to signing
|
||||
service with new vote signing request.
|
||||
|
||||
### New Vote Signing
|
||||
|
||||
The validator will create a "new vote" transaction and send it to the signing
|
||||
service using JSON RPC. The RPC request will also include the vote verification
|
||||
data. On success, RPC call will return the signature for the vote. On failure,
|
||||
RPC call will return the failure code.
|
||||
|
||||
## Challenges
|
||||
|
||||
1. Generation of verifiable data in untrusted space for PoH verification in the
|
||||
1. The nodes are currently being configured with asymmetric keys that are
|
||||
generated and stored in PKCS8 files.
|
||||
2. The genesis block contains an entry that's signed with leader's private key.
|
||||
This entry is used to identify the primordial leader.
|
||||
3. Generation of verifiable data in untrusted space for PoH verification in the
|
||||
enclave.
|
||||
2. Need infrastructure for granting stake to an ephemeral key.
|
||||
4. Need infrastructure for granting stake to an ephemeral key.
|
121
book/src/entry-tree.md
Normal file
121
book/src/entry-tree.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Entry Tree
|
||||
|
||||
This document proposes a change to ledger and window to support Solana's [fork
|
||||
generation](fork-generation.md) behavior.
|
||||
|
||||
## Current Design
|
||||
|
||||
### Functionality of Window And Ledger
|
||||
|
||||
The basic responsibilities of the window and the ledger in a Solana fullnode
|
||||
are:
|
||||
|
||||
1. Window: serve as a temporary, RAM-backed store of blobs of the PoH chain
|
||||
for re-ordering and assembly into contiguous blocks to be sent to the bank
|
||||
for verification.
|
||||
2. Window: serve as a RAM-backed repair facility for other validator nodes,
|
||||
which may query the network for as-yet unreceived blobs.
|
||||
3. Ledger: provide disk-based storage of the PoH chain in case of node
|
||||
restart.
|
||||
4. Ledger: provide disk-backed repair facility for when the (smaller)
|
||||
RAM-backed window doesn't cover the repair request.
|
||||
|
||||
The window is at the front of a validator node's processing pipeline, blobs are
|
||||
received, cached, re-ordered before being deserialized into Entries, passed to
|
||||
the bank for verification, and finally on to the ledger, which is at the back
|
||||
of a validator node's pipeline.
|
||||
|
||||
The window holds blobs (the over-the-air format, serialized Entries,
|
||||
one-per-blob). The ledger holds serialized Entries without any blob
|
||||
information.
|
||||
|
||||
### Limitations
|
||||
|
||||
#### One-dimensional key space
|
||||
|
||||
The window and the ledger are indexed by ledger height, which is number of
|
||||
Entries ever generated in the PoH chain until the current blob. This
|
||||
limitation prevents the window and the ledger from storing the overlapping
|
||||
histories possible in Solana's consensus protocol.
|
||||
|
||||
#### Limited caching
|
||||
|
||||
The window is a circular buffer. It cannot accept blobs that are farther in
|
||||
the future than the window is currently working. If a blob arrives that is too
|
||||
far ahead, it is dropped and will subsequently need to be repaired, incurring
|
||||
further delay for the node.
|
||||
|
||||
#### Loss of blob signatures
|
||||
|
||||
Because the blob signatures are stripped before being stored by the ledger,
|
||||
repair requests served from the ledger can't be verified to the original
|
||||
leader.
|
||||
|
||||
#### Rollback and checkpoint, switching forks, separate functions
|
||||
|
||||
The window and the ledger can't handle replay of alternate forks. Once a Blob
|
||||
has passed through the window, it's in the past. The replay stage of a
|
||||
validator will need to roll back to a previous checkpoint and decode an
|
||||
alternate set of Blobs to the Bank. The separated and one-way nature of window
|
||||
and ledger makes this hard.
|
||||
|
||||
## New Design
|
||||
|
||||
A unified window and ledger allows a validator to record every blob it observes
|
||||
on the network, in any order, as long as the blob is consistent with the
|
||||
network's leader schedule.
|
||||
|
||||
Blobs are moved to a fork-able key space the tuple of `leader slot` + `blob
|
||||
index` (within the slot). This permits the skip-list structure of the Solana
|
||||
protocol to be stored in its entirety, without a-priori choosing which fork to
|
||||
follow, which Entries to persist or when to persist them.
|
||||
|
||||
Repair requests for recent blobs are served out of RAM or recent files and out
|
||||
of deeper storage for less recent blobs, as implemented by the store backing
|
||||
EntryTree.
|
||||
|
||||
### Functionalities of EntryTree
|
||||
|
||||
1. Persistence: the EntryTree lives in the front of the nodes verification
|
||||
pipeline, right behind network receive and signature verification. If the
|
||||
blob received is consistent with the leader schedule (i.e. was signed by the
|
||||
leader for the indicated slot), it is immediately stored.
|
||||
2. Repair: repair is the same as window repair above, but able to serve any
|
||||
blob that's been received. EntryTree stores blobs with signatures,
|
||||
preserving the chain of origination.
|
||||
3. Forks: EntryTree supports random access of blobs, so can support a
|
||||
validator's need to rollback and replay from a Bank checkpoint.
|
||||
4. Restart: with proper pruning/culling, the EntryTree can be replayed by
|
||||
ordered enumeration of entries from slot 0. The logic of the replay stage
|
||||
(i.e. dealing with forks) will have to be used for the most recent entries in
|
||||
the EntryTree.
|
||||
|
||||
### Interfacing with Bank
|
||||
|
||||
The bank exposes to replay stage:
|
||||
|
||||
1. prev_id: which PoH chain it's working on as indicated by the id of the last
|
||||
entry it processed
|
||||
2. tick_height: the ticks in the PoH chain currently being verified by this
|
||||
bank
|
||||
3. votes: a stack of records that contain
|
||||
|
||||
1. prev_ids: what anything after this vote must chain to in PoH
|
||||
2. tick height: the tick_height at which this vote was cast
|
||||
3. lockout period: how long a chain must be observed to be in the ledger to
|
||||
be able to be chained below this vote
|
||||
|
||||
Replay stage uses EntryTree APIs to find the longest chain of entries it can
|
||||
hang off a previous vote. If that chain of entries does not hang off the
|
||||
latest vote, the replay stage rolls back the bank to that vote and replays the
|
||||
chain from there.
|
||||
|
||||
### Pruning EntryTree
|
||||
|
||||
Once EntryTree entries are old enough, representing all the possible forks
|
||||
becomes less useful, perhaps even problematic for replay upon restart. Once a
|
||||
validator's votes have reached max lockout, however, any EntryTree contents
|
||||
that are not on the PoH chain for that vote for can be pruned, expunged.
|
||||
|
||||
Replicator nodes will be responsible for storing really old ledger contents,
|
||||
and validators need only persist their bank periodically.
|
@@ -55,15 +55,13 @@ 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](tower-bft.md).
|
||||
[forks selection](fork-selection.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
|
||||
#### Time Progression The diagram below represents a validator's view of the
|
||||
PoH stream with possible forks over time. L1, L2, etc. are leader slot, 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.
|
||||
|
||||
|
||||
|
155
book/src/fork-selection.md
Normal file
155
book/src/fork-selection.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Fork Selection
|
||||
This article describes Solana's *Nakomoto Fork Selection* algorithm based on time
|
||||
locks. It satisfies the following properties:
|
||||
|
||||
|
||||
* A voter can eventually recover from voting on a fork that doesn't become the
|
||||
fork with the desired network finality.
|
||||
* If the voters share a common ancestor then they will converge to a fork
|
||||
containing that ancestor no matter how they are partitioned. The converged
|
||||
ancestor may not be the latest possible ancestor at the start of the fork.
|
||||
* Rollback requires exponentially more time for older votes than for newer
|
||||
votes.
|
||||
* Voters have the freedom to set a minimum network confirmation threshold
|
||||
before committing a vote to a higher lockout. This allows each voter to make
|
||||
a trade-off between risk and reward. See [cost of rollback](#cost-of-rollback).
|
||||
|
||||
## Time
|
||||
|
||||
For networks like Solana, time can be the PoH hash count, which is a VDF that
|
||||
provides a source of time before consensus. Other networks adopting this
|
||||
approach would need to consider a global source of time.
|
||||
|
||||
For Solana, time uniquely identifies a specific leader for fork generation. At
|
||||
any given time only 1 leader, which can be computed from the ledger itself, can
|
||||
propose a fork. For more details, see [fork generation](fork-generation.md)
|
||||
and [leader rotation](leader-rotation.md).
|
||||
|
||||
## Algorithm
|
||||
|
||||
The basic idea to this approach is to stack consensus votes. Each vote in the
|
||||
stack is a confirmation of a fork. Each confirmed fork is an ancestor of the
|
||||
fork above it. Each consensus vote has a `lockout` in units of time before the
|
||||
validator can submit a vote that does not contain the confirmed fork as an
|
||||
ancestor.
|
||||
|
||||
When a vote is added to the stack, the lockouts of all the previous votes in
|
||||
the stack are doubled (more on this in [Rollback](#Rollback)). With each new
|
||||
vote, a voter commits the previous votes to an ever-increasing lockout. At 32
|
||||
votes we can consider the vote to be at `max lockout` any votes with a lockout
|
||||
equal to or above `1<<32` are dequeued (FIFO). Dequeuing a vote is the trigger
|
||||
for a reward. If a vote expires before it is dequeued, it and all the votes
|
||||
above it are popped (LIFO) from the vote stack. The voter needs to start
|
||||
rebuilding the stack from that point.
|
||||
|
||||
|
||||
### Rollback
|
||||
|
||||
Before a vote is pushed to the stack, all the votes leading up to vote with a
|
||||
lower lock time than the new vote are popped. After rollback lockouts are not
|
||||
doubled until the voter catches up to the rollback height of votes.
|
||||
|
||||
For example, a vote stack with the following state:
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
|-----:|----------:|--------:|---------------------:|
|
||||
| 4 | 4 | 2 | 6 |
|
||||
| 3 | 3 | 4 | 7 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
*Vote 5* is at time 9, and the resulting state is
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
|-----:|----------:|--------:|---------------------:|
|
||||
| 5 | 9 | 2 | 11 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
*Vote 6* is at time 10
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
|-----:|----------:|--------:|---------------------:|
|
||||
| 6 | 10 | 2 | 12 |
|
||||
| 5 | 9 | 4 | 13 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
At time 10 the new votes caught up to the previous votes. But *vote 2* expires
|
||||
at 10, so the when *vote 7* at time 11 is applied the votes including and above
|
||||
*vote 2* will be popped.
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
|-----:|----------:|--------:|---------------------:|
|
||||
| 7 | 11 | 2 | 13 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
The lockout for vote 1 will not increase from 16 until the stack contains 5
|
||||
votes.
|
||||
|
||||
### Slashing and Rewards
|
||||
|
||||
The purpose of the lockout is to force a voter to commit opportunity cost to a
|
||||
specific fork. Voters that violate the lockouts and vote for a diverging fork
|
||||
within the lockout should be punished. Slashing or simply freezing the voter
|
||||
from rewards for a long period of time can be used as punishment.
|
||||
|
||||
Voters should be rewarded for selecting the fork that the rest of the network
|
||||
selected as often as possible. This is well-aligned with generating a reward
|
||||
when the vote stack is full and the oldest vote needs to be dequeued. Thus a
|
||||
reward should be generated for each successful dequeue.
|
||||
|
||||
### Cost of Rollback
|
||||
|
||||
Cost of rollback of *fork A* is defined as the cost in terms of lockout time to
|
||||
the validators to confirm any other fork that does not include *fork A* as an
|
||||
ancestor.
|
||||
|
||||
The **Economic Finality** of *fork A* can be calculated as the loss of all the
|
||||
rewards from rollback of *fork A* and its descendants, plus the opportunity
|
||||
cost of reward due to the exponentially growing lockout of the votes that have
|
||||
confirmed *fork A*.
|
||||
|
||||
### Thresholds
|
||||
|
||||
Each voter can independently set a threshold of network commitment to a fork
|
||||
before that voter commits to a fork. For example, at vote stack index 7, the
|
||||
lockout is 256 time units. A voter may withhold votes and let votes 0-7 expire
|
||||
unless the vote at index 7 has at greater than 50% commitment in the network.
|
||||
This allows each voter to independently control how much risk to commit to a
|
||||
fork. Committing to forks at a higher frequency would allow the voter to earn
|
||||
more rewards.
|
||||
|
||||
### Algorithm parameters
|
||||
|
||||
These parameters need to be tuned.
|
||||
|
||||
* Number of votes in the stack before dequeue occurs (32).
|
||||
* Rate of growth for lockouts in the stack (2x).
|
||||
* Starting default lockout (2).
|
||||
* Threshold depth for minimum network commitment before committing to the fork
|
||||
(8).
|
||||
* Minimum network commitment size at threshold depth (50%+).
|
||||
|
||||
### Free Choice
|
||||
|
||||
A "Free Choice" is an unenforcible voter action. A voter that maximizes
|
||||
self-reward over all possible futures should behave in such a way that the
|
||||
system is stable, and the local greedy choice should result in a greedy choice
|
||||
over all possible futures. A set of voter that are engaging in choices to
|
||||
disrupt the protocol should be bound by their stake weight to the denial of
|
||||
service. Two options exits for voter:
|
||||
|
||||
* a voter can outrun previous voters in virtual generation and submit a
|
||||
concurrent fork
|
||||
* a voter can withhold a vote to observe multiple forks before voting
|
||||
|
||||
In both cases, the voters in the network have several forks to pick from
|
||||
concurrently, even though each fork represents a different height. In both
|
||||
cases it is impossible for the protocol to detect if the voter behavior is
|
||||
intentional or not.
|
||||
|
||||
### Greedy Choice for Concurrent Forks
|
||||
|
||||
When evaluating multiple forks, each voter should pick the fork that will
|
||||
maximize economic finality for the network, or the latest fork if all are equal.
|
@@ -1,10 +1,10 @@
|
||||
# Anatomy of a Validator
|
||||
# Anatomy of a Fullnode
|
||||
|
||||
<img alt="Validator block diagrams" src="img/validator.svg" class="center"/>
|
||||
<img alt="Fullnode block diagrams" src="img/fullnode.svg" class="center"/>
|
||||
|
||||
## Pipelining
|
||||
|
||||
The validators make extensive use of an optimization common in CPU design,
|
||||
The fullnodes make extensive use of an optimization common in CPU design,
|
||||
called *pipelining*. Pipelining is the right tool for the job when there's a
|
||||
stream of input data that needs to be processed by a sequence of steps, and
|
||||
there's different hardware responsible for each. The quintessential example is
|
||||
@@ -19,9 +19,9 @@ dryer and the first is being folded. In this way, one can make progress on
|
||||
three loads of laundry simultaneously. Given infinite loads, the pipeline will
|
||||
consistently complete a load at the rate of the slowest stage in the pipeline.
|
||||
|
||||
## Pipelining in the Validator
|
||||
## Pipelining in the Fullnode
|
||||
|
||||
The validator contains two pipelined processes, one used in leader mode called
|
||||
The fullnode contains two pipelined processes, one used in leader mode called
|
||||
the TPU and one used in validator mode called the TVU. In both cases, the
|
||||
hardware being pipelined is the same, the network input, the GPU cards, the CPU
|
||||
cores, writes to disk, and the network output. What it does with that hardware
|
@@ -41,14 +41,8 @@ $ git checkout $TAG
|
||||
|
||||
### Configuration Setup
|
||||
|
||||
Ensure important programs such as the vote program are built before any
|
||||
nodes are started
|
||||
```bash
|
||||
$ cargo build --all
|
||||
```
|
||||
|
||||
The network is initialized with a genesis ledger generated by running the
|
||||
following script.
|
||||
The network is initialized with a genesis ledger and fullnode configuration files.
|
||||
These files can be generated by running the following script.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/setup.sh
|
||||
@@ -69,7 +63,7 @@ $ ./multinode-demo/drone.sh
|
||||
|
||||
### Singlenode Testnet
|
||||
|
||||
Before you start a validator, make sure you know the IP address of the machine you
|
||||
Before you start a fullnode, 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.
|
||||
|
||||
@@ -86,10 +80,10 @@ 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
|
||||
additional validators in separate shells:
|
||||
additional full nodes in separate shells:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator-x.sh
|
||||
$ ./multinode-demo/fullnode-x.sh
|
||||
```
|
||||
|
||||
To run a performance-enhanced full node on Linux,
|
||||
@@ -99,7 +93,7 @@ your system:
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/fullnode-x.sh
|
||||
```
|
||||
|
||||
### Testnet Client Demo
|
||||
@@ -145,7 +139,7 @@ Generally we are using `debug` for infrequent debug messages, `trace` for potent
|
||||
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_:
|
||||
_solana-fullnode_:
|
||||
|
||||
```bash
|
||||
$ sudo gdb
|
||||
@@ -161,8 +155,100 @@ This will dump all the threads stack traces into gdb.txt
|
||||
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 --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
|
||||
$ ./multinode-demo/client.sh --network $(dig +short testnet.solana.com):8001 --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 for evaluation. Note that CUDA is not supported by the Snap so
|
||||
performance will be limited.
|
||||
|
||||
The `edge` Snap channel is updated daily with the latest
|
||||
development from the `master` branch. To install:
|
||||
|
||||
```bash
|
||||
$ sudo snap install solana --edge --devmode
|
||||
```
|
||||
|
||||
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 fullnodes and a drone as system daemons.
|
||||
|
||||
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/bootstrap-leader/`,
|
||||
`/var/snap/solana/current/fullnode/`, 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=bootstrap-leader
|
||||
```
|
||||
|
||||
`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
|
||||
```ini
|
||||
[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=bootstrap-leader+drone
|
||||
|
||||
```
|
||||
|
||||
#### Validator daemon
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=fullnode
|
||||
|
||||
```
|
||||
|
||||
By default the node will attempt to connect to **testnet.solana.com**, override the
|
||||
cluster entrypoint IP address by running:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=fullnode entrypoint-ip=127.0.0.1 #<-- change IP address
|
||||
```
|
||||
|
||||
It's assumed that the node at the entrypoint IP will be running `rsync`
|
||||
configured as described in the previous **Leader daemon** section.
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Gossip Service
|
||||
|
||||
The Gossip Service acts as a gateway to nodes in the control plane. Validators
|
||||
The Gossip Service acts as a gateway to nodes in the control plane. Fullnodes
|
||||
use the service to ensure information is available to all other nodes in a cluster.
|
||||
The service broadcasts information using a gossip protocol.
|
||||
|
||||
@@ -22,7 +22,7 @@ gossip endpoint (a socket address).
|
||||
|
||||
Records shared over gossip are arbitrary, but signed and versioned (with a
|
||||
timestamp) as needed to make sense to the node receiving them. If a node
|
||||
receives two records from the same source, it updates its own copy with the
|
||||
recieves two records from the same source, it it updates its own copy with the
|
||||
record with the most recent timestamp.
|
||||
|
||||
## Gossip Service Interface
|
||||
@@ -34,8 +34,8 @@ Nodes send push messages to `PUSH_FANOUT` push peers.
|
||||
|
||||
Upon receiving a push message, a node examines the message for:
|
||||
|
||||
1. Duplication: if the message has been seen before, the node drops the message
|
||||
and may respond with `PushMessagePrune` if forwarded from a low staked node
|
||||
1. Duplication: if the message has been seen before, the node responds with
|
||||
`PushMessagePrune` and drops the message
|
||||
|
||||
2. New data: if the message is new to the node
|
||||
* Stores the new information with an updated version in its cluster info and
|
||||
@@ -51,7 +51,7 @@ Upon receiving a push message, a node examines the message for:
|
||||
A nodes selects its push peers at random from the active set of known peers.
|
||||
The node keeps this selection for a relatively long time. When a prune message
|
||||
is received, the node drops the push peer that sent the prune. Prune is an
|
||||
indication that there is another, higher stake weighted path to that node than direct push.
|
||||
indication that there is another, faster path to that node than direct push.
|
||||
|
||||
The set of push peers is kept fresh by rotating a new node into the set every
|
||||
`PUSH_MSG_TIMEOUT/2` milliseconds.
|
||||
@@ -77,52 +77,3 @@ Nodes retain prior versions of values (those updated by a pull or push) and
|
||||
expired values (those older than `GOSSIP_PULL_CRDS_TIMEOUT_MS`) in
|
||||
`purged_values` (things I recently had). Nodes purge `purged_values` that are
|
||||
older than `5 * GOSSIP_PULL_CRDS_TIMEOUT_MS`.
|
||||
|
||||
## Eclipse Attacks
|
||||
|
||||
An eclipse attack is an attempt to take over the set of node connections with
|
||||
adversarial endpoints.
|
||||
|
||||
This is relevant to our implementation in the following ways.
|
||||
|
||||
* Pull messages select a random node from the network. An eclipse attack on
|
||||
*pull* would require an attacker to influence the random selection in such a way
|
||||
that only adversarial nodes are selected for pull.
|
||||
|
||||
* Push messages maintain an active set of nodes and select a random fanout for
|
||||
every push message. An eclipse attack on *push* would influence the active set
|
||||
selection, or the random fanout selection.
|
||||
|
||||
### Time and Stake based weights
|
||||
|
||||
Weights are calculated based on `time since last picked` and the `natural log` of the `stake weight`.
|
||||
|
||||
Taking the `ln` of the stake weight allows giving all nodes a fairer chance of network
|
||||
coverage in a reasonable amount of time. It helps normalize the large possible `stake weight` differences between nodes.
|
||||
This way a node with low `stake weight`, compared to a node with large `stake weight` will only have to wait a
|
||||
few multiples of ln(`stake`) seconds before it gets picked.
|
||||
|
||||
There is no way for an adversary to influence these parameters.
|
||||
|
||||
### Pull Message
|
||||
|
||||
A node is selected as a pull target based on the weights described above.
|
||||
|
||||
### Push Message
|
||||
|
||||
A prune message can only remove an adversary from a potential connection.
|
||||
|
||||
Just like *pull message*, nodes are selected into the active set based on weights.
|
||||
|
||||
## Notable differences from PlumTree
|
||||
|
||||
The active push protocol described here is based on [Plum
|
||||
Tree](https://haslab.uminho.pt/jop/files/lpr07a.pdf). The main differences are:
|
||||
|
||||
* Push messages have a wallclock that is signed by the originator. Once the
|
||||
wallclock expires the message is dropped. A hop limit is difficult to implement
|
||||
in an adversarial setting.
|
||||
|
||||
* Lazy Push is not implemented because its not obvious how to prevent an
|
||||
adversary from forging the message fingerprint. A naive approach would allow an
|
||||
adversary to be prioritized for pull based on their input.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 64 KiB |
Binary file not shown.
Before Width: | Height: | Size: 256 KiB |
Binary file not shown.
Before Width: | Height: | Size: 269 KiB |
Binary file not shown.
Before Width: | Height: | Size: 372 KiB |
Binary file not shown.
Before Width: | Height: | Size: 401 KiB |
@@ -1,3 +0,0 @@
|
||||
# Implemented Design Proposals
|
||||
|
||||
The following design proposals are fully implemented.
|
@@ -1,213 +0,0 @@
|
||||
## Cluster Software Installation and Updates
|
||||
Currently users are required to build the solana cluster software themselves
|
||||
from the git repository and manually update it, which is error prone and
|
||||
inconvenient.
|
||||
|
||||
This document proposes an easy to use software install and updater that can be
|
||||
used to deploy pre-built binaries for supported platforms. Users may elect to
|
||||
use binaries supplied by Solana or any other party they trust. Deployment of
|
||||
updates is managed using an on-chain update manifest program.
|
||||
|
||||
### Motivating Examples
|
||||
#### Fetch and run a pre-built installer using a bootstrap curl/shell script
|
||||
The easiest install method for supported platforms:
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh
|
||||
```
|
||||
|
||||
This script will check github for the latest tagged release and download and run the
|
||||
`solana-install-init` binary from there.
|
||||
|
||||
|
||||
If additional arguments need to be specified during the installation, the
|
||||
following shell syntax is used:
|
||||
```bash
|
||||
$ init_args=.... # arguments for `solana-install-init ...`
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - ${init_args}
|
||||
```
|
||||
|
||||
#### Fetch and run a pre-built installer from a Github release
|
||||
With a well-known release URL, a pre-built binary can be obtained for supported
|
||||
platforms:
|
||||
|
||||
```bash
|
||||
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.18.0/solana-install-init-x86_64-apple-darwin
|
||||
$ chmod +x ./solana-install-init
|
||||
$ ./solana-install-init --help
|
||||
```
|
||||
|
||||
#### Build and run the installer from source
|
||||
If a pre-built binary is not available for a given platform, building the
|
||||
installer from source is always an option:
|
||||
```bash
|
||||
$ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana/install
|
||||
$ cargo run -- --help
|
||||
```
|
||||
|
||||
#### Deploy a new update to a cluster
|
||||
Given a solana release tarball (as created by `ci/publish-tarball.sh`) that has already been uploaded to a publicly accessible URL,
|
||||
the following commands will deploy the update:
|
||||
```bash
|
||||
$ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users
|
||||
$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json
|
||||
```
|
||||
|
||||
#### Run a validator node that auto updates itself
|
||||
```bash
|
||||
$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
|
||||
$ export PATH=~/.local/share/solana-install/bin:$PATH
|
||||
$ solana-keygen ... # <-- runs the latest solana-keygen
|
||||
$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necesary when an update is applied
|
||||
```
|
||||
|
||||
### On-chain Update Manifest
|
||||
An update manifest is used to advertise the deployment of new release tarballs
|
||||
on a solana cluster. The update manifest is stored using the `config` program,
|
||||
and each update manifest account describes a logical update channel for a given
|
||||
target triple (eg, `x86_64-apple-darwin`). The account public key is well-known
|
||||
between the entity deploying new updates and users consuming those updates.
|
||||
|
||||
The update tarball itself is hosted elsewhere, off-chain and can be fetched from
|
||||
the specified `download_url`.
|
||||
|
||||
```rust,ignore
|
||||
use solana_sdk::signature::Signature;
|
||||
|
||||
/// Information required to download and apply a given update
|
||||
pub struct UpdateManifest {
|
||||
pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH
|
||||
pub download_url: String, // Download URL to the release tar.bz2
|
||||
pub download_sha256: String, // SHA256 digest of the release tar.bz2 file
|
||||
}
|
||||
|
||||
/// Userdata of an Update Manifest program Account.
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||
pub struct SignedUpdateManifest {
|
||||
pub manifest: UpdateManifest,
|
||||
pub manifest_signature: Signature,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Note that the `manifest` field itself contains a corresponding signature
|
||||
(`manifest_signature`) to guard against man-in-the-middle attacks between the
|
||||
`solana-install` tool and the solana cluster RPC API.
|
||||
|
||||
To guard against rollback attacks, `solana-install` will refuse to install an
|
||||
update with an older `timestamp_secs` than what is currently installed.
|
||||
|
||||
### Release Archive Contents
|
||||
A release archive is expected to be a tar file compressed with
|
||||
bzip2 with the following internal structure:
|
||||
|
||||
* `/version.yml` - a simple YAML file containing the field `"target"` - the
|
||||
target tuple. Any additional fields are ignored.
|
||||
* `/bin/` -- directory containing available programs in the release.
|
||||
`solana-install` will symlink this directory to
|
||||
`~/.local/share/solana-install/bin` for use by the `PATH` environment
|
||||
variable.
|
||||
* `...` -- any additional files and directories are permitted
|
||||
|
||||
### solana-install Tool
|
||||
The `solana-install` tool is used by the user to install and update their cluster software.
|
||||
|
||||
It manages the following files and directories in the user's home directory:
|
||||
* `~/.config/solana/install/config.yml` - user configuration and information about currently installed software version
|
||||
* `~/.local/share/solana/install/bin` - a symlink to the current release. eg, `~/.local/share/solana-update/<update-pubkey>-<manifest_signature>/bin`
|
||||
* `~/.local/share/solana/install/releases/<download_sha256>/` - contents of a release
|
||||
|
||||
#### Command-line Interface
|
||||
```manpage
|
||||
solana-install 0.16.0
|
||||
The solana cluster software installer
|
||||
|
||||
USAGE:
|
||||
solana-install [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-c, --config <PATH> Configuration file to use [default: .../Library/Preferences/solana/install.yml]
|
||||
|
||||
SUBCOMMANDS:
|
||||
deploy deploys a new update
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
info displays information about the current installation
|
||||
init initializes a new installation
|
||||
run Runs a program while periodically checking and applying software updates
|
||||
update checks for an update, and if available downloads and applies it
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-install-init
|
||||
initializes a new installation
|
||||
|
||||
USAGE:
|
||||
solana-install init [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
OPTIONS:
|
||||
-d, --data_dir <PATH> Directory to store install data [default: .../Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://testnet.solana.com:8899]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-install-info
|
||||
displays information about the current installation
|
||||
|
||||
USAGE:
|
||||
solana-install info [FLAGS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-l, --local only display local information, don't check the cluster for new updates
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-install-deploy
|
||||
deploys a new update
|
||||
|
||||
USAGE:
|
||||
solana-install deploy <download_url> <update_manifest_keypair>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
ARGS:
|
||||
<download_url> URL to the solana release archive
|
||||
<update_manifest_keypair> Keypair file for the update manifest (/path/to/keypair.json)
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-install-update
|
||||
checks for an update, and if available downloads and applies it
|
||||
|
||||
USAGE:
|
||||
solana-install update
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-install-run
|
||||
Runs a program while periodically checking and applying software updates
|
||||
|
||||
USAGE:
|
||||
solana-install run <program_name> [program_arguments]...
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
ARGS:
|
||||
<program_name> program to run
|
||||
<program_arguments>... arguments to supply to the program
|
||||
|
||||
The program will be restarted upon a successful software update
|
||||
```
|
@@ -1,25 +0,0 @@
|
||||
# Instructions
|
||||
|
||||
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,20 +1,20 @@
|
||||
# What is Solana?
|
||||
|
||||
Solana is an open source project implementing a new,
|
||||
Solana is the name of an open source project that is implementing a new
|
||||
high-performance, permissionless blockchain. Solana is also the name of a
|
||||
company headquartered in San Francisco that maintains the open source project.
|
||||
|
||||
# About this Book
|
||||
|
||||
This book describes the Solana open source project, a blockchain built from the
|
||||
ground up for scale. The book covers why Solana is useful, how to use it, how it
|
||||
ground up for scale. The book covers why to use it, how to use it, how it
|
||||
works, and why it will continue to work long after the company Solana closes
|
||||
its doors. The goal of the Solana architecture is to demonstrate there exists a
|
||||
set of software algorithms that when used in combination to implement a
|
||||
blockchain, removes software as a performance bottleneck, allowing transaction
|
||||
throughput to scale proportionally with network bandwidth. The architecture
|
||||
goes on to satisfy all three desirable properties of a proper blockchain:
|
||||
it is scalable, secure and decentralized.
|
||||
goes on to satisfy all three desirable properties of a proper blockchain: that
|
||||
it not only be scalable, but that it is also secure and decentralized.
|
||||
|
||||
The architecture describes a theoretical upper bound of 710 thousand
|
||||
transactions per second (tps) on a standard gigabit network and 28.4 million
|
||||
@@ -32,7 +32,7 @@ solicitation for investment.
|
||||
|
||||
# History of the Solana Codebase
|
||||
|
||||
In November of 2017, Anatoly Yakovenko published a whitepaper describing Proof
|
||||
In November of 2017 Anatoly Yakovenko published a whitepaper describing Proof
|
||||
of History, a technique for keeping time between computers that do not trust
|
||||
one another. From Anatoly's previous experience designing distributed systems
|
||||
at Qualcomm, Mesosphere and Dropbox, he knew that a reliable clock makes
|
||||
@@ -41,13 +41,13 @@ resulting network can be blazing fast, bound only by network bandwidth.
|
||||
|
||||
Anatoly watched as blockchain systems without clocks, such as Bitcoin and
|
||||
Ethereum, struggled to scale beyond 15 transactions per second worldwide when
|
||||
centralized payment systems such as Visa required peaks of 65,000 tps. Without a
|
||||
centralized payment systems such as Visa required peaks of 65,000. Without a
|
||||
clock, it was clear they'd never graduate to being the global payment system or
|
||||
global supercomputer most had dreamed them to be. When Anatoly solved the problem of
|
||||
global supercomputer they had dreamed to be. When Anatoly solved the problem of
|
||||
getting computers that don’t trust each other to agree on time, he knew he had
|
||||
the key to bring 40 years of distributed systems research to the world of
|
||||
blockchain. The resulting cluster wouldn't be just 10 times faster, or a 100
|
||||
times, or a 1,000 times, but 10,000 times faster, right out of the gate!
|
||||
times, or a 1,000 times, but 10,000 times faster right out of the gate!
|
||||
|
||||
Anatoly's implementation began in a private codebase and was implemented in the
|
||||
C programming language. Greg Fitzgerald, who had previously worked with Anatoly
|
||||
@@ -72,7 +72,7 @@ Anatoly recruited Greg, Stephen and three others to co-found a company, then
|
||||
called Loom.
|
||||
|
||||
Around the same time, Ethereum-based project Loom Network sprung up and many
|
||||
people were confused about whether they were the same project. The Loom team decided it
|
||||
people were confused if they were the same project. The Loom team decided it
|
||||
would rebrand. They chose the name Solana, a nod to a small beach town North of
|
||||
San Diego called Solana Beach, where Anatoly, Greg and Stephen lived and surfed
|
||||
for three years when they worked for Qualcomm. On March 28th, the team created
|
||||
@@ -81,13 +81,13 @@ Solana.
|
||||
|
||||
In June of 2018, the team scaled up the technology to run on cloud-based
|
||||
networks and on July 19th, published a 50-node, permissioned, public testnet
|
||||
consistently supporting bursts of 250,000 transactions per second. In a later release in
|
||||
December, called v0.10 Pillbox, the team published a permissioned testnet
|
||||
consistently supporting bursts of 250,000 transactions per second. In the most
|
||||
recent release, v0.10 Pillbox, the team published a permissioned testnet
|
||||
running 150 nodes on a gigabit network and demonstrated soak tests processing
|
||||
an *average* of 200 thousand transactions per second with bursts over 500
|
||||
thousand. The project was also extended to support on-chain programs written in
|
||||
the C programming language and run concurrently in a safe execution environment
|
||||
called BPF.
|
||||
called BPF. Next step: going permissionless.
|
||||
|
||||
# What is a Solana Cluster?
|
||||
|
||||
@@ -110,7 +110,7 @@ organization that launched it.
|
||||
A sol is the name of Solana's native token, which can be passed to nodes in a
|
||||
Solana cluster in exchange for running an on-chain program or validating its
|
||||
output. The Solana protocol defines that only 1 billion sols will ever exist,
|
||||
but that the system may perform micropayments of fractional sols, and that a sol
|
||||
but that the system may perform micropayments of fractional sols and that a sol
|
||||
may be split as many as 34 times. The fractional sol is called a *lamport*. It
|
||||
is named in honor of Solana's biggest technical influence, [Leslie
|
||||
Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport). A lamport has a value
|
||||
|
@@ -24,23 +24,10 @@ Methods
|
||||
* [confirmTransaction](#confirmtransaction)
|
||||
* [getAccountInfo](#getaccountinfo)
|
||||
* [getBalance](#getbalance)
|
||||
* [getClusterNodes](#getclusternodes)
|
||||
* [getEpochInfo](#getepochinfo)
|
||||
* [getGenesisBlockhash](#getgenesisblockhash)
|
||||
* [getLeaderSchedule](#getleaderschedule)
|
||||
* [getProgramAccounts](#getprogramaccounts)
|
||||
* [getRecentBlockhash](#getrecentblockhash)
|
||||
* [getConfirmationTime](#getconfirmationTime)
|
||||
* [getLastId](#getlastid)
|
||||
* [getSignatureStatus](#getsignaturestatus)
|
||||
* [getSlot](#getslot)
|
||||
* [getSlotLeader](#getslotleader)
|
||||
* [getSlotsPerSegment](#getslotspersegment)
|
||||
* [getStorageTurn](#getstorageturn)
|
||||
* [getStorageTurnRate](#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](#gettransactioncount)
|
||||
* [getTotalSupply](#gettotalsupply)
|
||||
* [getVersion](#getversion)
|
||||
* [getVoteAccounts](#getvoteaccounts)
|
||||
* [requestAirdrop](#requestairdrop)
|
||||
* [sendTransaction](#sendtransaction)
|
||||
* [startSubscriptionChannel](#startsubscriptionchannel)
|
||||
@@ -48,8 +35,6 @@ Methods
|
||||
* [Subscription Websocket](#subscription-websocket)
|
||||
* [accountSubscribe](#accountsubscribe)
|
||||
* [accountUnsubscribe](#accountunsubscribe)
|
||||
* [programSubscribe](#programsubscribe)
|
||||
* [programUnsubscribe](#programunsubscribe)
|
||||
* [signatureSubscribe](#signaturesubscribe)
|
||||
* [signatureUnsubscribe](#signatureunsubscribe)
|
||||
|
||||
@@ -105,32 +90,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"jsonrpc":"2.0","result":true,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getAccountInfo
|
||||
Returns all information associated with the account of provided Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
|
||||
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `data`, array of bytes representing any data associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
### getBalance
|
||||
@@ -153,65 +112,45 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
|
||||
---
|
||||
|
||||
### getClusterNodes
|
||||
Returns information about all the nodes participating in the cluster
|
||||
### getAccountInfo
|
||||
Returns all information associated with the account of provided Pubkey
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
* `pubkey` - Node public key, as base-58 encoded string
|
||||
* `gossip` - Gossip network address for the node
|
||||
* `tpu` - TPU network address for the node
|
||||
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
|
||||
* `tokens`, number of tokens assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `userdata`, array of bytes representing any userdata associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
|
||||
* `loader`, array of 32 bytes representing the loader for this program (if `executable`), otherwise all
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
|
||||
{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getEpochInfo
|
||||
Returns information about the current epoch
|
||||
### getLastId
|
||||
Returns the last entry ID from the ledger
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an object with the following fields:
|
||||
* `epoch`, the current epoch
|
||||
* `slotIndex`, the current slot relative to the start of the current epoch
|
||||
* `slotsInEpoch`, the number of slots in this epoch
|
||||
* `string` - the ID of last entry, a Hash as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
### getGenesisBlockhash
|
||||
Returns the genesis block hash
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLastId"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
|
||||
@@ -219,77 +158,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
|
||||
---
|
||||
|
||||
### getLeaderSchedule
|
||||
Returns the leader schedule for the current epoch
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of leader public keys (as base-58 encoded
|
||||
strings) for each slot in the current epoch
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[...],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getProgramAccounts
|
||||
Returns all accounts owned by the provided program Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of program, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of arrays. Each sub array will contain:
|
||||
* `string` - the account Pubkey as base-58 encoded string
|
||||
and a JSON object, with the following sub fields:
|
||||
|
||||
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `data`, array of bytes representing any data associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getRecentBlockhash
|
||||
Returns a recent block hash from the ledger, and a fee schedule that can be used
|
||||
to compute the cost of submitting a transaction using it.
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
* `FeeCalculator object` - the fee schedule for this block hash
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getSignatureStatus
|
||||
Returns the status of a given signature. This method is similar to
|
||||
[confirmTransaction](#confirmtransaction) but provides more resolution for error
|
||||
@@ -299,10 +167,12 @@ events.
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `null` - Unknown transaction
|
||||
* `object` - Transaction status:
|
||||
* `"Ok": null` - Transaction was successful
|
||||
* `"Err": <ERR>` - Transaction failed with TransactionError <ERR> [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
* `string` - Transaction status:
|
||||
* `Confirmed` - Transaction was successful
|
||||
* `SignatureNotFound` - Unknown transaction
|
||||
* `ProgramRuntimeError` - An error occurred in the program that processed this Transaction
|
||||
* `AccountInUse` - Another Transaction had a write lock one of the Accounts specified in this Transaction. The Transaction may succeed if retried
|
||||
* `GenericFailure` - Some other error occurred. **Note**: In the future new Transaction statuses may be added to this list. It's safe to assume that all new statuses will be more specific error conditions that previously presented as `GenericFailure`
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
@@ -313,127 +183,7 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### getSlot
|
||||
Returns the current slot the node is processing
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Current slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1234","id":1}
|
||||
```
|
||||
-----
|
||||
|
||||
### getSlotLeader
|
||||
Returns the current slot leader
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `string` - Node Id as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getSlotsPerSegment
|
||||
Returns the current storage segment size in terms of slots
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in a storage segment
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurn
|
||||
Returns the current storage turn's blockhash and slot
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of
|
||||
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
|
||||
* `u64` - the current storage turn slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurnRate
|
||||
Returns the current storage turn rate in terms of slots per turn
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in storage turn
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getNumBlocksSinceSignatureConfirmation
|
||||
Returns the current number of blocks since signature has been confirmed.
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `integer` - count, as unsigned 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":8,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getTransactionCount
|
||||
Returns the current Transaction count from the ledger
|
||||
|
||||
@@ -453,80 +203,32 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getTotalSupply
|
||||
Returns the current total supply in Lamports
|
||||
### getConfirmationTime
|
||||
Returns the current cluster confirmation time in milliseconds
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `integer` - Total supply, as unsigned 64-bit integer
|
||||
* `integer` - confirmation time in milliseconds, as unsigned 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getConfirmationTime"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":10126,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getVersion
|
||||
Returns the current solana versions running on the node
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
* `solana-core`, software version of solana-core
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getVoteAccounts
|
||||
Returns the account info and associated stake for all the voting accounts in the current bank.
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object of `current` and `delinquent` accounts,
|
||||
each containing an array of JSON objects with the following sub fields:
|
||||
* `votePubkey` - Vote account public key, as base-58 encoded string
|
||||
* `nodePubkey` - Node public key, as base-58 encoded string
|
||||
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
|
||||
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
|
||||
* `commission`, an 8-bit integer used as a fraction (commission/MAX_U8) for rewards payout
|
||||
* `lastVote` - Most recent slot voted on by this vote account
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
|
||||
{"jsonrpc":"2.0","result":500,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### requestAirdrop
|
||||
Requests an airdrop of lamports to a Pubkey
|
||||
Requests an airdrop of tokens to a Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
|
||||
* `integer` - lamports, as a signed 64-bit integer
|
||||
* `string` - Pubkey of account to receive tokens, as base-58 encoded string
|
||||
* `integer` - token quantity, as a signed 64-bit integer
|
||||
|
||||
##### Results:
|
||||
* `string` - Transaction Signature of airdrop, as base-58 encoded string
|
||||
@@ -566,25 +268,14 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
|
||||
- Submit subscription requests to the websocket using the methods below
|
||||
- Multiple subscriptions may be active at once
|
||||
- All subscriptions take an optional `confirmations` parameter, which defines
|
||||
how many confirmed blocks the node should wait before sending a notification.
|
||||
The greater the number, the more likely the notification is to represent
|
||||
consensus across the cluster, and the less likely it is to be affected by
|
||||
forking or rollbacks. If unspecified, the default value is 0; the node will
|
||||
send a notification as soon as it witnesses the event. The maximum
|
||||
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
|
||||
represents the economic finality of the chain.
|
||||
|
||||
---
|
||||
|
||||
### accountSubscribe
|
||||
Subscribe to an account to receive notifications when the lamports or data
|
||||
for a given account public key changes
|
||||
Subscribe to an account to receive notifications when the userdata for a given account public key changes
|
||||
|
||||
##### Parameters:
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - Subscription id (needed to unsubscribe)
|
||||
@@ -594,21 +285,19 @@ for a given account public key changes
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
##### Notification Format:
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
|
||||
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"tokens":1,"userdata":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### accountUnsubscribe
|
||||
Unsubscribe from account change notifications
|
||||
Unsubscribe from account userdata change notifications
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - id of account Subscription to cancel
|
||||
@@ -627,66 +316,12 @@ Unsubscribe from account change notifications
|
||||
|
||||
---
|
||||
|
||||
### programSubscribe
|
||||
Subscribe to a program to receive notifications when the lamports or data
|
||||
for a given account owned by the program changes
|
||||
|
||||
##### Parameters:
|
||||
* `string` - program_id Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - Subscription id (needed to unsubscribe)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
##### Notification Format:
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `object` - account info JSON object (see [getAccountInfo](#getaccountinfo) for field details)
|
||||
```bash
|
||||
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### programUnsubscribe
|
||||
Unsubscribe from program-owned account change notifications
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - id of account Subscription to cancel
|
||||
|
||||
##### Results:
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### signatureSubscribe
|
||||
Subscribe to a transaction signature to receive notification when the transaction is confirmed
|
||||
On `signatureNotification`, the subscription is automatically cancelled
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Transaction Signature, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - subscription id (needed to unsubscribe)
|
||||
@@ -696,8 +331,6 @@ On `signatureNotification`, the subscription is automatically cancelled
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
@@ -710,10 +343,10 @@ On `signatureNotification`, the subscription is automatically cancelled
|
||||
---
|
||||
|
||||
### signatureUnsubscribe
|
||||
Unsubscribe from signature confirmation notification
|
||||
Unsubscribe from account userdata change notifications
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - subscription id to cancel
|
||||
* `integer` - id of account subscription to cancel
|
||||
|
||||
##### Results:
|
||||
* `bool` - unsubscribe success message
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user