Compare commits
94 Commits
Author | SHA1 | Date | |
---|---|---|---|
8f181b4350 | |||
48844924e5 | |||
f84593ad5f | |||
0469dc52ac | |||
4cf418f33f | |||
6c46fcfa4e | |||
12ec5304f2 | |||
e32f798d5f | |||
68a8b955bc | |||
f479021c0f | |||
b91afb7079 | |||
e189c429d5 | |||
6a1904664c | |||
3285cf8047 | |||
bdee3a25f2 | |||
8655df0520 | |||
c43eecb8ca | |||
18f45ebc2c | |||
fd28642603 | |||
038583b466 | |||
ed138d392d | |||
58f1f0a28b | |||
330d9330b0 | |||
d626a89c88 | |||
db5d22e532 | |||
aa8759744e | |||
060db36c34 | |||
fa1ea1c458 | |||
7685ba2805 | |||
a0d940acf0 | |||
f4c914a630 | |||
eede274cfe | |||
4df79b653b | |||
a2c1fa7cb4 | |||
95cead91a5 | |||
89c42ecd3f | |||
f93c9f052f | |||
e2871053bd | |||
351c9c33d2 | |||
59f2a478b7 | |||
3f7cd4adc4 | |||
4318854a64 | |||
430740b691 | |||
797603a0fe | |||
f402139991 | |||
4db72d85d7 | |||
007e17c290 | |||
ad7e727938 | |||
3d5eeab6d9 | |||
8278585545 | |||
061d6ec8fd | |||
000cc27e53 | |||
9b3092b965 | |||
ca819fc4fb | |||
5ff8f57c0e | |||
4798612560 | |||
9760cb2e6a | |||
46b3b3a1c6 | |||
1e70f85e83 | |||
b2d6681762 | |||
1b51cba778 | |||
19ab7333aa | |||
b0e6604b9a | |||
9ce1d5e990 | |||
facc47cb62 | |||
3dba8b7952 | |||
5e40a5bfc1 | |||
c60baf99f3 | |||
de04884c1b | |||
e666509409 | |||
28aff96d21 | |||
242975f8cd | |||
c6ba6cac83 | |||
dc67dd3357 | |||
733c2a0b07 | |||
07d6212d18 | |||
c20d60e4cf | |||
7147f03efe | |||
6740cb5b02 | |||
1e8e99cc3e | |||
ef7f30e09f | |||
ca8e0ec7ae | |||
2a4f4b3e53 | |||
7cecd3851a | |||
4d189f2c38 | |||
9a232475a7 | |||
09c9897591 | |||
06d7573478 | |||
0b55ffa368 | |||
ae750bb16b | |||
80b2f2f6b7 | |||
6684d84fbc | |||
dc02abae3c | |||
6caec655d3 |
@ -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
|
16
.buildkite/env/secrets.ejson
vendored
16
.buildkite/env/secrets.ejson
vendored
@ -1,14 +1,12 @@
|
||||
{
|
||||
"_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:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:rHBSqXK7uSnveA4qwUxARZjTNZcA0hXU:ko8lLGwPECpVm19znWBRxKEpMF7xpTHBCEzVOxRar2wDThw4lNDAKqTS61vtkJLtdkHtug==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:NzN6y0ooXJBYvxB589khepthSxhKFkLB:ZTTFZh2A/kB2SAgjJJAMbwAfanRlzxOCNMVcA2MXBCpQHJeeZGULg+0MLACYswfS]",
|
||||
"GITHUB_TOKEN": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:iy0Fnxeo0aslTCvgXc5Ddj2ly6ZsQ8gK:GNOOj/kZUJ2rYKxTbLyVKtajWNoGQ3PcChwfEB4HdN18qDHlB96Z7gx01Pcf0qeIHODOWRtxlH4=]",
|
||||
"INFLUX_DATABASE": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:Ly/TpIRF0oCxmiBWv225S3mX8s6pfQR+:+tXGB2c9rRCVDcgNO1IDOo89]",
|
||||
"INFLUX_PASSWORD": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:ycrq1uQLoSfI932czD+krUOaJeLWpeq6:2iS7ukp/C7wVD3IT0GvQVcwccWGyLr4UocStF/XiDi0OB/N3YKIKN8SQU4ob1b6StAPZ/XOHmag=]",
|
||||
"INFLUX_USERNAME": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:35hBKofakZ4Db/u0TOW53RXoNWzJTIcl:HWREcMTrgZ8DGB0ZupgSzNWr/tVyE06P]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:kRz8CyJYKAg/AiwgLrcRNDJAmlRX2zvX:uV1XV6y2Fb+dN4Z9BIMPBRiNS3n+NL8GlJXyu1i7meIsph1DzfLg4Thcp5Mj9nUsFNLgqQgjnsa5C4XNY/h5AgMSzRrJxVj7RhVTRmDJ5/Vjq6v7wCMRfBOvF3rITsV4zTwWSV8yafFmS+ZQ+QJTRgtYsuoYAUNZ06IEebfDHcuNwws72hEGoD9w43hOLSpyEOmXbtZ9h1lIRxrgsrhYDpBlU5LkhDeTXAX5M5dwYxyquJFRwd5quGDV5DYsCh9bAkbjAyjWYymVJ78U9YJIQHT9izzQqTDlMQN49EbLo7MDIaC7O7HVtb7unDJs+DRejbHacoyWVulqVVwu3GRiZezu8zdjwzGHphMMxOtKQaidnqYgflNp/O01I8wZRgR1alsGcmIhEhI8YV/IvQ==]"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
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
|
||||
@ -33,10 +31,3 @@ source ci/env.sh
|
||||
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
.gitignore
vendored
11
.gitignore
vendored
@ -1,12 +1,10 @@
|
||||
/target/
|
||||
/ledger-tool/target/
|
||||
/wallet/target/
|
||||
/core/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
|
||||
@ -23,4 +21,3 @@ log-*.txt
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
45
.mergify.yml
45
.mergify.yml
@ -1,45 +0,0 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.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
|
44
.travis.yml
44
.travis.yml
@ -1,44 +0,0 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
cache: cargo
|
||||
rust:
|
||||
- 1.36.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
|
@ -46,17 +46,10 @@ 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.
|
||||
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.
|
||||
|
||||
Rust coding conventions
|
||||
---
|
||||
@ -96,23 +89,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/book-edge/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.
|
||||
|
4510
Cargo.lock
generated
4510
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
107
Cargo.toml
107
Cargo.toml
@ -1,57 +1,92 @@
|
||||
[package]
|
||||
name = "solana-workspace"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.12.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]
|
||||
chacha = ["solana/chacha"]
|
||||
cuda = ["solana/cuda"]
|
||||
erasure = ["solana/erasure"]
|
||||
|
||||
[dev-dependencies]
|
||||
bincode = "1.1.2"
|
||||
bs58 = "0.2.0"
|
||||
hashbrown = "0.1.8"
|
||||
log = "0.4.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.0.0"
|
||||
reqwest = "0.9.11"
|
||||
serde_json = "1.0.39"
|
||||
solana = { path = "core", version = "0.12.1" }
|
||||
solana-budget-program = { path = "programs/budget", version = "0.12.1" }
|
||||
solana-client = { path = "client", version = "0.12.1" }
|
||||
solana-logger = { path = "logger", version = "0.12.1" }
|
||||
solana-netutil = { path = "netutil", version = "0.12.1" }
|
||||
solana-runtime = { path = "runtime", version = "0.12.1" }
|
||||
solana-sdk = { path = "sdk", version = "0.12.1" }
|
||||
solana-vote-api = { path = "programs/vote_api", version = "0.12.1" }
|
||||
sys-info = "0.5.6"
|
||||
|
||||
[[bench]]
|
||||
name = "banking_stage"
|
||||
|
||||
[[bench]]
|
||||
name = "blocktree"
|
||||
|
||||
[[bench]]
|
||||
name = "ledger"
|
||||
|
||||
[[bench]]
|
||||
name = "gen_keys"
|
||||
|
||||
[[bench]]
|
||||
name = "sigverify"
|
||||
|
||||
[[bench]]
|
||||
required-features = ["chacha"]
|
||||
name = "chacha"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"bench-exchange",
|
||||
".",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"fullnode",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"netutil",
|
||||
"programs/bpf",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"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",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/rewards",
|
||||
"programs/rewards_api",
|
||||
"programs/storage",
|
||||
"programs/storage_api",
|
||||
"programs/vote",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"upload-perf",
|
||||
"validator-info",
|
||||
"vote-signer",
|
||||
"wallet",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf/rust/noop",
|
||||
]
|
||||
exclude = ["programs/bpf/rust/noop"]
|
||||
|
94
README.md
94
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.34.0, please update it:
|
||||
If your rustc version is lower than 1.31.0, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@ -100,7 +66,7 @@ $ cd solana
|
||||
Build
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
$ cargo build --all
|
||||
```
|
||||
|
||||
Then to run a minimal local cluster
|
||||
@ -114,7 +80,13 @@ 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
|
||||
@ -127,9 +99,12 @@ 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
|
||||
|
||||
@ -156,47 +131,6 @@ 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
|
||||
---
|
||||
|
||||
|
107
RELEASE.md
107
RELEASE.md
@ -61,116 +61,45 @@ 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>`
|
||||
|
||||
#### Update master with the next version
|
||||
|
||||
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 the new branch to the solana repository
|
||||
1. 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 a
|
||||
`cargo build --all` 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
|
||||
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
|
||||
1. 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.
|
||||
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:
|
||||
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
|
||||
1. ...
|
||||
1. 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.
|
||||
1. 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`, then rebuild with `cargo
|
||||
build` to cause a refresh of `Cargo.lock`.
|
||||
`./scripts/increment-cargo-version.sh patch`, then rebuild with a `cargo
|
||||
build --all` to cause a refresh of `Cargo.lock`.
|
||||
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
|
||||
release branch.
|
||||
|
||||
### 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
|
||||
release branch
|
||||
|
4
bench-exchange/.gitignore
vendored
4
bench-exchange/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@ -1,42 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.17.2"
|
||||
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.0"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.7"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.97"
|
||||
serde_derive = "1.0.97"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana = { path = "../core", version = "0.17.2" }
|
||||
solana-client = { path = "../client", version = "0.17.2" }
|
||||
solana-drone = { path = "../drone", version = "0.17.2" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.17.2" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.17.2" }
|
||||
solana-logger = { path = "../logger", version = "0.17.2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.17.2" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.17.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.2" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.8.1"
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@ -1,480 +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 Swapper 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.
|
||||
- Token pair
|
||||
- A unique ordered list of two tokens. For the four types of tokens used in
|
||||
this demo, the valid pairs are AB, AC, AD, BC, BD, CD.
|
||||
- Direction of trade
|
||||
- Describes which token in the pair the investor wants to sell and buy and can
|
||||
be either "To" or "From". For example, if an investor issues a "To" trade
|
||||
for "AB" then they which to exchange A tokens to B tokens. A "From" order
|
||||
would read the other way, A tokens from B tokens.
|
||||
- Price ratio
|
||||
- An expression of the relative prices of two tokens. They consist of the
|
||||
price of the primary token and the price of the secondary token. For
|
||||
simplicity sake, the primary token's price is always 1, which forces the
|
||||
secondary to be the common denominator. For example, if token A was worth
|
||||
2 and token B was worth 6, the price ratio would be 1:3 or just 3. Price
|
||||
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 executed by the exchange requesting the trade of one
|
||||
type of token for another. order requests are made up of the token pair,
|
||||
the direction of the trade, 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 Swapper initiating the swap request.
|
||||
- Swap requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Swap request
|
||||
- A request to exchange tokens between to orders
|
||||
- Trade swap
|
||||
- A successful trade. A swap consists of two matching orders that meet
|
||||
swap requirements. A trade swap may not wholly satisfy one or both of the
|
||||
orders in which case the orders are adjusted appropriately. As
|
||||
long as the swap requirements are met there will be an exchange of tokens
|
||||
between accounts. Any price spread is deposited into the Swapper's profit
|
||||
account. All trade swaps are recorded in a new 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.
|
||||
- Swapper
|
||||
- An agent who facilitates trading between investors. Swappers operate as
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Swapper issues a swap request to the exchange.
|
||||
Swappers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Swappers 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 Swappers.
|
||||
|
||||
## Exchange startup
|
||||
|
||||
The exchange is up and running when it reaches a state where it can take
|
||||
investor's trades and Swapper's swap requests. To achieve this state the
|
||||
following must occur in order:
|
||||
|
||||
- Start the Solana blockchain
|
||||
- Start the Swapper thin-client
|
||||
- The Swapper 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 Swapper starts responding to queries for bid/ask price and OHLCV
|
||||
|
||||
The Swapper 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 Swapper could come and go without missing a trade. One way to achieve
|
||||
this is for the Swapper 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. Swappers 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 Swappers 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 Swapper 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
|
||||
Swapper 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
|
||||
Swapper's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Swapper's profit for initiating the trade.
|
||||
|
||||
The Swapper 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
|
||||
- Swapper 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 Swapper 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
|
||||
- Swapper 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 Swapper 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
|
||||
- Swapper 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 Swapper 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
|
||||
- Swapper 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 Swappers 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 Swappers profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
```
|
||||
|
||||
## Quotes and OHLCV
|
||||
|
||||
The Swapper 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::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::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.direction, info.tokens, info.price)?;
|
||||
match info.direction {
|
||||
Direction::To => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
Direction::From => {
|
||||
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.17.2"
|
||||
version = "0.12.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana = { path = "../core", version = "0.17.2" }
|
||||
solana-logger = { path = "../logger", version = "0.17.2" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.2" }
|
||||
clap = "2.32.0"
|
||||
solana = { path = "../core", version = "0.12.1" }
|
||||
solana-logger = { path = "../logger", version = "0.12.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.12.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana::packet::PacketsRecycler;
|
||||
use solana::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
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;
|
||||
@ -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();
|
||||
@ -85,13 +81,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let (s_reader, r_reader) = channel();
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
&exit,
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
));
|
||||
read_threads.push(receiver(Arc::new(read), &exit, s_reader, "bench-streamer"));
|
||||
}
|
||||
|
||||
let t_producer1 = producer(&addr, exit.clone());
|
||||
|
4
bench-tps/.gitignore
vendored
4
bench-tps/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@ -2,33 +2,21 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.17.2"
|
||||
version = "0.12.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.7"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.97"
|
||||
serde_derive = "1.0.97"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana = { path = "../core", version = "0.17.2" }
|
||||
solana-client = { path = "../client", version = "0.17.2" }
|
||||
solana-drone = { path = "../drone", version = "0.17.2" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.17.2" }
|
||||
solana-logger = { path = "../logger", version = "0.17.2" }
|
||||
solana-metrics = { path = "../metrics", version = "0.17.2" }
|
||||
solana-measure = { path = "../measure", version = "0.17.2" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.2" }
|
||||
solana-runtime = { path = "../runtime", version = "0.17.2" }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.2" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.17.2" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.17.2" }
|
||||
clap = "2.32.0"
|
||||
rayon = "1.0.3"
|
||||
serde_json = "1.0.39"
|
||||
solana = { path = "../core", version = "0.12.1" }
|
||||
solana-client = { path = "../client", version = "0.12.1" }
|
||||
solana-drone = { path = "../drone", version = "0.12.1" }
|
||||
solana-logger = { path = "../logger", version = "0.12.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.12.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.12.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,14 +2,13 @@ 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,
|
||||
@ -18,17 +17,14 @@ pub struct Config {
|
||||
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,
|
||||
@ -37,26 +33,23 @@ impl Default for Config {
|
||||
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 +57,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 +75,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 +95,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")
|
||||
@ -122,30 +120,6 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.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 +130,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)
|
||||
});
|
||||
@ -202,23 +176,8 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
}
|
||||
|
||||
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,251 @@
|
||||
#[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::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use crate::bench::*;
|
||||
use solana::cluster_info::FULLNODE_PORT_RANGE;
|
||||
use solana::gen_keys::GenKeys;
|
||||
use solana::gossip_service::discover;
|
||||
use solana_client::client::create_client;
|
||||
use solana_metrics;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::collections::VecDeque;
|
||||
use std::process::exit;
|
||||
|
||||
/// Number of signatures for all transactions in ~1 week at ~100K TPS
|
||||
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
|
||||
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;
|
||||
|
||||
fn main() {
|
||||
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,
|
||||
thread_batch_sleep_ms,
|
||||
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,
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
let nodes = discover(&network, num_nodes).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
if nodes.len() < num_nodes {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
if reject_extra_nodes && nodes.len() > num_nodes {
|
||||
eprintln!(
|
||||
"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 converge_only {
|
||||
return;
|
||||
}
|
||||
let cluster_entrypoint = nodes[0].clone(); // Pick the first node, why not?
|
||||
|
||||
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
|
||||
let mut keypairs = vec![];
|
||||
let mut last_balance = 0;
|
||||
let mut client = create_client(cluster_entrypoint.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
let mut barrier_client =
|
||||
create_client(cluster_entrypoint.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
|
||||
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 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_source_keypair = Keypair::new();
|
||||
let barrier_dest_id = Keypair::new().pubkey();
|
||||
|
||||
println!("Get lamports...");
|
||||
let num_lamports_per_account = 20;
|
||||
|
||||
// Sample the first keypair, see if it has lamports, if so then resume
|
||||
// to avoid lamport loss
|
||||
let keypair0_balance = client
|
||||
.poll_get_balance(&gen_keypairs.last().unwrap().pubkey())
|
||||
.unwrap_or(0);
|
||||
|
||||
if num_lamports_per_account > keypair0_balance {
|
||||
let extra = num_lamports_per_account - keypair0_balance;
|
||||
let total = extra * (gen_keypairs.len() as u64);
|
||||
airdrop_lamports(&mut client, &drone_addr, &id, total);
|
||||
println!("adding more lamports {}", 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_lamports(&mut barrier_client, &drone_addr, &barrier_source_keypair, 1);
|
||||
|
||||
println!("Get last ID...");
|
||||
let mut blockhash = client.get_recent_blockhash();
|
||||
println!("Got last ID {:?}", blockhash);
|
||||
|
||||
let first_tx_count = client.transaction_count();
|
||||
println!("Initial transaction count {}", first_tx_count);
|
||||
|
||||
let exit_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// 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 cluster_entrypoint = cluster_entrypoint.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,
|
||||
&cluster_entrypoint,
|
||||
&shared_tx_active_thread_count,
|
||||
&total_tx_sent_count,
|
||||
thread_batch_sleep_ms,
|
||||
);
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// generate and send transactions for the specified duration
|
||||
let start = Instant::now();
|
||||
let mut reclaim_lamports_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_lamport_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_lamports_back_to_source_account,
|
||||
&cluster_entrypoint,
|
||||
);
|
||||
// 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 blockhash,
|
||||
&barrier_source_keypair,
|
||||
&barrier_dest_id,
|
||||
);
|
||||
|
||||
i += 1;
|
||||
if should_switch_directions(num_lamports_per_account, i) {
|
||||
reclaim_lamports_back_to_source_account = !reclaim_lamports_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_lamport_balance(balance);
|
||||
|
||||
compute_and_report_stats(
|
||||
&maxes,
|
||||
sample_period,
|
||||
&start.elapsed(),
|
||||
total_tx_sent_count.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
248
benches/append_vec.rs
Normal file
248
benches/append_vec.rs
Normal file
@ -0,0 +1,248 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate rand;
|
||||
extern crate test;
|
||||
|
||||
use bincode::{deserialize, serialize_into, serialized_size};
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_runtime::append_vec::{
|
||||
deserialize_account, get_serialized_size, serialize_account, AppendVec,
|
||||
};
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::env;
|
||||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::spawn;
|
||||
use test::Bencher;
|
||||
|
||||
const START_SIZE: u64 = 4 * 1024 * 1024;
|
||||
const INC_SIZE: u64 = 1 * 1024 * 1024;
|
||||
|
||||
macro_rules! align_up {
|
||||
($addr: expr, $align: expr) => {
|
||||
($addr + ($align - 1)) & !($align - 1)
|
||||
};
|
||||
}
|
||||
|
||||
fn get_append_vec_bench_path(path: &str) -> PathBuf {
|
||||
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
|
||||
let mut buf = PathBuf::new();
|
||||
buf.push(&format!("{}/{}", out_dir, path));
|
||||
buf
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_atomic_append(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_append");
|
||||
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
|
||||
bencher.iter(|| {
|
||||
if vec.append(AtomicUsize::new(0)).is_none() {
|
||||
assert!(vec.grow_file().is_ok());
|
||||
assert!(vec.append(AtomicUsize::new(0)).is_some());
|
||||
}
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_atomic_random_access(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_ra");
|
||||
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
|
||||
let size = 1_000_000;
|
||||
for _ in 0..size {
|
||||
if vec.append(AtomicUsize::new(0)).is_none() {
|
||||
assert!(vec.grow_file().is_ok());
|
||||
assert!(vec.append(AtomicUsize::new(0)).is_some());
|
||||
}
|
||||
}
|
||||
bencher.iter(|| {
|
||||
let index = thread_rng().gen_range(0, size as u64);
|
||||
vec.get(index * std::mem::size_of::<AtomicUsize>() as u64);
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_atomic_random_change(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_rax");
|
||||
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
|
||||
let size = 1_000_000;
|
||||
for k in 0..size {
|
||||
if vec.append(AtomicUsize::new(k)).is_none() {
|
||||
assert!(vec.grow_file().is_ok());
|
||||
assert!(vec.append(AtomicUsize::new(k)).is_some());
|
||||
}
|
||||
}
|
||||
bencher.iter(|| {
|
||||
let index = thread_rng().gen_range(0, size as u64);
|
||||
let atomic1 = vec.get(index * std::mem::size_of::<AtomicUsize>() as u64);
|
||||
let current1 = atomic1.load(Ordering::Relaxed);
|
||||
assert_eq!(current1, index as usize);
|
||||
let next = current1 + 1;
|
||||
let mut index = vec.append(AtomicUsize::new(next));
|
||||
if index.is_none() {
|
||||
assert!(vec.grow_file().is_ok());
|
||||
index = vec.append(AtomicUsize::new(next));
|
||||
}
|
||||
let atomic2 = vec.get(index.unwrap());
|
||||
let current2 = atomic2.load(Ordering::Relaxed);
|
||||
assert_eq!(current2, next);
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_atomic_random_read(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_read");
|
||||
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
|
||||
let size = 1_000_000;
|
||||
for _ in 0..size {
|
||||
if vec.append(AtomicUsize::new(0)).is_none() {
|
||||
assert!(vec.grow_file().is_ok());
|
||||
assert!(vec.append(AtomicUsize::new(0)).is_some());
|
||||
}
|
||||
}
|
||||
bencher.iter(|| {
|
||||
let index = thread_rng().gen_range(0, size);
|
||||
let atomic1 = vec.get((index * std::mem::size_of::<AtomicUsize>()) as u64);
|
||||
let current1 = atomic1.load(Ordering::Relaxed);
|
||||
assert_eq!(current1, 0);
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_concurrent_lock_append(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_lock_append");
|
||||
let vec = Arc::new(RwLock::new(AppendVec::<AtomicUsize>::new(
|
||||
&path, true, START_SIZE, INC_SIZE,
|
||||
)));
|
||||
let vec1 = vec.clone();
|
||||
let size = 1_000_000;
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
let count1 = count.clone();
|
||||
spawn(move || loop {
|
||||
let mut len = count.load(Ordering::Relaxed);
|
||||
{
|
||||
let rlock = vec1.read().unwrap();
|
||||
loop {
|
||||
if rlock.append(AtomicUsize::new(0)).is_none() {
|
||||
break;
|
||||
}
|
||||
len = count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
if len >= size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut wlock = vec1.write().unwrap();
|
||||
if len >= size {
|
||||
break;
|
||||
}
|
||||
assert!(wlock.grow_file().is_ok());
|
||||
}
|
||||
});
|
||||
bencher.iter(|| {
|
||||
let _rlock = vec.read().unwrap();
|
||||
let len = count1.load(Ordering::Relaxed);
|
||||
assert!(len < size * 2);
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn append_vec_concurrent_get_append(bencher: &mut Bencher) {
|
||||
let path = get_append_vec_bench_path("bench_get_append");
|
||||
let vec = Arc::new(RwLock::new(AppendVec::<AtomicUsize>::new(
|
||||
&path, true, START_SIZE, INC_SIZE,
|
||||
)));
|
||||
let vec1 = vec.clone();
|
||||
let size = 1_000_000;
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
let count1 = count.clone();
|
||||
spawn(move || loop {
|
||||
let mut len = count.load(Ordering::Relaxed);
|
||||
{
|
||||
let rlock = vec1.read().unwrap();
|
||||
loop {
|
||||
if rlock.append(AtomicUsize::new(0)).is_none() {
|
||||
break;
|
||||
}
|
||||
len = count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
if len >= size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut wlock = vec1.write().unwrap();
|
||||
if len >= size {
|
||||
break;
|
||||
}
|
||||
assert!(wlock.grow_file().is_ok());
|
||||
}
|
||||
});
|
||||
bencher.iter(|| {
|
||||
let rlock = vec.read().unwrap();
|
||||
let len = count1.load(Ordering::Relaxed);
|
||||
if len > 0 {
|
||||
let index = thread_rng().gen_range(0, len);
|
||||
rlock.get((index * std::mem::size_of::<AtomicUsize>()) as u64);
|
||||
}
|
||||
});
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_account_serialize(bencher: &mut Bencher) {
|
||||
let num: usize = 1000;
|
||||
let account = Account::new(2, 100, &Keypair::new().pubkey());
|
||||
let len = get_serialized_size(&account);
|
||||
let ser_len = align_up!(len + std::mem::size_of::<u64>(), std::mem::size_of::<u64>());
|
||||
let mut memory = vec![0; num * ser_len];
|
||||
bencher.iter(|| {
|
||||
for i in 0..num {
|
||||
let start = i * ser_len;
|
||||
serialize_account(&mut memory[start..start + ser_len], &account, len);
|
||||
}
|
||||
});
|
||||
|
||||
// make sure compiler doesn't delete the code.
|
||||
let index = thread_rng().gen_range(0, num);
|
||||
if memory[index] != 0 {
|
||||
println!("memory: {}", memory[index]);
|
||||
}
|
||||
|
||||
let start = index * ser_len;
|
||||
let new_account = deserialize_account(&memory[start..start + ser_len], 0, num * len).unwrap();
|
||||
assert_eq!(new_account, account);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_account_serialize_bincode(bencher: &mut Bencher) {
|
||||
let num: usize = 1000;
|
||||
let account = Account::new(2, 100, &Keypair::new().pubkey());
|
||||
let len = serialized_size(&account).unwrap() as usize;
|
||||
let mut memory = vec![0u8; num * len];
|
||||
bencher.iter(|| {
|
||||
for i in 0..num {
|
||||
let start = i * len;
|
||||
let cursor = Cursor::new(&mut memory[start..start + len]);
|
||||
serialize_into(cursor, &account).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// make sure compiler doesn't delete the code.
|
||||
let index = thread_rng().gen_range(0, len);
|
||||
if memory[index] != 0 {
|
||||
println!("memory: {}", memory[index]);
|
||||
}
|
||||
|
||||
let start = index * len;
|
||||
let new_account: Account = deserialize(&memory[start..start + len]).unwrap();
|
||||
assert_eq!(new_account, account);
|
||||
}
|
241
benches/banking_stage.rs
Normal file
241
benches/banking_stage.rs
Normal file
@ -0,0 +1,241 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana::cluster_info::ClusterInfo;
|
||||
use solana::cluster_info::Node;
|
||||
use solana::packet::to_packets_chunked;
|
||||
use solana::poh_recorder::WorkingBankEntries;
|
||||
use solana::service::Service;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::genesis_block::GenesisBlock;
|
||||
use solana_sdk::hash::hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{KeypairUtil, Signature};
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES};
|
||||
use std::iter;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use test::Bencher;
|
||||
|
||||
fn check_txs(receiver: &Receiver<WorkingBankEntries>, 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]
|
||||
#[ignore]
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
let num_threads = 4;
|
||||
// a multiple of packet chunk 2X duplicates to avoid races
|
||||
let txes = 192 * 50 * num_threads * 2;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&genesis_block));
|
||||
let dummy = SystemTransaction::new_move(
|
||||
&mint_keypair,
|
||||
&mint_keypair.pubkey(),
|
||||
1,
|
||||
genesis_block.hash(),
|
||||
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 = SystemTransaction::new_move(
|
||||
&mint_keypair,
|
||||
&tx.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_block.hash(),
|
||||
0,
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let 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 (exit, poh_recorder, poh_service, signal_receiver) = create_test_recorder(&bank);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new(&cluster_info, &poh_recorder, verified_receiver);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let mut id = genesis_block.hash();
|
||||
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
|
||||
id = hash(&id.as_ref());
|
||||
bank.register_tick(&id);
|
||||
}
|
||||
|
||||
let half_len = verified.len() / 2;
|
||||
let mut start = 0;
|
||||
bencher.iter(move || {
|
||||
// make sure the transactions are still valid
|
||||
bank.register_tick(&genesis_block.hash());
|
||||
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver, txes / 2);
|
||||
bank.clear_signatures();
|
||||
start += half_len;
|
||||
start %= verified.len();
|
||||
});
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
poh_service.join().unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
||||
let progs = 4;
|
||||
let num_threads = 4;
|
||||
// a multiple of packet chunk 2X duplicates to avoid races
|
||||
let txes = 96 * 100 * num_threads * 2;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&genesis_block));
|
||||
let dummy = SystemTransaction::new_move(
|
||||
&mint_keypair,
|
||||
&mint_keypair.pubkey(),
|
||||
1,
|
||||
genesis_block.hash(),
|
||||
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 = SystemTransaction::new_move(
|
||||
&mint_keypair,
|
||||
&tx.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_block.hash(),
|
||||
0,
|
||||
);
|
||||
bank.process_transaction(&fund).unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let 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 (exit, poh_recorder, poh_service, signal_receiver) = create_test_recorder(&bank);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new(&cluster_info, &poh_recorder, verified_receiver);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let mut id = genesis_block.hash();
|
||||
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
|
||||
id = hash(&id.as_ref());
|
||||
bank.register_tick(&id);
|
||||
}
|
||||
|
||||
let half_len = verified.len() / 2;
|
||||
let mut start = 0;
|
||||
bencher.iter(move || {
|
||||
// make sure the transactions are still valid
|
||||
bank.register_tick(&genesis_block.hash());
|
||||
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver, txes / 2);
|
||||
bank.clear_signatures();
|
||||
start += half_len;
|
||||
start %= verified.len();
|
||||
});
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
poh_service.join().unwrap();
|
||||
}
|
@ -5,7 +5,7 @@ extern crate test;
|
||||
use solana::entry::{next_entries, reconstruct_entries_from_blobs, EntrySlice};
|
||||
use solana_sdk::hash::{hash, Hash};
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
@ -13,7 +13,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 = SystemTransaction::new_move(&keypair, &keypair.pubkey(), 1, one, 0);
|
||||
let transactions = vec![tx0; 10];
|
||||
let entries = next_entries(&zero, 1, transactions);
|
||||
|
@ -3,7 +3,6 @@
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::to_packets;
|
||||
use solana::recycler::Recycler;
|
||||
use solana::sigverify;
|
||||
use solana::test_tx::test_tx;
|
||||
use test::Bencher;
|
||||
@ -15,10 +14,8 @@ fn bench_sigverify(bencher: &mut Bencher) {
|
||||
// generate packet vector
|
||||
let batches = to_packets(&vec![tx; 128]);
|
||||
|
||||
let recycler = Recycler::default();
|
||||
let recycler_out = Recycler::default();
|
||||
// verify packets
|
||||
bencher.iter(|| {
|
||||
let _ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out);
|
||||
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,15 +0,0 @@
|
||||
+--------------+
|
||||
| |
|
||||
+------------+ Leader +------------+
|
||||
| | | |
|
||||
| +--------------+ |
|
||||
v v
|
||||
+------------+----------------------------------------+------------+
|
||||
| |
|
||||
| +-----------------+ Neighborhood 0 +-----------------+ |
|
||||
| | +--------------------->+ | |
|
||||
| | Validator 1 | | Validator 2 | |
|
||||
| | +<---------------------+ | |
|
||||
| +-----------------+ +-----------------+ |
|
||||
| |
|
||||
+------------------------------------------------------------------+
|
@ -1,18 +1,28 @@
|
||||
+--------------------+
|
||||
| |
|
||||
+--------+ 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 v v v v
|
||||
+--------------------+ +--------------------+ +--------------------+ +--------------------+
|
||||
| | | | | | | |
|
||||
| Neighborhood 1 | | Neighborhood 2 | | Neighborhood 3 | | Neighborhood 4 |
|
||||
| | | | | | | |
|
||||
+--------------------+ +--------------------+ +--------------------+ +--------------------+
|
||||
|
@ -1,5 +1,5 @@
|
||||
.--------------------------------------.
|
||||
| Validator |
|
||||
| Fullnode |
|
||||
| |
|
||||
.--------. | .-------------------. |
|
||||
| |---->| | |
|
||||
@ -25,6 +25,6 @@
|
||||
| | | | | | | Downstream | |
|
||||
| | .--+--. .-------+---. | | | Validators | |
|
||||
`-------->| TPU +---->| Broadcast +--------------->| | |
|
||||
| `-----` | Stage | | | `------------` |
|
||||
| `-----` | Service | | | `------------` |
|
||||
| `-----------` | `------------------`
|
||||
`--------------------------------------`
|
@ -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,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 | | | Broadcast |
|
||||
| Clients |--->| Stage |->| Stage |->| Stage |------>| Service |
|
||||
`---------` | | | | | | | | | |
|
||||
| `-------` `-----------` `----+----` | `------------`
|
||||
| | |
|
||||
`---------------------------------|---------`
|
||||
|
|
||||
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 |
|
||||
| |
|
||||
`------------`
|
||||
|
@ -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,6 @@
|
||||
- [Terminology](terminology.md)
|
||||
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Testnet Participation](testnet-participation.md)
|
||||
- [Testnet Replicator](testnet-replicator.md)
|
||||
- [Example: Web Wallet](webwallet.md)
|
||||
|
||||
- [Programming Model](programs.md)
|
||||
@ -18,33 +16,32 @@
|
||||
- [Leader Rotation](leader-rotation.md)
|
||||
- [Fork Generation](fork-generation.md)
|
||||
- [Managing Forks](managing-forks.md)
|
||||
- [Turbine Block Propagation](turbine-block-propagation.md)
|
||||
- [Data Plane Fanout](data-plane-fanout.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)
|
||||
- [Staking Delegation and Rewards](stake-delegation-and-rewards.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)
|
||||
|
||||
- [API Reference](api-reference.md)
|
||||
- [Transaction](transaction-api.md)
|
||||
- [Instruction](instruction-api.md)
|
||||
- [Blockstreamer](blockstreamer.md)
|
||||
- [JSON RPC API](jsonrpc-api.md)
|
||||
- [JavaScript API](javascript-api.md)
|
||||
- [solana-wallet CLI](wallet.md)
|
||||
|
||||
- [Accepted Design Proposals](proposals.md)
|
||||
- [Proposed Architectural Changes](proposals.md)
|
||||
- [Ledger Replication](ledger-replication-to-implement.md)
|
||||
- [Secure Vote Signing](vote-signing-to-implement.md)
|
||||
- [Staking Rewards](staking-rewards.md)
|
||||
- [Fork Selection](fork-selection.md)
|
||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||
- [Persistent Account Storage](persistent-account-storage.md)
|
||||
- [Leader to Leader Transition](leader-leader-transition.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)
|
||||
@ -56,24 +53,7 @@
|
||||
- [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)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Embedding the Move Langauge](embedding-move.md)
|
||||
|
@ -4,7 +4,7 @@ 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).
|
||||
described in [fork selection](fork-selection.md).
|
||||
|
||||
## Current Design
|
||||
|
||||
@ -50,11 +50,12 @@ 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.
|
||||
Locktower is the proposed [fork selection](fork-selection.md) 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 locktower. Vote instructions
|
||||
would need to reference a global locktower account so that it can track
|
||||
cross-block state.
|
||||
|
||||
## Challenges
|
||||
|
||||
|
@ -12,7 +12,7 @@ To run a blockstreamer, include the argument `no-signer` and (optional)
|
||||
`blockstream` socket location:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
|
||||
$ ./multinode-demo/fullnode-x.sh --no-signer --blockstream <SOCKET>
|
||||
```
|
||||
|
||||
The stream will output a series of JSON objects:
|
||||
|
@ -20,7 +20,7 @@ least amount of internal plumbing exposed to the test.
|
||||
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
|
||||
Each node in the cluster is configured with a `fullnode::FullnodeConfig` 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.
|
||||
@ -51,28 +51,28 @@ 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;
|
||||
use crate::gossip_service::discover;
|
||||
|
||||
// Discover the cluster over a few seconds.
|
||||
let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
|
||||
let cluster_nodes = discover(&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`.
|
||||
`fullnode::FullnodeConfig`.
|
||||
|
||||
For example:
|
||||
|
||||
```rust,ignore
|
||||
let mut validator_config = ValidatorConfig::default();
|
||||
validator_config.rpc_config.enable_fullnode_exit = true;
|
||||
let mut fullnode_config = FullnodeConfig::default();
|
||||
fullnode_config.rpc_config.enable_fullnode_exit = true;
|
||||
let local = LocalCluster::new_with_config(
|
||||
num_nodes,
|
||||
10_000,
|
||||
100,
|
||||
&validator_config
|
||||
&fullnode_config
|
||||
);
|
||||
```
|
||||
|
||||
@ -86,9 +86,9 @@ 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;
|
||||
let mut fullnode_config = FullnodeConfig::default();
|
||||
fullnode_config.rpc_config.enable_rpc_gossip_push = true;
|
||||
fullnode_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
|
||||
```
|
||||
|
||||
Wire the RPCs and write a new test:
|
||||
@ -99,10 +99,10 @@ pub fn test_large_invalid_gossip_nodes(
|
||||
funding_keypair: &Keypair,
|
||||
num_nodes: usize,
|
||||
) {
|
||||
let cluster = discover_nodes(&entry_point_info, num_nodes);
|
||||
let cluster = discover(&entry_point_info, num_nodes);
|
||||
|
||||
// Poison the cluster.
|
||||
let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
let mut 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()
|
||||
@ -112,7 +112,7 @@ pub fn test_large_invalid_gossip_nodes(
|
||||
|
||||
// Force refresh of the active set.
|
||||
for node in &cluster {
|
||||
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
let mut client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
|
||||
client.gossip_refresh_active_set();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
84
book/src/data-plane-fanout.md
Normal file
84
book/src/data-plane-fanout.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Data Plane Fanout
|
||||
|
||||
A Solana cluster uses a multi-layer mechanism called *data plane fanout* to
|
||||
broadcast transaction blobs to all nodes in a very quick and efficient manner.
|
||||
In order to establish the fanout, the cluster divides itself into small
|
||||
collections of nodes, called *neighborhoods*. Each node is responsible for
|
||||
sharing any data it receives with the other nodes in its neighborhood, as well
|
||||
as propagating the data on to a small set of nodes in other neighborhoods.
|
||||
|
||||
During its slot, the leader node distributes blobs between the validator nodes
|
||||
in one neighborhood (layer 1). Each validator shares its data within its
|
||||
neighborhood, but also retransmits the blobs to one node in each of multiple
|
||||
neighborhoods in the next layer (layer 2). The layer-2 nodes each share their
|
||||
data with their neighborhood peers, and retransmit to nodes in the next layer,
|
||||
etc, until all nodes in the cluster have received all the blobs.
|
||||
|
||||
<img alt="Two layer cluster" src="img/data-plane.svg" class="center"/>
|
||||
|
||||
## Neighborhood Assignment - Weighted Selection
|
||||
|
||||
In order for data plane fanout to work, the entire cluster must agree on how the
|
||||
cluster is divided into neighborhoods. To achieve this, all the recognized
|
||||
validator nodes (the TVU peers) are sorted by stake and stored in a list. This
|
||||
list is then indexed in different ways to figure out neighborhood boundaries and
|
||||
retransmit peers. For example, the leader will simply select the first nodes to
|
||||
make up layer 1. These will automatically be the highest stake holders, allowing
|
||||
the heaviest votes to come back to the leader first. Layer-1 and lower-layer
|
||||
nodes use the same logic to find their neighbors and lower layer peers.
|
||||
|
||||
## Layer and Neighborhood Structure
|
||||
|
||||
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
|
||||
nodes. If this layer 1 is smaller than the number of nodes in the cluster, then
|
||||
the data plane fanout mechanism adds layers below. Subsequent layers follow
|
||||
these constraints to determine layer-capacity: Each neighborhood contains
|
||||
`NEIGHBORHOOD_SIZE` nodes and each layer may have up to `DATA_PLANE_FANOUT/2`
|
||||
neighborhoods.
|
||||
|
||||
As mentioned above, each node in a layer only has to broadcast its blobs to its
|
||||
neighbors and to exactly 1 node in each next-layer neighborhood, instead of to
|
||||
every TVU peer in the cluster. In the default mode, each layer contains
|
||||
`DATA_PLANE_FANOUT/2` neighborhoods. The retransmit mechanism also supports a
|
||||
second, `grow`, mode of operation that squares the number of neighborhoods
|
||||
allowed each layer. This dramatically reduces the number of layers needed to
|
||||
support a large cluster, but can also have a negative impact on the network
|
||||
pressure on each node in the lower layers. A good way to think of the default
|
||||
mode (when `grow` is disabled) is to imagine it as chain of layers, where the
|
||||
leader sends blobs to layer-1 and then layer-1 to layer-2 and so on, the `layer
|
||||
capacities` remain constant, so all layers past layer-2 will have the same
|
||||
number of nodes until the whole cluster is covered. When `grow` is enabled, this
|
||||
becomes a traditional fanout where layer-3 will have the square of the number of
|
||||
nodes in layer-2 and so on.
|
||||
|
||||
#### Configuration Values
|
||||
|
||||
`DATA_PLANE_FANOUT` - Determines the size of layer 1. Subsequent
|
||||
layers have `DATA_PLANE_FANOUT/2` neighborhoods when `grow` is inactive.
|
||||
|
||||
`NEIGHBORHOOD_SIZE` - The number of nodes allowed in a neighborhood.
|
||||
Neighborhoods will fill to capacity before new ones are added, i.e if a
|
||||
neighborhood isn't full, it _must_ be the last one.
|
||||
|
||||
`GROW_LAYER_CAPACITY` - Whether or not retransmit should be behave like a
|
||||
_traditional fanout_, i.e if each additional layer should have growing
|
||||
capacities. When this mode is disabled (default), all layers after layer 1 have
|
||||
the same capacity, keeping the network pressure on all nodes equal.
|
||||
|
||||
Currently, configuration is set when the cluster is launched. In the future,
|
||||
these parameters may be hosted on-chain, allowing modification on the fly as the
|
||||
cluster sizes change.
|
||||
|
||||
## Neighborhoods
|
||||
|
||||
The following diagram shows how two neighborhoods in different layers interact.
|
||||
What this diagram doesn't capture is that each neighbor actually receives
|
||||
blobs from one validator per neighborhood above it. This means that, to
|
||||
cripple a neighborhood, enough nodes (erasure codes +1 per neighborhood) from
|
||||
the layer above need to fail. Since multiple neighborhoods exist in the upper
|
||||
layer and a node will receive blobs from a node in each of those neighborhoods,
|
||||
we'd need a big network failure in the upper layers to end up with incomplete
|
||||
data.
|
||||
|
||||
<img alt="Inner workings of a neighborhood"
|
||||
src="img/data-plane-neighborhood.svg" class="center"/>
|
@ -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`
|
||||
@ -76,7 +76,7 @@ beyond a certain *age*.
|
||||
|
||||
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,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.
|
@ -8,7 +8,7 @@ These protocol-based rewards, to be distributed to participating validation and
|
||||
|
||||
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 funding of the mining pool through a pre-dedicated portion of transaction fees 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. 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.
|
||||
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. 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. 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/solana_economic_design.png" alt="== Solana Economic Design Diagram ==" width="800"/></p>
|
||||
|
@ -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.
|
@ -55,7 +55,7 @@ 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
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Tower BFT
|
||||
# Fork Selection
|
||||
|
||||
This design describes Solana's *Tower BFT* algorithm. It addresses the
|
||||
following problems:
|
||||
This design describes a *Fork Selection* algorithm. It addresses the following
|
||||
problems:
|
||||
|
||||
* Some forks may not end up accepted by the super-majority of the cluster, and
|
||||
voters need to recover from voting on such forks.
|
@ -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
|
@ -47,8 +47,8 @@ nodes are started
|
||||
$ 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 +69,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 +86,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 +99,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 +145,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,7 +161,7 @@ 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)
|
||||
|
@ -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.
|
||||
@ -116,8 +116,8 @@ Just like *pull message*, nodes are selected into the active set based on weight
|
||||
|
||||
## 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:
|
||||
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
|
||||
|
@ -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.16.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.16.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.16.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,13 +1,13 @@
|
||||
# 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 it's useful, 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
|
||||
|
@ -24,20 +24,9 @@ Methods
|
||||
* [confirmTransaction](#confirmtransaction)
|
||||
* [getAccountInfo](#getaccountinfo)
|
||||
* [getBalance](#getbalance)
|
||||
* [getClusterNodes](#getclusternodes)
|
||||
* [getEpochInfo](#getepochinfo)
|
||||
* [getLeaderSchedule](#getleaderschedule)
|
||||
* [getProgramAccounts](#getprogramaccounts)
|
||||
* [getRecentBlockhash](#getrecentblockhash)
|
||||
* [getSignatureStatus](#getsignaturestatus)
|
||||
* [getSlotLeader](#getslotleader)
|
||||
* [getSlotsPerSegment](#getslotspersegment)
|
||||
* [getStorageTurn](#getstorageturn)
|
||||
* [getStorageTurnRate](#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](#gettransactioncount)
|
||||
* [getTotalSupply](#gettotalsupply)
|
||||
* [getEpochVoteAccounts](#getepochvoteaccounts)
|
||||
* [requestAirdrop](#requestairdrop)
|
||||
* [sendTransaction](#sendtransaction)
|
||||
* [startSubscriptionChannel](#startsubscriptionchannel)
|
||||
@ -102,32 +91,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
|
||||
@ -150,112 +113,40 @@ 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
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getEpochInfo
|
||||
Returns information about the current epoch
|
||||
|
||||
##### 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
|
||||
|
||||
##### 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}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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` - a the account Pubkey as base-58 encoded string
|
||||
and a JSON object, with the following sub fields:
|
||||
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)
|
||||
* `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":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' 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":[["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}
|
||||
{"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],"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}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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.
|
||||
Returns a recent block hash from the ledger
|
||||
|
||||
##### 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
|
||||
@ -263,7 +154,7 @@ An array consisting of
|
||||
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}
|
||||
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
|
||||
```
|
||||
|
||||
---
|
||||
@ -277,10 +168,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
|
||||
@ -291,108 +184,7 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"jsonrpc":"2.0","result":"SignatureNotFound","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
|
||||
|
||||
@ -413,51 +205,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
|
||||
---
|
||||
|
||||
### getTotalSupply
|
||||
Returns the current total supply in Lamports
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `integer` - Total supply, 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
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":10126,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getEpochVoteAccounts
|
||||
Returns the account info and associated stake for all the voting accounts in the current epoch.
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
* `votePubkey` - Vote account public key, as base-58 encoded string
|
||||
* `nodePubkey` - Node public key, as base-58 encoded string
|
||||
* `stake` - the stake, in lamports, delegated to this vote account
|
||||
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"commission":0,"nodePubkey":"Et2RaZJdJRTzTkodUwiHr4H6sLkVmijBFv8tkd7oSSFY","stake":42,"votePubkey":"B4CdWq3NBSoH2wYsVE1CaZSWPo2ZtopE4SJipQhZ3srF"}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### requestAirdrop
|
||||
Requests an airdrop of lamports to a Pubkey
|
||||
|
||||
@ -503,14 +250,6 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
@ -520,8 +259,6 @@ 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)
|
||||
@ -531,15 +268,13 @@ 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],"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}}
|
||||
```
|
||||
|
||||
---
|
||||
@ -570,8 +305,6 @@ 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)
|
||||
@ -581,8 +314,6 @@ for a given account owned by the program changes
|
||||
// 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}
|
||||
```
|
||||
@ -622,8 +353,6 @@ 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)
|
||||
@ -633,8 +362,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}
|
||||
```
|
||||
|
@ -45,7 +45,7 @@ The upsides compared to guards:
|
||||
* The timeout is not fixed.
|
||||
|
||||
* The timeout is local to the leader, and therefore can be clever. The leader's
|
||||
heuristic can take into account turbine performance.
|
||||
heuristic can take into account avalanche performance.
|
||||
|
||||
* This design doesn't require a ledger hard fork to update.
|
||||
|
||||
|
@ -96,7 +96,7 @@ ends up scheduled for the first two epochs because the leader schedule is also
|
||||
generated at slot 0 for the next epoch. The length of the first two epochs can
|
||||
be specified in the genesis block as well. The minimum length of the first
|
||||
epochs must be greater than or equal to the maximum rollback depth as defined in
|
||||
[Tower BFT](tower-bft.md).
|
||||
[fork selection](fork-selection.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
|
@ -57,7 +57,7 @@ Forwarding is preferred, as it would minimize network congestion, allowing the
|
||||
cluster to advertise higher TPS capacity.
|
||||
|
||||
|
||||
## Validator Loop
|
||||
## Fullnode Loop
|
||||
|
||||
The PoH Recorder manages the transition between modes. Once a ledger is
|
||||
replayed, the validator can run until the recorder indicates it should be
|
||||
|
@ -2,12 +2,6 @@
|
||||
|
||||
Replication behavior yet to be implemented.
|
||||
|
||||
### Storage epoch
|
||||
|
||||
The storage epoch should be the number of slots which results in around 100GB-1TB of
|
||||
ledger to be generated for replicators to store. Replicators will start storing ledger
|
||||
when a given fork has a high probability of not being rolled back.
|
||||
|
||||
### Validator behavior
|
||||
|
||||
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
|
||||
@ -43,100 +37,3 @@ transacation proves the validator incorrectly validated a fake storage proof.
|
||||
The replicator is rewarded and the validator's staking balance is slashed or
|
||||
frozen.
|
||||
|
||||
### Storage proof contract logic
|
||||
|
||||
Each replicator and validator will have their own storage account. The validator's
|
||||
account would be separate from their gossip id similiar to their vote account.
|
||||
These should be implemented as two programs one which handles the validator as the keysigner
|
||||
and one for the replicator. In that way when the programs reference other accounts, they
|
||||
can check the program id to ensure it is a validator or replicator account they are
|
||||
referencing.
|
||||
|
||||
#### SubmitMiningProof
|
||||
```rust,ignore
|
||||
SubmitMiningProof {
|
||||
slot: u64,
|
||||
sha_state: Hash,
|
||||
signature: Signature,
|
||||
};
|
||||
keys = [replicator_keypair]
|
||||
```
|
||||
Replicators create these after mining their stored ledger data for a certain hash value.
|
||||
The slot is the end slot of the segment of ledger they are storing, the sha\_state
|
||||
the result of the replicator using the hash function to sample their encrypted ledger segment.
|
||||
The signature is the signature that was created when they signed a PoH value for the
|
||||
current storage epoch. The list of proofs from the current storage epoch should be saved
|
||||
in the account state, and then transfered to a list of proofs for the previous epoch when
|
||||
the epoch passes. In a given storage epoch a given replicator should only submit proofs
|
||||
for one segment.
|
||||
|
||||
The program should have a list of slots which are valid storage mining slots.
|
||||
This list should be maintained by keeping track of slots which are rooted slots in which a significant
|
||||
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
|
||||
number of slots would be added to this set. The program should check that the slot is in this set. The set can
|
||||
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
|
||||
|
||||
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
|
||||
the previous storage epoch PoH value.
|
||||
|
||||
#### ProofValidation
|
||||
```rust,ignore
|
||||
ProofValidation {
|
||||
proof_mask: Vec<ProofStatus>,
|
||||
}
|
||||
keys = [validator_keypair, replicator_keypair(s) (unsigned)]
|
||||
```
|
||||
A validator will submit this transaction to indicate that a set of proofs for a given
|
||||
segment are valid/not-valid or skipped where the validator did not look at it. The
|
||||
keypairs for the replicators that it looked at should be referenced in the keys so the program
|
||||
logic can go to those accounts and see that the proofs are generated in the previous epoch. The
|
||||
sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by
|
||||
the validator according to the logic outlined in the validator behavior of sampling.
|
||||
|
||||
The included replicator keys will indicate the the storage samples which are being referenced; the
|
||||
length of the proof\_mask should be verified against the set of storage proofs in the referenced
|
||||
replicator account(s), and should match with the number of proofs submitted in the previous storage
|
||||
epoch in the state of said replicator account.
|
||||
|
||||
#### ClaimStorageReward
|
||||
```rust,ignore
|
||||
ClaimStorageReward {
|
||||
}
|
||||
keys = [validator_keypair or replicator_keypair, validator/replicator_keypairs (unsigned)]
|
||||
```
|
||||
Replicators and validators will use this transaction to get paid tokens from a program state
|
||||
where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where
|
||||
proofs have been submitted and validated and there are no ChallengeProofValidations referencing
|
||||
those proofs. For a validator, it should reference the replicator keypairs to which it has validated
|
||||
proofs in the relevant epoch. And for a replicator it should reference validator keypairs for which it
|
||||
has validated and wants to be rewarded.
|
||||
|
||||
#### ChallengeProofValidation
|
||||
```rust,ignore
|
||||
ChallengeProofValidation {
|
||||
proof_index: u64,
|
||||
hash_seed_value: Vec<u8>,
|
||||
}
|
||||
keys = [replicator_keypair, validator_keypair]
|
||||
```
|
||||
|
||||
This transaction is for catching lazy validators who are not doing the work to validate proofs.
|
||||
A replicator will submit this transaction when it sees a validator has approved a fake SubmitMiningProof
|
||||
transaction. Since the replicator is a light client not looking at the full chain, it will have to ask
|
||||
a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for
|
||||
a certain segment in the previous storage epoch. The program will look in the validator account
|
||||
state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and
|
||||
see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so,
|
||||
then it will save the challenge to the list of challenges that it has in its state.
|
||||
|
||||
#### AdvertiseStorageRecentBlockhash
|
||||
```rust,ignore
|
||||
AdvertiseStorageRecentBlockhash {
|
||||
hash: Hash,
|
||||
slot: u64,
|
||||
}
|
||||
```
|
||||
|
||||
Validators and replicators will submit this to indicate that a new storage epoch has passed and that the
|
||||
storage proofs which are current proofs should now be for the previous epoch. Other transactions should
|
||||
check to see that the epoch that they are referencing is accurate according to current chain state.
|
||||
|
@ -1,18 +1,19 @@
|
||||
# Ledger Replication
|
||||
|
||||
At full capacity on a 1gbps network solana will generate 4 petabytes of data
|
||||
per year. To prevent the network from centralizing around validators that have
|
||||
per year. To prevent the network from centralizing around full nodes that have
|
||||
to store the full data set this protocol proposes a way for mining nodes to
|
||||
provide storage capacity for pieces of the data.
|
||||
provide storage capacity for pieces of the network.
|
||||
|
||||
The basic idea to Proof of Replication is encrypting a dataset with a public
|
||||
symmetric key using CBC encryption, then hash the encrypted dataset. The main
|
||||
problem with the naive approach is that a dishonest storage node can stream the
|
||||
encryption and delete the data as it's hashed. The simple solution is to periodically
|
||||
regenerate the hash based on a signed PoH value. This ensures that all the data is present
|
||||
during the generation of the proof and it also requires validators to have the
|
||||
entirety of the encrypted data present for verification of every proof of every identity.
|
||||
So the space required to validate is `number_of_proofs * data_size`
|
||||
encryption and delete the data as its hashed. The simple solution is to force
|
||||
the hash to be done on the reverse of the encryption, or perhaps with a random
|
||||
order. This ensures that all the data is present during the generation of the
|
||||
proof and it also requires the validator to have the entirety of the encrypted
|
||||
data present for verification of every proof of every identity. So the space
|
||||
required to validate is `number_of_proofs * data_size`
|
||||
|
||||
## Optimization with PoH
|
||||
|
||||
@ -28,12 +29,13 @@ core. The total space required for verification is `1_ledger_segment +
|
||||
## Network
|
||||
|
||||
Validators for PoRep are the same validators that are verifying transactions.
|
||||
If a replicator can prove that a validator verified a fake PoRep, then the
|
||||
validator will not receive a reward for that storage epoch.
|
||||
They have some stake that they have put up as collateral that ensures that
|
||||
their work is honest. If you can prove that a validator verified a fake PoRep,
|
||||
then the validators stake can be slashed.
|
||||
|
||||
Replicators are specialized *light clients*. They download a part of the
|
||||
ledger (a.k.a Segment) and store it, and provide PoReps of storing the ledger.
|
||||
For each verified PoRep replicators earn a reward of sol from the mining pool.
|
||||
Replicators are specialized *light clients*. They download a part of the ledger
|
||||
and store it, and provide PoReps of storing the ledger. For each verified PoRep
|
||||
replicators earn a reward of sol from the mining pool.
|
||||
|
||||
## Constraints
|
||||
|
||||
@ -51,10 +53,11 @@ changes to determine what rate it can validate storage proofs.
|
||||
|
||||
### Constants
|
||||
|
||||
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
|
||||
1. NUM\_STORAGE\_ENTRIES: Number of entries in a segment of ledger data. The
|
||||
unit of storage for a replicator.
|
||||
2. NUM\_KEY\_ROTATION\_SEGMENTS: Number of segments after which replicators
|
||||
regenerate their encryption keys and select a new dataset to store.
|
||||
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
|
||||
key generation for the section of ledger just generated and the rotation of
|
||||
another key in the set.
|
||||
3. NUM\_STORAGE\_PROOFS: Number of storage proofs required for a storage proof
|
||||
claim to be successfully rewarded.
|
||||
4. RATIO\_OF\_FAKE\_PROOFS: Ratio of fake proofs to real proofs that a storage
|
||||
@ -63,108 +66,75 @@ mining proof claim has to contain to be valid for a reward.
|
||||
proof.
|
||||
6. NUM\_CHACHA\_ROUNDS: Number of encryption rounds performed to generate
|
||||
encrypted state.
|
||||
7. NUM\_SLOTS\_PER\_TURN: Number of slots that define a single storage epoch or
|
||||
a "turn" of the PoRep game.
|
||||
|
||||
### Validator behavior
|
||||
|
||||
1. Validators join the network and begin looking for replicator accounts at each
|
||||
storage epoch/turn boundary.
|
||||
2. Every turn, Validators sign the PoH value at the boundary and use that signature
|
||||
to randomly pick proofs to verify from each storage account found in the turn boundary.
|
||||
This signed value is also submitted to the validator's storage account and will be used by
|
||||
replicators at a later stage to cross-verify.
|
||||
3. Every `NUM_SLOTS_PER_TURN` slots the validator advertises the PoH value. This is value
|
||||
is also served to Replicators via RPC interfaces.
|
||||
4. For a given turn N, all validations get locked out until turn N+3 (a gap of 2 turn/epoch).
|
||||
At which point all validations during that turn are available for reward collection.
|
||||
5. Any incorrect validations will be marked during the turn in between.
|
||||
|
||||
1. Validator joins the network and submits a storage validation capacity
|
||||
transaction which tells the network how many proofs it can process in a given
|
||||
period defined by NUM\_KEY\_ROTATION\_TICKS.
|
||||
2. Every NUM\_KEY\_ROTATION\_TICKS the validator stores the PoH value at that
|
||||
height.
|
||||
3. Validator generates a storage proof confirmation transaction.
|
||||
4. The storage proof confirmation transaction is integrated into the ledger.
|
||||
6. Validator responds to RPC interfaces for what the last storage epoch PoH
|
||||
value is and its entry\_height.
|
||||
|
||||
### Replicator behavior
|
||||
|
||||
1. Since a replicator is somewhat of a light client and not downloading all the
|
||||
ledger data, they have to rely on other validators and replicators for information.
|
||||
Any given validator may or may not be malicious and give incorrect information, although
|
||||
there are not any obvious attack vectors that this could accomplish besides having the
|
||||
replicator do extra wasted work. For many of the operations there are a number of options
|
||||
depending on how paranoid a replicator is:
|
||||
ledger data, they have to rely on other full nodes (validators) for
|
||||
information. Any given validator may or may not be malicious and give incorrect
|
||||
information, although there are not any obvious attack vectors that this could
|
||||
accomplish besides having the replicator do extra wasted work. For many of the
|
||||
operations there are a number of options depending on how paranoid a replicator
|
||||
is:
|
||||
- (a) replicator can ask a validator
|
||||
- (b) replicator can ask multiple validators
|
||||
- (c) replicator can ask other replicators
|
||||
- (d) replicator can subscribe to the full transaction stream and generate
|
||||
the information itself (assuming the slot is recent enough)
|
||||
- (e) replicator can subscribe to an abbreviated transaction stream to
|
||||
generate the information itself (assuming the slot is recent enough)
|
||||
2. A replicator obtains the PoH hash corresponding to the last turn with its slot.
|
||||
- (c) replicator can subscribe to the full transaction stream and generate
|
||||
the information itself
|
||||
- (d) replicator can subscribe to an abbreviated transaction stream to
|
||||
generate the information itself
|
||||
2. A replicator obtains the PoH hash corresponding to the last key rotation
|
||||
along with its entry\_height.
|
||||
3. The replicator signs the PoH hash with its keypair. That signature is the
|
||||
seed used to pick the segment to replicate and also the encryption key. The
|
||||
replicator mods the signature with the slot to get which segment to
|
||||
replicator mods the signature with the entry\_height to get which segment to
|
||||
replicate.
|
||||
4. The replicator retrives the ledger by asking peer validators and
|
||||
replicators. See 6.5.
|
||||
5. The replicator then encrypts that segment with the key with chacha algorithm
|
||||
in CBC mode with `NUM_CHACHA_ROUNDS` of encryption.
|
||||
6. The replicator initializes a chacha rng with the a signed recent PoH value as
|
||||
in CBC mode with NUM\_CHACHA\_ROUNDS of encryption.
|
||||
6. The replicator initializes a chacha rng with the signature from step 2 as
|
||||
the seed.
|
||||
7. The replicator generates `NUM_STORAGE_SAMPLES` samples in the range of the
|
||||
7. The replicator generates NUM\_STORAGE\_SAMPLES samples in the range of the
|
||||
entry size and samples the encrypted segment with sha256 for 32-bytes at each
|
||||
offset value. Sampling the state should be faster than generating the encrypted
|
||||
segment.
|
||||
8. The replicator sends a PoRep proof transaction which contains its sha state
|
||||
at the end of the sampling operation, its seed and the samples it used to the
|
||||
current leader and it is put onto the ledger.
|
||||
9. During a given turn the replicator should submit many proofs for the same segment
|
||||
and based on the `RATIO_OF_FAKE_PROOFS` some of those proofs must be fake.
|
||||
10. As the PoRep game enters the next turn, the replicator must submit a
|
||||
transaction with the mask of which proofs were fake during the last turn. This
|
||||
transaction will define the rewards for both replicators and validators.
|
||||
11. Finally for a turn N, as the PoRep game enters turn N + 3, replicator's proofs for
|
||||
turn N will be counted towards their rewards.
|
||||
|
||||
|
||||
### The PoRep Game
|
||||
|
||||
The Proof of Replication game has 4 primary stages. For each "turn" multiple PoRep
|
||||
games can be in progress but each in a different stage.
|
||||
|
||||
The 4 stages of the PoRep Game are as follows:
|
||||
|
||||
1. Proof submission stage
|
||||
- Replicators: submit as many proofs as possible during this stage
|
||||
- Validators: No-op
|
||||
2. Proof verification stage
|
||||
- Replicators: No-op
|
||||
- Validators: Select replicators and verify their proofs from the previous turn
|
||||
3. Proof challenge stage
|
||||
- Replicators: Submit the proof mask with justifications (for fake proofs submitted 2 turns ago)
|
||||
- Validators: No-op
|
||||
4. Reward collection stage
|
||||
- Replicators: Collect rewards for 3 turns ago
|
||||
- Validators: Collect rewards for 3 turns ago
|
||||
|
||||
|
||||
For each turn of the PoRep game, both Validators and Replicators evaluate each
|
||||
stage. The stages are run as separate transactions on the storage program.
|
||||
|
||||
### Finding who has a given block of ledger
|
||||
|
||||
1. Validators monitor the turns in the PoRep game and look at the rooted bank
|
||||
at turn boundaries for any proofs.
|
||||
2. Validators maintain a map of ledger segments and corresponding replicator public keys.
|
||||
The map is updated when a Validator processes a replicator's proofs for a segment.
|
||||
The validator provides an RPC interface to access the this map. Using this API, clients
|
||||
can map a segment to a replicator's network address (correlating it via cluster_info table).
|
||||
The clients can then send repair requests to the replicator to retrieve segments.
|
||||
3. Validators would need to invalidate this list every N turns.
|
||||
1. Validators monitor the transaction stream for storage mining proofs, and
|
||||
keep a mapping of ledger segments by entry\_height to public keys. When it sees
|
||||
a storage mining proof it updates this mapping and provides an RPC interface
|
||||
which takes an entry\_height and hands back a list of public keys. The client
|
||||
then looks up in their cluster\_info table to see which network address that
|
||||
corresponds to and sends a repair request to retrieve the necessary blocks of
|
||||
ledger.
|
||||
2. Validators would need to prune this list which it could do by periodically
|
||||
looking at the oldest entries in its mappings and doing a network query to see
|
||||
if the storage host is still serving the first entry.
|
||||
|
||||
## Sybil attacks
|
||||
|
||||
For any random seed, we force everyone to use a signature that is derived from
|
||||
a PoH hash at the turn boundary. Everyone uses the same count, so the same PoH
|
||||
hash is signed by every participant. The signatures are then each cryptographically
|
||||
tied to the keypair, which prevents a leader from grinding on the resulting
|
||||
value for more than 1 identity.
|
||||
a PoH hash. Everyone must use the same count, so the same PoH hash is signed by
|
||||
every participant. The signatures are then each cryptographically tied to the
|
||||
keypair, which prevents a leader from grinding on the resulting value for more
|
||||
than 1 identity.
|
||||
|
||||
Since there are many more client identities then encryption identities, we need
|
||||
to split the reward for multiple clients, and prevent Sybil attacks from
|
||||
@ -185,7 +155,8 @@ the network can reward long lived client identities more than new ones.
|
||||
showing the initial state for the hash.
|
||||
- If a validator marks real proofs as fake, no on-chain computation can be done
|
||||
to distinguish who is correct. Rewards would have to rely on the results from
|
||||
multiple validators to catch bad actors and replicators from being denied rewards.
|
||||
multiple validators in a stake-weighted fashion to catch bad actors and
|
||||
replicators from being locked out of the network.
|
||||
- Validator stealing mining proof results for itself. The proofs are derived
|
||||
from a signature from a replicator, since the validator does not know the
|
||||
private key used to generate the encryption key, it cannot be the generator of
|
||||
|
@ -1,216 +0,0 @@
|
||||
# Stake Delegation and Reward
|
||||
|
||||
This design proposal focuses on the software architecture for the on-chain
|
||||
voting and staking programs. Incentives for staking is covered in [staking
|
||||
rewards](staking-rewards.md).
|
||||
|
||||
The current architecture requires a vote for each delegated stake from the
|
||||
validator, and therefore does not scale to allow replicator clients to
|
||||
automatically delegate their rewards.
|
||||
|
||||
The design proposes a new set of programs for voting and stake delegation, The
|
||||
proposed programs allow many stake accounts to passively earn rewards with a
|
||||
single validator vote without permission or active involvement from the
|
||||
validator.
|
||||
|
||||
## Current Design Problems
|
||||
|
||||
In the current design each staker creates their own VoteState, and assigns a
|
||||
**delegate** in the VoteState that can submit votes. Since the validator has to
|
||||
actively vote for each stake delegated to it, validators can censor stakes by
|
||||
not voting for them.
|
||||
|
||||
The number of votes is equal to the number of stakers, and not the number of
|
||||
validators. Replicator clients are expected to delegate their replication
|
||||
rewards as they are earned, and therefore the number of stakes is expected to be
|
||||
large compared to the number of validators in a long running cluster.
|
||||
|
||||
## Proposed changes to the current design.
|
||||
|
||||
The general idea is that instead of the staker, the validator will own the
|
||||
VoteState program. In this proposal the VoteState program is there to track
|
||||
validator votes, count validator generated credits and to provide any
|
||||
additional validator specific state. The VoteState program is not aware of any
|
||||
stakes delegated to it, and has no staking weight.
|
||||
|
||||
The rewards generated are proportional to the amount of lamports staked. In
|
||||
this proposal stake state is stored as part of the StakeState program. This
|
||||
program is owned by the staker only. Lamports stored in this program are the
|
||||
stake. Unlike the current design, this program contains a new field to indicate
|
||||
which VoteState program the stake is delegated to.
|
||||
|
||||
### VoteState
|
||||
|
||||
VoteState is the current state of all the votes the **delegate** has submitted
|
||||
to the bank. VoteState contains the following state information:
|
||||
|
||||
* votes - The submitted votes data structure.
|
||||
|
||||
* credits - The total number of rewards this vote program has generated over its
|
||||
lifetime.
|
||||
|
||||
* root\_slot - The last slot to reach the full lockout commitment necessary for
|
||||
rewards.
|
||||
|
||||
* commission - The commission taken by this VoteState for any rewards claimed by
|
||||
staker's StakeState accounts. This is the percentage ceiling of the reward.
|
||||
|
||||
* Account::lamports - The accumulated lamports from the commission. These do not
|
||||
count as stakes.
|
||||
|
||||
* `authorized_vote_signer` - Only this identity is authorized to submit votes, and
|
||||
this field can only modified by this entity
|
||||
|
||||
### VoteInstruction::Initialize
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is initialized to `account[0]`
|
||||
other VoteState members defaulted
|
||||
|
||||
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
|
||||
signed by Pubkey
|
||||
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of two forms, StakeState::Stake and StakeState::MiningPool.
|
||||
|
||||
### StakeState::Stake
|
||||
|
||||
Stake is the current delegation preference of the **staker**. Stake
|
||||
contains the following state information:
|
||||
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
||||
delegated to.
|
||||
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the
|
||||
program.
|
||||
|
||||
* `stake` - The actual activated stake.
|
||||
|
||||
* Account::lamports - Lamports available for staking, including any earned as rewards.
|
||||
|
||||
|
||||
### StakeState::MiningPool
|
||||
|
||||
There are two approaches to the mining pool. The bank could allow the
|
||||
StakeState program to bypass the token balance check, or a program representing
|
||||
the mining pool could run on the network. To avoid a single network wide lock,
|
||||
the pool can be split into several mining pools. This design focuses on using a
|
||||
StakeState::MiningPool as the cluster wide mining pools.
|
||||
|
||||
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
|
||||
tokens stored as `Account::lamports`.
|
||||
|
||||
The stakes and the MiningPool are accounts that are owned by the same `Stake`
|
||||
program.
|
||||
|
||||
### StakeInstruction::DelegateStake(stake)
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance.
|
||||
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`.
|
||||
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`
|
||||
`StakeState::Stake::stake` is initialized to `stake`, as long as it's less than account[0].lamports
|
||||
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
|
||||
### StakeInstruction::RedeemVoteCredits
|
||||
|
||||
The VoteState program and the StakeState programs maintain a lifetime counter
|
||||
of total rewards generated and claimed. Therefore an explicit `Clear`
|
||||
instruction is not necessary. When claiming rewards, the total lamports
|
||||
deposited into the StakeState and as validator commission is proportional to
|
||||
`VoteState::credits - StakeState::credits_observed`.
|
||||
|
||||
|
||||
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
|
||||
reward.
|
||||
* `account[1]` - RW - The StakeState::Stake instance that is redeeming votes
|
||||
credits.
|
||||
* `account[2]` - R - The VoteState instance, must be the same as
|
||||
`StakeState::voter_pubkey`
|
||||
|
||||
Reward is payed out for the difference between `VoteState::credits` to
|
||||
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
|
||||
`VoteState::credits`. The commission is deposited into the `VoteState` token
|
||||
balance, and the reward is deposited to the `StakeState::Stake` token balance. The
|
||||
reward and the commission is weighted by the `StakeState::lamports` divided by total lamports staked.
|
||||
|
||||
The Staker or the owner of the Stake program sends a transaction with this
|
||||
instruction to claim the reward.
|
||||
|
||||
Any random MiningPool can be used to redeem the credits.
|
||||
|
||||
```rust,ignore
|
||||
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
|
||||
stake_state.credits_observed = vote_state.credits;
|
||||
```
|
||||
|
||||
`credits_to_claim` is used to compute the reward and commission, and
|
||||
`StakeState::Stake::credits_observed` is updated to the latest
|
||||
`VoteState::credits` value.
|
||||
|
||||
### Collecting network fees into the MiningPool
|
||||
|
||||
At the end of the block, before the bank is frozen, but after it processed all
|
||||
the transactions for the block, a virtual instruction is executed to collect
|
||||
the transaction fees.
|
||||
|
||||
* A portion of the fees are deposited into the leader's account.
|
||||
* A portion of the fees are deposited into the smallest StakeState::MiningPool
|
||||
account.
|
||||
|
||||
### Benefits
|
||||
|
||||
* Single vote for all the stakers.
|
||||
|
||||
* Clearing of the credit variable is not necessary for claiming rewards.
|
||||
|
||||
* Each delegated stake can claim its rewards independently.
|
||||
|
||||
* Commission for the work is deposited when a reward is claimed by the delegated
|
||||
stake.
|
||||
|
||||
This proposal would benefit from the `read-only` accounts proposal to allow for
|
||||
many rewards to be claimed concurrently.
|
||||
|
||||
## Passive Delegation
|
||||
|
||||
Any number of instances of StakeState::Stake programs can delegate to a single
|
||||
VoteState program without an interactive action from the identity controlling
|
||||
the VoteState program or submitting votes to the program.
|
||||
|
||||
The total stake allocated to a VoteState program can be calculated by the sum of
|
||||
all the StakeState programs that have the VoteState pubkey as the
|
||||
`StakeState::Stake::voter_pubkey`.
|
||||
|
||||
## Example Callflow
|
||||
|
||||
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
|
||||
|
||||
## Future work
|
||||
|
||||
Validators may want to split the stake delegated to them amongst many validator
|
||||
nodes since stake is used as weight in the network control and data planes. One
|
||||
way to implement this would be for the StakeState to delegate to a pool of
|
||||
validators instead of a single one.
|
||||
|
||||
Instead of a single `vote_pubkey` and `credits_observed` entry in the StakeState
|
||||
program, the program can be initialized with a vector of tuples.
|
||||
|
||||
```rust,ignore
|
||||
Voter {
|
||||
voter_pubkey: Pubkey,
|
||||
credits_observed: u64,
|
||||
weight: u8,
|
||||
}
|
||||
```
|
||||
|
||||
* voters: Vec<Voter> - Array of VoteState accounts that are voting rewards with
|
||||
this stake.
|
||||
|
||||
A StakeState program would claim a fraction of the reward from each voter in
|
||||
the `voters` array, and each voter would be delegated a fraction of the stake.
|
@ -1,29 +0,0 @@
|
||||
# Performance Metrics
|
||||
|
||||
Solana cluster performance is measured as average number of transactions per second
|
||||
that the network can sustain (TPS). And, how long it takes for a transaction to be
|
||||
confirmed by super majority of the cluster (Confirmation Time).
|
||||
|
||||
Each cluster node maintains various counters that are incremented on certain events.
|
||||
These counters are periodically uploaded to a cloud based database. Solana's metrics
|
||||
dashboard fetches these counters, and computes the performance metrics and displays
|
||||
it on the dashboard.
|
||||
|
||||
## TPS
|
||||
|
||||
The leader node's banking stage maintains a count of transactions that it recorded.
|
||||
The dashboard displays the count averaged over 2 second period in the TPS time series
|
||||
graph. The dashboard also shows per second mean, maximum and total TPS as a running
|
||||
counter.
|
||||
|
||||
## Confirmation Time
|
||||
|
||||
Each validator node maintains a list of active ledger forks that are visible to the node.
|
||||
A fork is considered to be frozen when the node has received and processed all entries
|
||||
corresponding to the fork. A fork is considered to be confirmed when it receives cumulative
|
||||
super majority vote, and when one of its children forks is frozen.
|
||||
|
||||
The node assigns a timestamp to every new fork, and computes the time it took to confirm
|
||||
the fork. This time is reflected as validator confirmation time in performance metrics.
|
||||
The performance dashboard displays the average of each validator node's confirmation time
|
||||
as a time series graph.
|
@ -60,7 +60,7 @@ The read is satisfied by pointing to a memory-mapped location in the
|
||||
|
||||
## Root Forks
|
||||
|
||||
[Tower BFT](tower-bft.md) eventually selects a fork as a
|
||||
The [fork selection algorithm](fork-selection.md) eventually selects a fork as a
|
||||
root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
||||
|
||||
When a fork is squashed, all accounts in its parents not already present in the
|
||||
|
@ -3,8 +3,8 @@
|
||||
A client *app* interacts with a Solana cluster by sending it *transactions*
|
||||
with one or more *instructions*. The Solana *runtime* passes those instructions
|
||||
to user-contributed *programs*. An instruction might, for example, tell a
|
||||
program to transfer *lamports* from one *account* to another or create an interactive
|
||||
contract that governs how lamports are transfered. Instructions are executed
|
||||
program to move *lamports* from one *account* to another or create an interactive
|
||||
contract that governs how lamports are moved. Instructions are executed
|
||||
atomically. If any instruction is invalid, any changes made within the
|
||||
transaction are discarded.
|
||||
|
||||
|
@ -1,51 +0,0 @@
|
||||
# Repair Service
|
||||
|
||||
The RepairService is in charge of retrieving missing blobs that failed to be delivered by primary communication protocols like Avalanche. It is in charge of managing the protocols described below in the `Repair Protocols` section below.
|
||||
|
||||
# Challenges:
|
||||
|
||||
1) Validators can fail to receive particular blobs due to network failures
|
||||
|
||||
2) Consider a scenario where blocktree contains the set of slots {1, 3, 5}. Then Blocktree receives blobs for some slot 7, where for each of the blobs b, b.parent == 6, so then the parent-child relation 6 -> 7 is stored in blocktree. However, there is no way to chain these slots to any of the existing banks in Blocktree, and thus the `Blob Repair` protocol will not repair these slots. If these slots happen to be part of the main chain, this will halt replay progress on this node.
|
||||
|
||||
3) Validators that find themselves behind the cluster by an entire epoch struggle/fail to catch up because they do not have a leader schedule for future epochs. If nodes were to blindly accept repair blobs in these future epochs, this exposes nodes to spam.
|
||||
|
||||
# Repair Protocols
|
||||
|
||||
The repair protocol makes best attempts to progress the forking structure of Blocktree.
|
||||
|
||||
The different protocol strategies to address the above challenges:
|
||||
|
||||
1. Blob Repair (Addresses Challenge #1):
|
||||
This is the most basic repair protocol, with the purpose of detecting and filling "holes" in the ledger. Blocktree tracks the latest root slot. RepairService will then periodically iterate every fork in blocktree starting from the root slot, sending repair requests to validators for any missing blobs. It will send at most some `N` repair reqeusts per iteration.
|
||||
|
||||
Note: Validators will only accept blobs within the current verifiable epoch (epoch the validator has a leader schedule for).
|
||||
|
||||
2. Preemptive Slot Repair (Addresses Challenge #2):
|
||||
The goal of this protocol is to discover the chaining relationship of "orphan" slots that do not currently chain to any known fork.
|
||||
|
||||
* Blocktree will track the set of "orphan" slots in a separate column family.
|
||||
|
||||
* RepairService will periodically make `RequestOrphan` requests for each of the orphans in blocktree.
|
||||
|
||||
`RequestOrphan(orphan)` request - `orphan` is the orphan slot that the requestor wants to know the parents of
|
||||
`RequestOrphan(orphan)` response - The highest blobs for each of the first `N` parents of the requested `orphan`
|
||||
|
||||
On receiving the responses `p`, where `p` is some blob in a parent slot, validators will:
|
||||
* Insert an empty `SlotMeta` in blocktree for `p.slot` if it doesn't already exist.
|
||||
* If `p.slot` does exist, update the parent of `p` based on `parents`
|
||||
|
||||
Note: that once these empty slots are added to blocktree, the `Blob Repair` protocol should attempt to fill those slots.
|
||||
|
||||
Note: Validators will only accept responses containing blobs within the current verifiable epoch (epoch the validator has a leader schedule for).
|
||||
|
||||
3. Repairmen (Addresses Challenge #3):
|
||||
This part of the repair protocol is the primary mechanism by which new nodes joining the cluster catch up after loading a snapshot. This protocol works in a "forward" fashion, so validators can verify every blob that they receive against a known leader schedule.
|
||||
|
||||
Each validator advertises in gossip:
|
||||
* Current root
|
||||
* The set of all completed slots in the confirmed epochs (an epoch that was calculated based on a bank <= current root) past the current root
|
||||
|
||||
Observers of this gossip message with higher epochs (repairmen) send blobs to catch the lagging node up with the rest of the cluster. The repairmen are responsible for sending the slots within the epochs that are confrimed by the advertised `root` in gossip. The repairmen divide the responsibility of sending each of the missing slots in these epochs based on a random seed (simple blob.index iteration by N, seeded with the repairman's node_pubkey). Ideally, each repairman in an N node cluster (N nodes whose epochs are higher than that of the repairee) sends 1/N of the missing blobs. Both data and coding blobs for missing slots are sent. Repairmen do not send blobs again to the same validator until they see the message in gossip updated, at which point they perform another iteration of this protocol.
|
||||
|
||||
Gossip messages are updated every time a validator receives a complete slot within the epoch. Completed slots are detected by blocktree and sent over a channel to RepairService. It is important to note that we know that by the time a slot X is complete, the epoch schedule must exist for the epoch that contains slot X because WindowService will reject blobs for unconfirmed epochs. When a newly completed slot is detected, we also update the current root if it has changed since the last update. The root is made available to RepairService through Blocktree, which holds the latest root.
|
@ -67,7 +67,7 @@ data array and assign it to a Program.
|
||||
|
||||
* `Assign` - Allows the user to assign an existing account to a program.
|
||||
|
||||
* `Transfer` - Transfers lamports between accounts.
|
||||
* `Move` - Moves lamports between accounts.
|
||||
|
||||
## Program State Security
|
||||
|
||||
|
@ -1,172 +0,0 @@
|
||||
# Simple Payment and State Verification
|
||||
|
||||
It is often useful to allow low resourced clients to participate in a Solana
|
||||
cluster. Be this participation economic or contract execution, verification
|
||||
that a client's activity has been accepted by the network is typically
|
||||
expensive. This proposal lays out a mechanism for such clients to confirm that
|
||||
their actions have been committed to the ledger state with minimal resource
|
||||
expenditure and third-party trust.
|
||||
|
||||
## A Naive Approach
|
||||
|
||||
Validators store the signatures of recently confirmed transactions for a short
|
||||
period of time to ensure that they are not processed more than once. Validators
|
||||
provide a JSON RPC endpoint, which clients can use to query the cluster if a
|
||||
transaction has been recently processed. Validators also provide a PubSub
|
||||
notification, whereby a client registers to be notified when a given signature
|
||||
is observed by the validator. While these two mechanisms allow a client to
|
||||
verify a payment, they are not a proof and rely on completely trusting a
|
||||
fullnode.
|
||||
|
||||
We will describe a way to minimize this trust using Merkle Proofs to anchor the
|
||||
fullnode's response in the ledger, allowing the client to confirm on their own
|
||||
that a sufficient number of their preferred validators have confirmed a
|
||||
transaction. Requiring multiple validator attestations further reduces trust in
|
||||
the fullnode, as it increases both the technical and economic difficulty of
|
||||
compromising several other network participants.
|
||||
|
||||
## Light Clients
|
||||
|
||||
A 'light client' is a cluster participant that does not itself run a fullnode.
|
||||
This light client would provide a level of security greater than trusting a
|
||||
remote fullnode, without requiring the light client to spend a lot of resources
|
||||
verifying the ledger.
|
||||
|
||||
Rather than providing transaction signatures directly to a light client, the
|
||||
fullnode instead generates a Merkle Proof from the transaction of interest to
|
||||
the root of a Merkle Tree of all transactions in the including block. This Merkle
|
||||
Root is stored in a ledger entry which is voted on by validators, providing it
|
||||
consensus legitimacy. The additional level of security for a light client depends
|
||||
on an initial canonical set of validators the light client considers to be the
|
||||
stakeholders of the cluster. As that set is changed, the client can update its
|
||||
internal set of known validators with [receipts](#receipts). This may become
|
||||
challenging with a large number of delegated stakes.
|
||||
|
||||
Fullnodes themselves may want to use light client APIs for performance reasons.
|
||||
For example, during the initial launch of a fullnode, the fullnode may use a
|
||||
cluster provided checkpoint of the state and verify it with a receipt.
|
||||
|
||||
## Receipts
|
||||
|
||||
A receipt is a minimal proof that; a transaction has been included in a block,
|
||||
that the block has been voted on by the client's preferred set of validators and
|
||||
that the votes have reached the desired confirmation depth.
|
||||
|
||||
The receipts for both state and payments start with a Merkle Path from the
|
||||
value into a Bank-Merkle that has been voted on and included in the ledger. A
|
||||
chain of PoH Entries containing subsequent validator votes, deriving from the
|
||||
Bank-Merkle, is the confirmation proof.
|
||||
|
||||
Clients can examine this ledger data and compute the finality using Solana's fork
|
||||
selection rules.
|
||||
|
||||
### Payment Merkle Path
|
||||
|
||||
A payment receipt is a data structure that contains a Merkle Path from a
|
||||
transaction to the required set of validator votes.
|
||||
|
||||
An Entry-Merkle is a Merkle Root including all transactions in the entry, sorted
|
||||
by signature.
|
||||
|
||||
<img alt="Block Merkle Diagram" src="img/spv-block-merkle.svg" class="center"/>
|
||||
|
||||
A Block-Merkle is a Merkle root of all the Entry-Merkles sequenced in the block.
|
||||
Transaction status is necessary for the receipt because the state receipt is
|
||||
constructed for the block. Two transactions over the same state can appear in
|
||||
the block, and therefore, there is no way to infer from just the state whether a
|
||||
transaction that is committed to the ledger has succeeded or failed in modifying
|
||||
the intended state. It may not be necessary to encode the full status code, but
|
||||
a single status bit to indicate the transaction's success.
|
||||
|
||||
### State Merkle Path
|
||||
|
||||
A state receipt provides a confirmation that a specific state is committed at the
|
||||
end of the block. Inter-block state transitions do not generate a receipt.
|
||||
|
||||
For example:
|
||||
|
||||
* A sends 5 Lamports to B
|
||||
* B spends 5 Lamports
|
||||
* C sends 5 Lamports to A
|
||||
|
||||
At the end of the block, A and B are in the exact same starting state, and any
|
||||
state receipt would point to the same value for A or B.
|
||||
|
||||
The Bank-Merkle is computed from the Merkle Tree of the new state changes, along
|
||||
with the Previous Bank-Merkle, and the Block-Merkle.
|
||||
|
||||
<img alt="Bank Merkle Diagram" src="img/spv-bank-merkle.svg" class="center"/>
|
||||
|
||||
A state receipt contains only the state changes occurring in the block. A direct
|
||||
Merkle Path to the current Bank-Merkle guarantees the state value at that bank
|
||||
hash, but it cannot be used to generate a “current” receipt to the latest state
|
||||
if the state modification occurred in some previous block. There is no guarantee
|
||||
that the path provided by the validator is the latest one available out of all
|
||||
the previous Bank-Merkles.
|
||||
|
||||
Clients that want to query the chain for a receipt of the "latest" state would
|
||||
need to create a transaction that would update the Merkle Path for that account,
|
||||
such as a credit of 0 Lamports.
|
||||
|
||||
### Validator Votes
|
||||
|
||||
Leaders should coalesce the validator votes by stake weight into a single entry.
|
||||
This will reduce the number of entries necessary to create a receipt.
|
||||
|
||||
### Chain of Entries
|
||||
|
||||
A receipt has a PoH link from the payment or state Merkle Path root to a list of
|
||||
consecutive validation votes.
|
||||
|
||||
It contains the following:
|
||||
* State -> Bank-Merkle
|
||||
or
|
||||
* Transaction -> Entry-Merkle -> Block-Merkle -> Bank-Merkle
|
||||
|
||||
And a vector of PoH entries:
|
||||
|
||||
* Validator vote entries
|
||||
* Ticks
|
||||
* Light entries
|
||||
|
||||
|
||||
```rust,ignore
|
||||
/// This Entry definition skips over the transactions and only contains the
|
||||
/// hash of the transactions used to modify PoH.
|
||||
LightEntry {
|
||||
/// The number of hashes since the previous Entry ID.
|
||||
pub num_hashes: u64,
|
||||
/// The SHA-256 hash `num_hashes` after the previous Entry ID.
|
||||
hash: Hash,
|
||||
/// The Merkle Root of the transactions encoded into the Entry.
|
||||
entry_hash: Hash,
|
||||
}
|
||||
```
|
||||
|
||||
The light entries are reconstructed from Entries and simply show the entry Merkle
|
||||
Root that was mixed in to the PoH hash, instead of the full transaction set.
|
||||
|
||||
Clients do not need the starting vote state. The [fork selection](book/src/fork-selection.md) algorithm is
|
||||
defined such that only votes that appear after the transaction provide finality
|
||||
for the transaction, and finality is independent of the starting state.
|
||||
|
||||
### Verification
|
||||
|
||||
A light client that is aware of the supermajority set validators can verify a
|
||||
receipt by following the Merkle Path to the PoH chain. The Bank-Merkle is the
|
||||
Merkle Root and will appear in votes included in an Entry. The light client can
|
||||
simulate [fork selection](book/src/fork-selection.md) for the consecutive votes
|
||||
and verify that the receipt is confirmed at the desired lockout threshold.
|
||||
|
||||
### Synthetic State
|
||||
|
||||
Synthetic state should be computed into the Bank-Merkle along with the bank
|
||||
generated state.
|
||||
|
||||
For example:
|
||||
|
||||
* Epoch validator accounts and their stakes and weights.
|
||||
* Computed fee rates
|
||||
|
||||
These values should have an entry in the Bank-Merkle. They should live under
|
||||
known accounts, and therefore have an exact address in the Merkle Path.
|
@ -1,197 +1,68 @@
|
||||
# Stake Delegation and Rewards
|
||||
|
||||
Stakers are rewarded for helping to validate the ledger. They do this by
|
||||
delegating their stake to validator nodes. Those validators do the legwork of
|
||||
replaying the ledger and send votes to a per-node vote account to which stakers
|
||||
can delegate their stakes. The rest of the cluster uses those stake-weighted
|
||||
votes to select a block when forks arise. Both the validator and staker need
|
||||
some economic incentive to play their part. The validator needs to be
|
||||
compensated for its hardware and the staker needs to be compensated for the risk
|
||||
of getting its stake slashed. The economics are covered in [staking
|
||||
Stakers are rewarded for helping validate the ledger. They do it by delegating
|
||||
their stake to fullnodes. Those fullnodes do the legwork and send votes to the
|
||||
stakers' staking accounts. The rest of the cluster uses those stake-weighted
|
||||
votes to select a block when forks arise. Both the fullnode and staker need
|
||||
some economic incentive to play their part. The fullnode needs to be
|
||||
compensated for its hardware and the staker needs to be compensated for risking
|
||||
getting its stake slashed. The economics are covered in [staking
|
||||
rewards](staking-rewards.md). This chapter, on the other hand, describes the
|
||||
underlying mechanics of its implementation.
|
||||
|
||||
## Basic Design
|
||||
## Vote and Rewards accounts
|
||||
|
||||
The general idea is that the validator owns a Vote account. The Vote account
|
||||
tracks validator votes, counts validator generated credits, and provides any
|
||||
additional validator specific state. The Vote account is not aware of any
|
||||
stakes delegated to it and has no staking weight.
|
||||
The rewards process is split into two on-chain programs. The Vote program
|
||||
solves the problem of making stakes slashable. The Rewards account acts as
|
||||
custodian of the rewards pool. It is responsible for paying out each staker
|
||||
once the staker proves to the Rewards program that it participated in
|
||||
validating the ledger.
|
||||
|
||||
A separate Stake account (created by a staker) names a Vote account to which the
|
||||
stake is delegated. Rewards generated are proportional to the amount of
|
||||
lamports staked. The Stake account is owned by the staker only. Some portion of the lamports
|
||||
stored in this account are the stake.
|
||||
The Vote account contains the following state information:
|
||||
|
||||
## Passive Delegation
|
||||
* votes - The submitted votes.
|
||||
|
||||
Any number of Stake accounts can delegate to a single
|
||||
Vote account without an interactive action from the identity controlling
|
||||
the Vote account or submitting votes to the account.
|
||||
* `delegate_id` - An identity that may operate with the weight of this
|
||||
account's stake. It is typically the identity of a fullnode, but may be any
|
||||
identity involved in stake-weighted computations.
|
||||
|
||||
The total stake allocated to a Vote account can be calculated by the sum of
|
||||
all the Stake accounts that have the Vote account pubkey as the
|
||||
`StakeState::Stake::voter_pubkey`.
|
||||
* `authorized_voter_id` - Only this identity is authorized to submit votes.
|
||||
|
||||
## Vote and Stake accounts
|
||||
* `credits` - The amount of unclaimed rewards.
|
||||
|
||||
The rewards process is split into two on-chain programs. The Vote program solves
|
||||
the problem of making stakes slashable. The Stake account acts as custodian of
|
||||
the rewards pool, and provides passive delegation. The Stake program is
|
||||
responsible for paying out each staker once the staker proves to the Stake
|
||||
program that its delegate has participated in validating the ledger.
|
||||
* `root_slot` - The last slot to reach the full lockout commitment necessary
|
||||
for rewards.
|
||||
|
||||
### VoteState
|
||||
The Rewards program is stateless and pays out reward when a staker submits its
|
||||
Vote account to the program. Claiming a reward requires a transaction that
|
||||
includes the following instructions:
|
||||
|
||||
VoteState is the current state of all the votes the validator has submitted to
|
||||
the network. VoteState contains the following state information:
|
||||
1. `RewardsInstruction::RedeemVoteCredits`
|
||||
2. `VoteInstruction::ClearCredits`
|
||||
|
||||
* `votes` - The submitted votes data structure.
|
||||
The Rewards program transfers lamports from the Rewards account to the Vote
|
||||
account's public key. The Rewards program also ensures that the `ClearCredits`
|
||||
instruction follows the `RedeemVoteCredits` instruction, such that a staker may
|
||||
not claim rewards for the same work more than once.
|
||||
|
||||
* `credits` - The total number of rewards this vote program has generated over its
|
||||
lifetime.
|
||||
|
||||
* `root_slot` - The last slot to reach the full lockout commitment necessary for
|
||||
rewards.
|
||||
### Delegating Stake
|
||||
|
||||
* `commission` - The commission taken by this VoteState for any rewards claimed by
|
||||
staker's Stake accounts. This is the percentage ceiling of the reward.
|
||||
`VoteInstruction::DelegateStake` allows the staker to choose a fullnode to
|
||||
validate the ledger on its behalf. By being a delegate, the fullnode is
|
||||
entitled to collect transaction fees when its is leader. The larger the stake,
|
||||
the more often the fullnode will be able to collect those fees.
|
||||
|
||||
* Account::lamports - The accumulated lamports from the commission. These do not
|
||||
count as stakes.
|
||||
### Authorizing a Vote Signer
|
||||
|
||||
* `authorized_vote_signer` - Only this identity is authorized to submit votes. This field can only modified by this identity.
|
||||
|
||||
### VoteInstruction::Initialize
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is initialized to `account[0]`
|
||||
other VoteState members defaulted
|
||||
|
||||
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, the transaction must by
|
||||
signed by the Vote account's current `authorized_vote_signer`. <br>
|
||||
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
|
||||
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
|
||||
for its votes. That service is responsible for ensuring the vote won't cause
|
||||
the staker to be slashed.
|
||||
|
||||
## Limitations
|
||||
|
||||
### VoteInstruction::Vote(Vec<Vote>)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](tower-bft.md)
|
||||
|
||||
|
||||
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
|
||||
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of three forms, StakeState::Uninitialized, StakeState::Stake and StakeState::RewardsPool.
|
||||
|
||||
### StakeState::Stake
|
||||
|
||||
StakeState::Stake is the current delegation preference of the **staker** and
|
||||
contains the following state information:
|
||||
|
||||
* Account::lamports - The lamports available for staking.
|
||||
|
||||
* `stake` - the staked amount (subject to warm up and cool down) for generating rewards, always less than or equal to Account::lamports
|
||||
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
||||
delegated to.
|
||||
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the
|
||||
program.
|
||||
|
||||
* `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
|
||||
|
||||
* `deactivated` - the epoch at which this stake will be completely de-activated, which is `cool down` epochs after StakeInstruction::Deactivate is issued.
|
||||
|
||||
### StakeState::RewardsPool
|
||||
|
||||
To avoid a single network wide lock or contention in redemption, 256 RewardsPools are part of genesis under pre-determined keys, each with std::u64::MAX credits to be able to satisfy redemptions according to point value.
|
||||
|
||||
The Stakes and the RewardsPool are accounts that are owned by the same `Stake` program.
|
||||
|
||||
### StakeInstruction::DelegateStake(u64)
|
||||
|
||||
The Stake account is moved from Uninitialized to StakeState::Stake form. This is
|
||||
how stakers choose their initial delegate validator node and activate their
|
||||
stake account lamports.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance. <br>
|
||||
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`,<br>
|
||||
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`,<br>
|
||||
`StakeState::Stake::stake` is initialized to the u64 passed as an argument above,<br>
|
||||
`StakeState::Stake::activated` is initialized to current Bank epoch, and<br>
|
||||
`StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
||||
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
|
||||
* `account[2]` - R - syscall::current account, carries information about current Bank epoch
|
||||
|
||||
### StakeInstruction::RedeemVoteCredits
|
||||
|
||||
The staker or the owner of the Stake account sends a transaction with this
|
||||
instruction to claim rewards.
|
||||
|
||||
The Vote account and the Stake account pair maintain a lifetime counter of total
|
||||
rewards generated and claimed. Rewards are paid according to a point value
|
||||
supplied by the Bank from inflation. A `point` is one credit * one staked
|
||||
lamport, rewards paid are proportional to the number of lamports staked.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards.
|
||||
* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey`
|
||||
* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request (picked at random).
|
||||
* `account[3]` - R - syscall::rewards account from the Bank that carries point value.
|
||||
|
||||
Reward is paid out for the difference between `VoteState::credits` to
|
||||
`StakeState::Stake::credits_observed`, multiplied by `syscall::rewards::Rewards::validator_point_value`.
|
||||
`StakeState::Stake::credits_observed` is updated to`VoteState::credits`. The commission is deposited into the Vote account token
|
||||
balance, and the reward is deposited to the Stake account token balance.
|
||||
|
||||
|
||||
```rust,ignore
|
||||
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
|
||||
stake_state.credits_observed = vote_state.credits;
|
||||
```
|
||||
|
||||
`credits_to_claim` is used to compute the reward and commission, and
|
||||
`StakeState::Stake::credits_observed` is updated to the latest
|
||||
`VoteState::credits` value.
|
||||
|
||||
### StakeInstruction::Deactivate
|
||||
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is deactivating, the transaction must be signed by this key.
|
||||
* `account[1]` - R - syscall::current account from the Bank that carries current epoch
|
||||
|
||||
StakeState::Stake::deactivated is set to the current epoch + cool down. The account's stake will ramp down to zero by
|
||||
that epoch, and Account::lamports will be available for withdrawal.
|
||||
|
||||
|
||||
### StakeInstruction::Withdraw(u64)
|
||||
Lamports build up over time in a Stake account and any excess over activated stake can be withdrawn.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake from which to withdraw, the transaction must be signed by this key.
|
||||
* `account[1]` - RW - Account that should be credited with the withdrawn lamports.
|
||||
* `account[2]` - R - syscall::current account from the Bank that carries current epoch, to calculate stake.
|
||||
|
||||
|
||||
## Benefits of the design
|
||||
|
||||
* Single vote for all the stakers.
|
||||
|
||||
* Clearing of the credit variable is not necessary for claiming rewards.
|
||||
|
||||
* Each delegated stake can claim its rewards independently.
|
||||
|
||||
* Commission for the work is deposited when a reward is claimed by the delegated
|
||||
stake.
|
||||
|
||||
## Example Callflow
|
||||
|
||||
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
|
||||
Many stakers may delegate their stakes to the same fullnode. The fullnode must
|
||||
send a separate vote to each staking account. If there are far more stakers
|
||||
than fullnodes, that's a lot of network traffic. An alternative design might
|
||||
have fullnodes submit each vote to just one account and then have each staker
|
||||
submit that account along with their own to collect its reward.
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Staking Rewards
|
||||
|
||||
A Proof of Stake (PoS), (i.e. using in-protocol asset, SOL, to provide
|
||||
secure consensus) design is outlined here. Solana implements a proof of
|
||||
stake reward/security scheme for validator nodes in the cluster. The purpose is
|
||||
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide
|
||||
secure consensus) design ideas outlined here. Solana will implement a proof of
|
||||
stake reward/security scheme for node validators in the cluster. The purpose is
|
||||
threefold:
|
||||
|
||||
- Align validator incentives with that of the greater cluster through
|
||||
@ -48,7 +48,7 @@ specific parameters will be necessary:
|
||||
|
||||
Solana's trustless sense of time and ordering provided by its PoH data
|
||||
structure, along with its
|
||||
[turbine](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
|
||||
[avalanche](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
|
||||
and transmission design, should provide sub-second transaction confirmation times that scale
|
||||
with the log of the number of nodes in the cluster. This means we shouldn't
|
||||
have to restrict the number of validating nodes with a prohibitive 'minimum
|
||||
@ -64,7 +64,7 @@ capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
|
||||
We intend to implement slashing rules which, if broken, result some amount of
|
||||
the offending validator's deposited stake to be removed from circulation. Given
|
||||
the ordering properties of the PoH data structure, we believe we can simplify
|
||||
our slashing rules to the level of a voting lockout time assigned per vote.
|
||||
our slashing rules to the level of a voting lockout time assigned per vote.
|
||||
|
||||
I.e. Each vote has an associated lockout time (PoH duration) that represents a
|
||||
duration by any additional vote from that validator must be in a PoH that
|
||||
@ -110,7 +110,7 @@ in a slashable amount as a function of either:
|
||||
1. the fraction of validators, out of the total validator pool, that were also
|
||||
slashed during the same time period (ala Casper)
|
||||
2. the amount of time since the vote was cast (e.g. a linearly increasing % of
|
||||
total deposited as slashable amount over time), or both.
|
||||
total deposited as slashable amount over time), or both.
|
||||
|
||||
This is an area currently under exploration
|
||||
|
||||
|
@ -91,10 +91,6 @@ History](#proof-of-history).
|
||||
The time, i.e. number of [slots](#slot), for which a [leader
|
||||
schedule](#leader-schedule) is valid.
|
||||
|
||||
#### finality
|
||||
|
||||
When nodes representing 2/3rd of the stake have a common [root](#root).
|
||||
|
||||
#### fork
|
||||
|
||||
A [ledger](#ledger) derived from common entries but then diverged.
|
||||
@ -217,15 +213,6 @@ The public key of a [keypair](#keypair).
|
||||
Storage mining client, stores some part of the ledger enumerated in blocks and
|
||||
submits storage proofs to the chain. Not a full-node.
|
||||
|
||||
#### root
|
||||
|
||||
A [block](#block) or [slot](#slot) that has reached maximum [lockout](#lockout)
|
||||
on a validator. The root is the highest block that is an ancestor of all active
|
||||
forks on a validator. All ancestor blocks of a root are also transitively a
|
||||
root. Blocks that are not an ancestor and not a descendant of the root are
|
||||
excluded from consideration for consensus and can be discarded.
|
||||
|
||||
|
||||
#### runtime
|
||||
|
||||
The component of a [fullnode](#fullnode) responsible for [program](#program)
|
||||
|
@ -15,43 +15,39 @@ reasons:
|
||||
* The cluster rolled back the ledger
|
||||
* A validator responded to queries maliciously
|
||||
|
||||
### The AsyncClient and SyncClient Traits
|
||||
### The Transact Trait
|
||||
|
||||
To troubleshoot, the application should retarget a lower-level component, where
|
||||
fewer errors are possible. Retargeting can be done with different
|
||||
implementations of the AsyncClient and SyncClient traits.
|
||||
implementations of the Transact trait.
|
||||
|
||||
Components implement the following primary methods:
|
||||
When Futures 0.3.0 is released, the Transact trait may look like this:
|
||||
|
||||
```rust,ignore
|
||||
trait AsyncClient {
|
||||
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>;
|
||||
}
|
||||
|
||||
trait SyncClient {
|
||||
fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>;
|
||||
trait Transact {
|
||||
async fn send_transactions(txs: &[Transaction]) -> Vec<Result<(), BankError>>;
|
||||
}
|
||||
```
|
||||
|
||||
Users send transactions and asynchrounously and synchrounously await results.
|
||||
Users send transactions and asynchrounously await their results.
|
||||
|
||||
#### ThinClient for Clusters
|
||||
#### Transact with Clusters
|
||||
|
||||
The highest level implementation, ThinClient, targets a Solana cluster, which
|
||||
may be a deployed testnet or a local cluster running on a development machine.
|
||||
The highest level implementation targets a Solana cluster, which may be a
|
||||
deployed testnet or a local cluster running on a development machine.
|
||||
|
||||
#### TpuClient for the TPU
|
||||
#### Transact with the TPU
|
||||
|
||||
The next level is the TPU implementation, which is not yet implemented. At the
|
||||
TPU level, the application sends transactions over Rust channels, where there
|
||||
can be no surprises from network queues or dropped packets. The TPU implements
|
||||
all "normal" transaction errors. It does signature verification, may report
|
||||
The next level is the TPU implementation of Transact. At the TPU level, the
|
||||
application sends transactions over Rust channels, where there can be no
|
||||
surprises from network queues or dropped packets. The TPU implements all
|
||||
"normal" transaction errors. It does signature verification, may report
|
||||
account-in-use errors, and otherwise results in the ledger, complete with proof
|
||||
of history hashes.
|
||||
|
||||
### Low-level testing
|
||||
|
||||
#### BankClient for the Bank
|
||||
### Testing with the Bank
|
||||
|
||||
Below the TPU level is the Bank. The Bank doesn't do signature verification or
|
||||
generate a ledger. The Bank is a convenient layer at which to test new on-chain
|
||||
|
@ -1,284 +0,0 @@
|
||||
## Testnet Participation
|
||||
This document describes how to participate in the testnet as a
|
||||
validator node.
|
||||
|
||||
Please note some of the information and instructions described here may change
|
||||
in future releases.
|
||||
|
||||
### Overview
|
||||
The testnet features a validator running at testnet.solana.com, which
|
||||
serves as the entrypoint to the cluster for your validator.
|
||||
|
||||
Additionally there is a blockexplorer available at
|
||||
[http://testnet.solana.com/](http://testnet.solana.com/).
|
||||
|
||||
The testnet is configured to reset the ledger daily, or sooner
|
||||
should the hourly automated cluster sanity test fail.
|
||||
|
||||
There is a **#validator-support** Discord channel available to reach other
|
||||
testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
|
||||
|
||||
Also we'd love it if you choose to register your validator node with us at
|
||||
[https://forms.gle/LfFscZqJELbuUP139](https://forms.gle/LfFscZqJELbuUP139).
|
||||
|
||||
### Machine Requirements
|
||||
Since the testnet is not intended for stress testing of max transaction
|
||||
throughput, a higher-end machine with a GPU is not necessary to participate.
|
||||
|
||||
However ensure the machine used is not behind a residential NAT to avoid NAT
|
||||
traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
|
||||
8000 through 10000 are not blocked for Internet inbound and outbound traffic.**
|
||||
|
||||
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
|
||||
MacOS or WSL users may build from source.
|
||||
|
||||
For a performance testnet with many transactions we have some preliminary recommended setups:
|
||||
|
||||
| | Low end | Medium end | High end | Notes |
|
||||
| --- | ---------|------------|----------| -- |
|
||||
| CPU | AMD Threadripper 1900x | AMD Threadripper 2920x | AMD Threadripper 2950x | Consider a 10Gb-capable motherboard with as many PCIe lanes and m.2 slots as possible. |
|
||||
| RAM | 16GB | 32GB | 64GB | |
|
||||
| OS Drive | Samsung 860 Evo 2TB | Samsung 860 Evo 4TB | Samsung 860 Evo 4TB | Or equivalent SSD |
|
||||
| Accounts Drive(s) | None | Samsung 970 Pro 1TB | 2x Samsung 970 Pro 1TB | |
|
||||
| GPU | 4x Nvidia 1070 or 2x Nvidia 1080 Ti or 2x Nvidia 2070 | 2x Nvidia 2080 Ti | 4x Nvidia 2080 Ti | Any number of cuda-capable GPUs are supported on Linux platforms. |
|
||||
|
||||
#### GPU Requirements
|
||||
CUDA is required to make use of the GPU on your system. The provided Solana
|
||||
release binaries are built on Ubuntu 18.04 with <a
|
||||
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
|
||||
update 1"</a>. If your machine is using a different CUDA version then you will
|
||||
need to rebuild from source.
|
||||
|
||||
#### Confirm The Testnet Is Reachable
|
||||
Before attaching a validator node, sanity check that the cluster is accessible
|
||||
to your machine by running some simple commands. If any of the commands fail,
|
||||
please retry 5-10 minutes later to confirm the testnet is not just restarting
|
||||
itself before debugging further.
|
||||
|
||||
Fetch the current transaction count over JSON RPC:
|
||||
```bash
|
||||
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
|
||||
```
|
||||
|
||||
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
|
||||
|
||||
View the [metrics dashboard](
|
||||
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
|
||||
for more detail on cluster activity.
|
||||
|
||||
### Validator Setup
|
||||
#### Obtaining The Software
|
||||
##### Bootstrap with `solana-install`
|
||||
|
||||
The `solana-install` tool can be used to easily install and upgrade the cluster
|
||||
software on Linux x86_64 and mac OS systems.
|
||||
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
|
||||
```
|
||||
|
||||
Alternatively build the `solana-install` program from source and run the
|
||||
following command to obtain the same result:
|
||||
```bash
|
||||
$ solana-install init
|
||||
```
|
||||
|
||||
After a successful install, `solana-install update` may be used to easily update the cluster
|
||||
software to a newer version at any time.
|
||||
|
||||
##### Download Prebuilt Binaries
|
||||
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
|
||||
|
||||
###### Linux
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
###### mac OS
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
##### Build From Source
|
||||
If you are unable to use the prebuilt binaries or prefer to build it yourself
|
||||
from source, navigate to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
and download the **Source Code** archive. Extract the code and build the
|
||||
binaries with:
|
||||
```bash
|
||||
$ ./scripts/cargo-install-all.sh .
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
If building for CUDA (Linux only), fetch the perf-libs first then include the
|
||||
`cuda` feature flag when building:
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ source ./target/perf-libs/env.sh
|
||||
$ ./scripts/cargo-install-all.sh . cuda
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
### Starting The Validator
|
||||
Sanity check that you are able to interact with the cluster by receiving a small
|
||||
airdrop of lamports from the testnet drone:
|
||||
```bash
|
||||
$ solana-wallet airdrop 123
|
||||
$ solana-wallet balance
|
||||
```
|
||||
|
||||
Also try running following command to join the gossip network and view all the other nodes in the cluster:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
# Press ^C to exit
|
||||
```
|
||||
|
||||
Now configure a key pair for your validator by running:
|
||||
```bash
|
||||
$ solana-keygen new -o ~/validator-keypair.json
|
||||
```
|
||||
|
||||
Then use one of the following commands, depending on your installation
|
||||
choice, to start the node:
|
||||
|
||||
If this is a `solana-install`-installation:
|
||||
```bash
|
||||
$ validator.sh --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
Alternatively, the `solana-install run` command can be used to run the validator
|
||||
node while periodically checking for and applying software updates:
|
||||
```bash
|
||||
$ solana-install run validator.sh -- --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
If you built from source:
|
||||
```bash
|
||||
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
#### Enabling CUDA
|
||||
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
|
||||
define the SOLANA_CUDA flag in your environment *before* running any of the
|
||||
previusly mentioned commands
|
||||
```bash
|
||||
$ export SOLANA_CUDA=1
|
||||
```
|
||||
|
||||
When your validator is started look for the following log message to indicate that CUDA is enabled:
|
||||
`"[<timestamp> solana::validator] CUDA is enabled"`
|
||||
|
||||
#### Controlling local network port allocation
|
||||
By default the validator will dynamically select available network ports in the
|
||||
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
|
||||
example, `validator.sh --dynamic-port-range 11000-11010 ...` will restrict the
|
||||
validator to ports 11000-11011.
|
||||
|
||||
### Validator Monitoring
|
||||
When `validator.sh` starts, it will output a validator configuration that looks
|
||||
similar to:
|
||||
```bash
|
||||
======================[ validator configuration ]======================
|
||||
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
|
||||
vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
|
||||
ledger: ...
|
||||
accounts: ...
|
||||
======================================================================
|
||||
```
|
||||
|
||||
The **identity pubkey** for your validator can also be found by running:
|
||||
```bash
|
||||
$ solana-keygen pubkey ~/validator-keypair.json
|
||||
```
|
||||
|
||||
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
|
||||
gossip network by running:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
```
|
||||
|
||||
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
|
||||
the recent voting activity from your validator:
|
||||
```bash
|
||||
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
|
||||
```
|
||||
|
||||
The vote pubkey for the validator can also be found by running:
|
||||
```bash
|
||||
# If this is a `solana-install`-installation run:
|
||||
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/validator-vote-keypair.json
|
||||
# Otherwise run:
|
||||
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
|
||||
```
|
||||
|
||||
|
||||
#### Validator Metrics
|
||||
Metrics are available for local monitoring of your validator.
|
||||
|
||||
Docker must be installed and the current user added to the docker group. Then
|
||||
download `solana-metrics.tar.bz2` from the Github Release and run
|
||||
```bash
|
||||
$ tar jxf solana-metrics.tar.bz2
|
||||
$ cd solana-metrics/
|
||||
$ ./start.sh
|
||||
```
|
||||
|
||||
A local InfluxDB and Grafana instance is now running on your machine. Define
|
||||
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
|
||||
`start.sh` output and restart your validator.
|
||||
|
||||
Metrics should now be streaming and visible from your local Grafana dashboard.
|
||||
|
||||
#### Timezone For Log Messages
|
||||
Log messages emitted by your validator include a timestamp. When sharing logs
|
||||
with others to help triage issues, that timestamp can cause confusion as it does
|
||||
not contain timezone information.
|
||||
|
||||
To make it easier to compare logs between different sources we request that
|
||||
everybody use Pacific Time on their validator nodes. In Linux this can be
|
||||
accomplished by running:
|
||||
```bash
|
||||
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
|
||||
```
|
||||
|
||||
#### Publishing Validator Info
|
||||
|
||||
You can publish your validator information to the chain to be publicly visible
|
||||
to other users.
|
||||
|
||||
Run the solana-validator-info CLI to populate a validator-info account:
|
||||
```bash
|
||||
$ solana-validator-info publish ~/validator-keypair.json <VALIDATOR_NAME> <VALIDATOR_INFO_ARGS>
|
||||
```
|
||||
Optional fields for VALIDATOR_INFO_ARGS:
|
||||
* Website
|
||||
* Keybase Username
|
||||
* Details
|
||||
|
||||
##### Keybase
|
||||
|
||||
Including a Keybase username allows client applications (like the Solana Network
|
||||
Explorer) to automatically pull in your validator public profile, including
|
||||
cryptographic proofs, brand identity, etc. To connect your validator pubkey with
|
||||
Keybase:
|
||||
|
||||
1. Join https://keybase.io/ and complete the profile for your validator
|
||||
2. Add your validator **identity pubkey** to Keybase:
|
||||
* Create an empty file on your local computer called `validator-<PUBKEY>`
|
||||
* In Keybase, navigate to the Files section, and upload your pubkey file to
|
||||
a `solana` subdirectory in your public folder: `/keybase/public/<KEYBASE_USERNAME>/solana`
|
||||
* To check your pubkey, ensure you can successfully browse to
|
||||
`https://keybase.pub/<KEYBASE_USERNAME>/solana/validator-<PUBKEY>`
|
||||
3. Add or update your `solana-validator-info` with your Keybase username. The
|
||||
CLI will verify the `validator-<PUBKEY>` file
|
@ -1,153 +0,0 @@
|
||||
## Testnet Replicator
|
||||
This document describes how to setup a replicator in the testnet
|
||||
|
||||
Please note some of the information and instructions described here may change
|
||||
in future releases.
|
||||
|
||||
### Overview
|
||||
Replicators are specialized light clients. They download a part of the
|
||||
ledger (a.k.a Segment) and store it. They earn rewards for storing segments.
|
||||
|
||||
The testnet features a validator running at testnet.solana.com, which
|
||||
serves as the entrypoint to the cluster for your replicator node.
|
||||
|
||||
Additionally there is a blockexplorer available at
|
||||
[http://testnet.solana.com/](http://testnet.solana.com/).
|
||||
|
||||
The testnet is configured to reset the ledger daily, or sooner
|
||||
should the hourly automated cluster sanity test fail.
|
||||
|
||||
### Machine Requirements
|
||||
Replicators don't need specialized hardware. Anything with more than
|
||||
128GB of disk space will be able to participate in the cluster as a replicator node.
|
||||
|
||||
Currently the disk space requirements are very low but we expect them to change
|
||||
in the future.
|
||||
|
||||
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended),
|
||||
macOS, and Windows.
|
||||
|
||||
#### Confirm The Testnet Is Reachable
|
||||
Before starting a replicator node, sanity check that the cluster is accessible
|
||||
to your machine by running some simple commands. If any of the commands fail,
|
||||
please retry 5-10 minutes later to confirm the testnet is not just restarting
|
||||
itself before debugging further.
|
||||
|
||||
Fetch the current transaction count over JSON RPC:
|
||||
```bash
|
||||
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
|
||||
```
|
||||
|
||||
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
|
||||
|
||||
View the [metrics dashboard](
|
||||
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
|
||||
for more detail on cluster activity.
|
||||
|
||||
### Replicator Setup
|
||||
##### Obtaining The Software
|
||||
##### Bootstrap with `solana-install`
|
||||
|
||||
The `solana-install` tool can be used to easily install and upgrade the cluster
|
||||
software.
|
||||
|
||||
##### Linux and mac OS
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
|
||||
```
|
||||
|
||||
Alternatively build the `solana-install` program from source and run the
|
||||
following command to obtain the same result:
|
||||
```bash
|
||||
$ solana-install init
|
||||
```
|
||||
|
||||
##### Windows
|
||||
Download and install **solana-install-init** from
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest)
|
||||
|
||||
After a successful install, `solana-install update` may be used to
|
||||
easily update the software to a newer version at any time.
|
||||
|
||||
##### Download Prebuilt Binaries
|
||||
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
|
||||
|
||||
##### Linux
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
##### mac OS
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
##### Windows
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-pc-windows-msvc.tar.bz2**, then extract it into a folder.
|
||||
It is a good idea to add this extracted folder to your windows PATH.
|
||||
|
||||
### Starting The Replicator
|
||||
Try running following command to join the gossip network and view all the other nodes in the cluster:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
# Press ^C to exit
|
||||
```
|
||||
|
||||
Now configure the keypairs for your replicator by running:
|
||||
|
||||
Navigate to the solana install location and open a cmd prompt
|
||||
```bash
|
||||
$ solana-keygen new -o replicator-keypair.json
|
||||
$ solana-keygen new -o storage-keypair.json
|
||||
```
|
||||
|
||||
Use solana-keygen to show the public keys for each of the keypairs,
|
||||
they will be needed in the next step:
|
||||
- Windows
|
||||
```bash
|
||||
# The replicator's identity
|
||||
$ solana-keygen pubkey replicator-keypair.json
|
||||
$ solana-keygen pubkey storage-keypair.json
|
||||
```
|
||||
- Linux and mac OS
|
||||
```bash
|
||||
$ export REPLICATOR_IDENTITY=$(solana-keygen pubkey replicator-keypair.json)
|
||||
$ export STORAGE_IDENTITY=$(solana-keygen pubkey storage-keypair.json)
|
||||
|
||||
```
|
||||
Then set up the storage accounts for your replicator by running:
|
||||
```bash
|
||||
$ solana-wallet --keypair replicator-keypair.json airdrop 100000
|
||||
$ solana-wallet --keypair replicator-keypair.json create-replicator-storage-account $REPLICATOR_IDENTITY $STORAGE_IDENTITY
|
||||
```
|
||||
Note: Every time the testnet restarts, run the wallet steps to setup the replicator accounts again.
|
||||
|
||||
To start the replicator:
|
||||
```bash
|
||||
$ solana-replicator --entrypoint testnet.solana.com:8001 --identity replicator-keypair.json --storage-keypair storage-keypair.json --ledger replicator-ledger
|
||||
```
|
||||
|
||||
### Verify Replicator Setup
|
||||
From another console, confirm the IP address and **identity pubkey** of your replicator is visible in the
|
||||
gossip network by running:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
```
|
||||
|
||||
Provide the **storage account pubkey** to the `solana-wallet show-storage-account` command to view
|
||||
the recent mining activity from your replicator:
|
||||
```bash
|
||||
$ solana-wallet --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
|
||||
```
|
@ -1,48 +0,0 @@
|
||||
# The Transaction
|
||||
|
||||
### Components of a `Transaction`
|
||||
|
||||
* **Transaction:**
|
||||
* **message:** Defines the transaction
|
||||
* **header:** Details the account types of and signatures required by
|
||||
the transaction
|
||||
* **num_required_signatures:** The total number of signatures
|
||||
required to make the transaction valid.
|
||||
* **num_credit_only_signed_accounts:** The last
|
||||
`num_credit_only_signed_accounts` signatures refer to signing
|
||||
credit only accounts. Credit only accounts can be used concurrently
|
||||
by multiple parallel transactions, but their balance may only be
|
||||
increased, and their account data is read-only.
|
||||
* **num_credit_only_unsigned_accounts:** The last
|
||||
`num_credit_only_unsigned_accounts` pubkeys in `account_keys` refer
|
||||
to non-signing credit only accounts
|
||||
* **account_keys:** List of pubkeys used by the transaction, including
|
||||
by the instructions and for signatures. The first
|
||||
`num_required_signatures` pubkeys must sign the transaction.
|
||||
* **recent_blockhash:** The ID of a recent ledger entry. Validators will
|
||||
reject transactions with a `recent_blockhash` that is too old.
|
||||
* **instructions:** A list of [instructions](instruction.md) that are
|
||||
run sequentially and committed in one atomic transaction if all
|
||||
succeed.
|
||||
* **signatures:** A list of signatures applied to the transaction. The
|
||||
list is always of length `num_required_signatures`, and the signature
|
||||
at index `i` corresponds to the pubkey at index `i` in `account_keys`.
|
||||
The list is initialized with empty signatures (i.e. zeros), and
|
||||
populated as signatures are added.
|
||||
|
||||
### Transaction Signing
|
||||
|
||||
A `Transaction` is signed by using an ed25519 keypair to sign the
|
||||
serialization of the `message`. The resulting signature is placed at the
|
||||
index of `signatures` matching the index of the keypair's pubkey in
|
||||
`account_keys`.
|
||||
|
||||
### Transaction Serialization
|
||||
|
||||
`Transaction`s (and their `message`s) are serialized and deserialized
|
||||
using the [bincode](https://crates.io/crates/bincode) crate with a
|
||||
non-standard vector serialization that uses only one byte for the length
|
||||
if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3
|
||||
bytes if it requires 15 or 16 bits. The vector serialization is defined
|
||||
by Solana's
|
||||
[short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).
|
@ -1,59 +0,0 @@
|
||||
# Deterministic Transaction Fees
|
||||
|
||||
Transactions currently include a fee field that indicates the maximum fee field
|
||||
a slot leader is permitted to charge to process a transaction. The cluster, on
|
||||
the other hand, agrees on a minimum fee. If the network is congested, the slot
|
||||
leader may prioritize the transactions offering higher fees. That means the
|
||||
client won't know how much was collected until the transaction is confirmed by
|
||||
the cluster and the remaining balance is checked. It smells of exactly what we
|
||||
dislike about Ethereum's "gas", non-determinism.
|
||||
|
||||
### Congestion-driven fees
|
||||
|
||||
Each validator uses *signatures per slot* (SPS) to estimate network congestion
|
||||
and *SPS target* to estimate the desired processing capacity of the cluster.
|
||||
The validator learns the SPS target from the genesis block, whereas it
|
||||
calculates SPS from recently processed transactions. The genesis block also
|
||||
defines a target `lamports_per_signature`, which is the fee to charge per
|
||||
signature when the cluster is operating at *SPS target*.
|
||||
|
||||
### Calculating fees
|
||||
|
||||
The client uses the JSON RPC API to query the cluster for the current fee
|
||||
parameters. Those parameters are tagged with a blockhash and remain valid
|
||||
until that blockhash is old enough to be rejected by the slot leader.
|
||||
|
||||
Before sending a transaction to the cluster, a client may submit the
|
||||
transaction and fee account data to an SDK module called the *fee calculator*.
|
||||
So long as the client's SDK version matches the slot leader's version, the
|
||||
client is assured that its account will be changed exactly the same number of
|
||||
lamports as returned by the fee calculator.
|
||||
|
||||
### Fee Parameters
|
||||
|
||||
In the first implementation of this design, the only fee parameter is
|
||||
`lamports_per_signature`. The more signatures the cluster needs to verify, the
|
||||
higher the fee. The exact number of lamports is determined by the ratio of SPS
|
||||
to the SPS target. At the end of each slot, the cluster lowers
|
||||
`lamports_per_signature` when SPS is below the target and raises it when above
|
||||
the target. The minimum value for `lamports_per_signature` is 50% of the target
|
||||
`lamports_per_signature` and the maximum value is 10x the target
|
||||
`lamports_per_signature'
|
||||
|
||||
Future parameters might include:
|
||||
|
||||
* `lamports_per_pubkey` - cost to load an account
|
||||
* `lamports_per_slot_distance` - higher cost to load very old accounts
|
||||
* `lamports_per_byte` - cost per size of account loaded
|
||||
* `lamports_per_bpf_instruction` - cost to run a program
|
||||
|
||||
### Attacks
|
||||
|
||||
#### Hijacking the SPS Target
|
||||
|
||||
A group of validators can centralize the cluster if they can convince it to
|
||||
raise the SPS Target above a point where the rest of the validators can keep
|
||||
up. Raising the target will cause fees to drop, presumably creating more demand
|
||||
and therefore higher TPS. If the validator doesn't have hardware that can
|
||||
process that many transactions that fast, its confirmation votes will
|
||||
eventually get so long that the cluster will be forced to boot it.
|
@ -1,43 +0,0 @@
|
||||
# Anatomy of a Transaction
|
||||
|
||||
Transactions encode lists of instructions that are executed
|
||||
sequentially, and only committed if all the instructions complete
|
||||
successfully. All account states are reverted upon the failure of a
|
||||
transaction. Each Transaction details the accounts used, including which
|
||||
must sign and which are credit only, a recent blockhash, the
|
||||
instructions, and any signatures.
|
||||
|
||||
## Accounts and Signatures
|
||||
|
||||
Each transaction explicitly lists all accounts that it needs access to.
|
||||
This includes accounts that are transferring tokens, accounts whose user
|
||||
data is being modified, and the program accounts that are being called
|
||||
by the instructions. Each account that is not an executable program can
|
||||
be marked as a requiring a signature and/or as credit only. All accounts
|
||||
marked as signers must have a valid signature in the transaction's list
|
||||
of signatures before the transaction is considered valid. Any accounts
|
||||
marked as credit only may only have their token value increased, and
|
||||
their user data is read only. Accounts are locked by the runtime,
|
||||
ensuring that they are not modified by a concurrent program while the
|
||||
transaction is running. Credit only accounts can safely be shared, so
|
||||
the runtime will allow multiple concurrent credit only locks on an
|
||||
account.
|
||||
|
||||
## Recent Blockhash
|
||||
|
||||
A Transaction includes a recent blockhash to prevent duplication and to
|
||||
give transactions lifetimes. Any transaction that is completely
|
||||
identical to a previous one is rejected, so adding a newer blockhash
|
||||
allows multiple transactions to repeat the exact same action.
|
||||
Transactions also have lifetimes that are defined by the blockhash, as
|
||||
any transaction whose blockhash is too old will be rejected.
|
||||
|
||||
## Instructions
|
||||
|
||||
Each instruction specifies a single program account (which must be
|
||||
marked executable), a subset of the transaction's accounts that should
|
||||
be passed to the program, and a data byte array instruction that is
|
||||
passed to the program. The program interprets the data array and
|
||||
operates on the accounts specified by the instructions. The program can
|
||||
return successfully, or with an error code. An error return causes the
|
||||
entire transaction to fail immediately.
|
@ -1,90 +0,0 @@
|
||||
# Turbine Block Propagation
|
||||
|
||||
A Solana cluster uses a multi-layer block propagation mechanism called *Turbine*
|
||||
to broadcast transaction blobs to all nodes with minimal amount of duplicate
|
||||
messages. The cluster divides itself into small collections of nodes, called
|
||||
*neighborhoods*. Each node is responsible for sharing any data it receives with
|
||||
the other nodes in its neighborhood, as well as propagating the data on to a
|
||||
small set of nodes in other neighborhoods. This way each node only has to
|
||||
communicate with a small number of nodes.
|
||||
|
||||
During its slot, the leader node distributes blobs between the validator nodes
|
||||
in the first neighborhood (layer 0). Each validator shares its data within its
|
||||
neighborhood, but also retransmits the blobs to one node in some neighborhoods
|
||||
in the next layer (layer 1). The layer-1 nodes each share their data with their
|
||||
neighborhood peers, and retransmit to nodes in the next layer, etc, until all
|
||||
nodes in the cluster have received all the blobs.
|
||||
|
||||
## Neighborhood Assignment - Weighted Selection
|
||||
|
||||
In order for data plane fanout to work, the entire cluster must agree on how the
|
||||
cluster is divided into neighborhoods. To achieve this, all the recognized
|
||||
validator nodes (the TVU peers) are sorted by stake and stored in a list. This
|
||||
list is then indexed in different ways to figure out neighborhood boundaries and
|
||||
retransmit peers. For example, the leader will simply select the first nodes to
|
||||
make up layer 0. These will automatically be the highest stake holders, allowing
|
||||
the heaviest votes to come back to the leader first. Layer-0 and lower-layer
|
||||
nodes use the same logic to find their neighbors and next layer peers.
|
||||
|
||||
To reduce the possibility of attack vectors, each blob is transmitted over a
|
||||
random tree of neighborhoods. Each node uses the same set of nodes representing
|
||||
the cluster. A random tree is generated from the set for each blob using
|
||||
randomness derived from the blob itself. Since the random seed is not known in
|
||||
advance, attacks that try to eclipse neighborhoods from certain leaders or
|
||||
blocks become very difficult, and should require almost complete control of the
|
||||
stake in the cluster.
|
||||
|
||||
## Layer and Neighborhood Structure
|
||||
|
||||
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
|
||||
nodes. If this layer 0 is smaller than the number of nodes in the cluster, then
|
||||
the data plane fanout mechanism adds layers below. Subsequent layers follow
|
||||
these constraints to determine layer-capacity: Each neighborhood contains
|
||||
`DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes.
|
||||
The number of nodes in each additional layer grows by a factor of fanout.
|
||||
|
||||
As mentioned above, each node in a layer only has to broadcast its blobs to its
|
||||
neighbors and to exactly 1 node in some next-layer neighborhoods,
|
||||
instead of to every TVU peer in the cluster. A good way to think about this is,
|
||||
layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout"
|
||||
neighborhoods, each with fanout nodes and layer-2 will have
|
||||
`fanout * number of nodes in layer-1` and so on.
|
||||
|
||||
This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
|
||||
|
||||
The following diagram shows how the Leader sends blobs with a Fanout of 2 to
|
||||
Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data
|
||||
with each other.
|
||||
|
||||
<img alt="Leader sends blobs to Neighborhood 0 in Layer 0" src="img/data-plane-seeding.svg" class="center"/>
|
||||
|
||||
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
|
||||
|
||||
<img alt="Neighborhood 0 Fanout to Neighborhood 1 and 2" src="img/data-plane-fanout.svg" class="center"/>
|
||||
|
||||
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
|
||||
|
||||
<img alt="Two layer cluster with a Fanout of 2" src="img/data-plane.svg" class="center"/>
|
||||
|
||||
#### Configuration Values
|
||||
|
||||
`DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent
|
||||
layers grow by a factor of `DATA_PLANE_FANOUT`.
|
||||
The number of nodes in a neighborhood is equal to the fanout value.
|
||||
Neighborhoods will fill to capacity before new ones are added, i.e if a
|
||||
neighborhood isn't full, it _must_ be the last one.
|
||||
|
||||
Currently, configuration is set when the cluster is launched. In the future,
|
||||
these parameters may be hosted on-chain, allowing modification on the fly as the
|
||||
cluster sizes change.
|
||||
|
||||
## Neighborhoods
|
||||
|
||||
The following diagram shows how two neighborhoods in different layers interact.
|
||||
To cripple a neighborhood, enough nodes (erasure codes +1) from the neighborhood
|
||||
above need to fail. Since each neighborhood receives blobs from multiple nodes
|
||||
in a neighborhood in the upper layer, we'd need a big network failure in the upper
|
||||
layers to end up with incomplete data.
|
||||
|
||||
<img alt="Inner workings of a neighborhood"
|
||||
src="img/data-plane-neighborhood.svg" class="center"/>
|
@ -1,56 +0,0 @@
|
||||
# Anatomy of a Validator
|
||||
|
||||
## History
|
||||
|
||||
When we first started Solana, the goal was to de-risk our TPS claims. We knew
|
||||
that between optimistic concurrency control and sufficiently long leader slots,
|
||||
that PoS consensus was not the biggest risk to TPS. It was GPU-based signature
|
||||
verification, software pipelining and concurrent banking. Thus, the TPU was
|
||||
born. After topping 100k TPS, we split the team into one group working toward
|
||||
710k TPS and another to flesh out the validator pipeline. Hence, the TVU was
|
||||
born. The current architecture is a consequence of incremental development with
|
||||
that ordering and project priorities. It is not a reflection of what we ever
|
||||
believed was the most technically elegant cross-section of those technologies.
|
||||
In the context of leader rotation, the strong distinction between leading and
|
||||
validating is blurred.
|
||||
|
||||
## Difference between validating and leading
|
||||
|
||||
The fundamental difference between the pipelines is when the PoH is present. In
|
||||
a leader, we process transactions, removing bad ones, and then tag the result
|
||||
with a PoH hash. In the validator, we verify that hash, peel it off, and
|
||||
process the transactions in exactly the same way. The only difference is that
|
||||
if a validator sees a bad transaction, it can't simply remove it like the
|
||||
leader does, because that would cause the PoH hash to change. Instead, it
|
||||
rejects the whole block. The other difference between the pipelines is what
|
||||
happens *after* banking. The leader broadcasts entries to downstream validators
|
||||
whereas the validator will have already done that in RetransmitStage, which is
|
||||
a confirmation time optimization. The validation pipeline, on the other hand,
|
||||
has one last step. Any time it finishes processing a block, it needs to weigh
|
||||
any forks it's observing, possibly cast a vote, and if so, reset its PoH hash
|
||||
to the block hash it just voted on.
|
||||
|
||||
## Proposed Design
|
||||
|
||||
We unwrap the many abstraction layers and build a single pipeline that can
|
||||
toggle leader mode on whenever the validator's ID shows up in the leader
|
||||
schedule.
|
||||
|
||||
<img alt="Validator block diagram" src="img/validator-proposal.svg" class="center"/>
|
||||
|
||||
## Notable changes
|
||||
|
||||
* No threads are shut down to switch out of leader mode. Instead, FetchStage
|
||||
should forward transactions to the next leader.
|
||||
* Hoist FetchStage and BroadcastStage out of TPU
|
||||
* Blocktree renamed to Blockstore
|
||||
* BankForks renamed to Banktree
|
||||
* TPU moves to new socket-free crate called solana-tpu.
|
||||
* TPU's BankingStage absorbs ReplayStage
|
||||
* TVU goes away
|
||||
* New RepairStage absorbs Blob Fetch Stage and repair requests
|
||||
* JSON RPC Service is optional - used for debugging. It should instead be part
|
||||
of a separate `solana-blockstreamer` executable.
|
||||
* New MulticastStage absorbs retransmit part of RetransmitStage
|
||||
* MulticastStage downstream of Blockstore
|
||||
|
@ -41,7 +41,7 @@ $ solana-wallet balance
|
||||
$ solana-wallet confirm <TX_SIGNATURE>
|
||||
|
||||
// Return
|
||||
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
|
||||
"Confirmed" / "Not found"
|
||||
```
|
||||
|
||||
#### Deploy program
|
||||
@ -284,18 +284,6 @@ ARGS:
|
||||
<PATH> /path/to/program.o
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-wallet-fees
|
||||
Display current cluster fees
|
||||
|
||||
USAGE:
|
||||
solana-wallet fees
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-wallet-get-transaction-count
|
||||
Get current transaction count
|
||||
@ -364,3 +352,4 @@ ARGS:
|
||||
<PUBKEY> The pubkey of recipient
|
||||
<PROCESS_ID> The process id of the transfer to unlock
|
||||
```
|
||||
|
||||
|
@ -521,4 +521,4 @@ ul#searchresults span.teaser em {
|
||||
}
|
||||
.content pre {
|
||||
padding: 0 28px;
|
||||
}
|
||||
}
|
@ -152,4 +152,4 @@ blockquote {
|
||||
*:active,
|
||||
*:hover {
|
||||
outline: none;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
19
build-perf-libs.sh
Executable file
19
build-perf-libs.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds perf-libs from the upstream source and installs them into the correct
|
||||
# location in the tree
|
||||
#
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [[ -d target/perf-libs ]]; then
|
||||
echo "target/perf-libs/ already exists, to continue run:"
|
||||
echo "$ rm -rf target/perf-libs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
|
||||
cd target/perf-libs
|
||||
make -j"$(nproc)"
|
||||
make DESTDIR=. install
|
2
chacha-sys/.gitignore
vendored
2
chacha-sys/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user