Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cfe91c67f9 | ||
|
0f9c516570 | ||
|
e4aba9c900 | ||
|
88aea74ab1 | ||
|
357f28ae4e | ||
|
7c9c667c5c | ||
|
d62d1174d0 | ||
|
5d5ae1c962 | ||
|
bf11d7ef43 | ||
|
9568a1da03 | ||
|
f11a3d9796 | ||
|
8a7e8aacdd | ||
|
e3750fb73d | ||
|
d78b09492e | ||
|
d573b55821 | ||
|
18cad565cf | ||
|
9abf90b443 | ||
|
cd3ff5c335 | ||
|
e55249e63f | ||
|
10bc0c6ee2 | ||
|
ed14b78d81 | ||
|
7f404941bb | ||
|
6d45ac1bc7 | ||
|
fabb6d2092 | ||
|
93cea4c86c | ||
|
5fb35f79c3 | ||
|
da11274b63 | ||
|
5d70e2efa9 | ||
|
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,42 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+\.[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
clone_folder: d:\projects\solana
|
||||
|
||||
build_script:
|
||||
- bash ci/publish-tarball.sh
|
||||
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
|
||||
secret_access_key:
|
||||
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
1
.buildkite/env/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/secrets_unencrypted.ejson
|
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,22 +10,11 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
buildkite-agent annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
buildkite-agent pipeline upload ci/buildkite-release.yml
|
||||
else
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
buildkite-agent annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
fi
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
|
||||
if [[ $BUILDKITE_MESSAGE =~ GitBook: ]]; then
|
||||
buildkite-agent annotate --style info --context gitbook-ci-skip \
|
||||
"GitBook commit detected, CI skipped"
|
||||
exit
|
||||
fi
|
||||
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
buildkite-agent annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
fi
|
||||
|
||||
|
@@ -1,5 +0,0 @@
|
||||
root: ./book/src
|
||||
|
||||
structure:
|
||||
readme: introduction.md
|
||||
summary: SUMMARY.md
|
24
.github/stale.yml
vendored
@@ -1,24 +0,0 @@
|
||||
only: pulls
|
||||
|
||||
# Number of days of inactivity before a pull request becomes stale
|
||||
daysUntilStale: 30
|
||||
|
||||
# Number of days of inactivity before a stale pull request is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- security
|
||||
|
||||
# Label to use when marking a pull request as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking a pull request as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
# Comment to post when closing a stale pull request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This stale pull request has been automatically closed.
|
||||
Thank you for your contributions.
|
17
.gitignore
vendored
@@ -1,16 +1,18 @@
|
||||
/book/html/
|
||||
/book/src/tests.ok
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
/solana-metrics/
|
||||
/solana-metrics.tar.bz2
|
||||
/target/
|
||||
/ledger-tool/target/
|
||||
/wallet/target/
|
||||
/core/target/
|
||||
/book/html/
|
||||
/book/src/img/
|
||||
/book/src/tests.ok
|
||||
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# node config that is rsynced
|
||||
/config/
|
||||
# node config that remains local
|
||||
/config-local/
|
||||
|
||||
# log files
|
||||
*.log
|
||||
@@ -19,4 +21,3 @@ log-*.txt
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
77
.mergify.yml
@@ -1,77 +0,0 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.16 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.16
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.16
|
||||
- name: v0.17 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.17
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.17
|
||||
- name: v0.18 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.18
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.18
|
||||
- name: v0.19 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.19
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.19
|
||||
- name: v0.20 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.20
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.20
|
||||
- name: v0.21 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.21
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.21
|
||||
- name: v0.22 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.22
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.22
|
43
.travis.yml
@@ -1,43 +0,0 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
- 1.37.0
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
- test $rust_stable = $TRAVIS_RUST_VERSION # Update .travis.yml rust version above when this fails
|
||||
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- ci/publish-tarball.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+$/
|
||||
|
||||
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://docs.solana.com/book/v/master/proposals) 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.
|
||||
|
5108
Cargo.lock
generated
193
Cargo.toml
@@ -1,127 +1,92 @@
|
||||
[package]
|
||||
name = "solana-workspace"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.12.3"
|
||||
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.3" }
|
||||
solana-budget-program = { path = "programs/budget", version = "0.12.3" }
|
||||
solana-client = { path = "client", version = "0.12.3" }
|
||||
solana-logger = { path = "logger", version = "0.12.3" }
|
||||
solana-netutil = { path = "netutil", version = "0.12.3" }
|
||||
solana-runtime = { path = "runtime", version = "0.12.3" }
|
||||
solana-sdk = { path = "sdk", version = "0.12.3" }
|
||||
solana-vote-api = { path = "programs/vote_api", version = "0.12.3" }
|
||||
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]
|
||||
# The members list excluding the `validator-cuda` crate
|
||||
default-members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking_bench",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"local_cluster",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/btc_spv_program",
|
||||
"programs/btc_spv_api",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"programs/config_tests",
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"upload-perf",
|
||||
"netutil",
|
||||
"fixed-buf",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
]
|
||||
|
||||
# The default-members list and the `validator-cuda` crate
|
||||
members = [
|
||||
"bench-exchange",
|
||||
".",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking_bench",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"fullnode",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"local_cluster",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/btc_spv_program",
|
||||
"programs/btc_spv_api",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"programs/config_tests",
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"upload-perf",
|
||||
"netutil",
|
||||
"fixed-buf",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"validator-cuda",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/budget_api",
|
||||
"programs/token",
|
||||
"programs/token_api",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/rewards",
|
||||
"programs/rewards_api",
|
||||
"programs/storage",
|
||||
"programs/storage_api",
|
||||
"programs/vote",
|
||||
"programs/vote_api",
|
||||
"replicator",
|
||||
"sdk",
|
||||
"upload-perf",
|
||||
"vote-signer",
|
||||
"wallet",
|
||||
]
|
||||
exclude = ["programs/bpf/rust/noop"]
|
||||
|
106
README.md
@@ -1,5 +1,5 @@
|
||||
[](https://crates.io/crates/solana-core)
|
||||
[](https://docs.rs/solana-core)
|
||||
[](https://crates.io/crates/solana)
|
||||
[](https://docs.rs/solana)
|
||||
[](https://buildkite.com/solana-labs/solana/builds?branch=master)
|
||||
[](https://codecov.io/gh/solana-labs/solana)
|
||||
|
||||
@@ -26,43 +26,9 @@ Furthermore, and much to our surprise, it can be implemented using a mechanism t
|
||||
Architecture
|
||||
===
|
||||
|
||||
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
|
||||
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://solana-labs.github.io/book/).
|
||||
|
||||
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
|
||||
|
||||
Developing
|
||||
===
|
||||
@@ -75,10 +41,10 @@ Install rustc, cargo and rustfmt:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt
|
||||
$ rustup component add rustfmt-preview
|
||||
```
|
||||
|
||||
If your rustc version is lower than 1.37.0, please update it:
|
||||
If your rustc version is lower than 1.31.0, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@@ -100,7 +66,7 @@ $ cd solana
|
||||
Build
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
$ cargo build --all
|
||||
```
|
||||
|
||||
Then to run a minimal local cluster
|
||||
@@ -114,22 +80,31 @@ Testing
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
$ cargo test
|
||||
$ cargo test --all
|
||||
```
|
||||
|
||||
To emulate all the tests that will run on a Pull Request, run:
|
||||
|
||||
```bash
|
||||
$ ./ci/run-local.sh
|
||||
```
|
||||
|
||||
Local Testnet
|
||||
---
|
||||
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://solana-labs.github.io/book/getting-started.html).
|
||||
|
||||
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
|
||||
---
|
||||
|
||||
@@ -240,3 +174,5 @@ problem is solved by this code?" On the other hand, if a test does fail and you
|
||||
better way to solve the same problem, a Pull Request with your solution would most certainly be
|
||||
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
|
||||
send us that patch!
|
||||
|
||||
|
||||
|
171
RELEASE.md
@@ -59,146 +59,47 @@ There are three release channels that map to branches as follows:
|
||||
* beta - tracks the largest (and latest) `vX.Y` stabilization branch, more stable.
|
||||
* stable - tracks the second largest `vX.Y` stabilization branch, most stable.
|
||||
|
||||
## Steps to Create a Branch
|
||||
## Release Steps
|
||||
|
||||
### Create the new branch
|
||||
1. Check out the latest commit on `master` branch:
|
||||
```
|
||||
git fetch --all
|
||||
git checkout upstream/master
|
||||
```
|
||||
1. Determine the new branch name. The name should be "v" + the first 2 version fields
|
||||
### Changing channels
|
||||
|
||||
When cutting a new channel branch these pre-steps are required:
|
||||
|
||||
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. Create the new branch and push this branch to the `solana` repository:
|
||||
```
|
||||
git checkout -b <branchname>
|
||||
git push -u origin <branchname>
|
||||
```
|
||||
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
|
||||
|
||||
### Update master branch with the next version
|
||||
At this point, ci/channel-info.sh should show your freshly cut release branch as
|
||||
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
|
||||
1. After the new branch has been created and pushed, update the Cargo.toml files on **master** to the next semantic version (e.g. 0.9.0 -> 0.10.0) with:
|
||||
```
|
||||
scripts/increment-cargo-version.sh minor
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the `master` branch with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y to X.Y+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
1. Confirm that your freshly cut release branch is shown as `BETA_CHANNEL` and the previous release branch as `STABLE_CHANNEL`:
|
||||
```
|
||||
ci/channel_info.sh
|
||||
```
|
||||
### Updating channels (i.e. "making a release")
|
||||
|
||||
## Steps to Create a Release
|
||||
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
|
||||
### Create the Release Tag on GitHub
|
||||
|
||||
1. Go to [GitHub's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
1. 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`.
|
||||
1. If the Cargo.toml verion field is **0.12.3**, then the release tag must be **v0.12.3**
|
||||
1. Make sure the Target Branch field matches the branch you want to make a release on.
|
||||
1. If you want to release v0.12.0, the target branch must be v0.12
|
||||
1. If this is the first release on the branch (e.g. v0.13.**0**), paste in [this
|
||||
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md). Engineering Lead can provide summary contents for release notes if needed.
|
||||
1. Click "Save Draft", then confirm the release notes look good and the tag name and branch are correct. Go back into edit the release and click "Publish release" when ready.
|
||||
|
||||
### Update release branch with the next patch version
|
||||
|
||||
1. After the new release has been tagged, update the Cargo.toml files on **release branch** to the next semantic version (e.g. 0.9.0 -> 0.9.1) with:
|
||||
```
|
||||
scripts/increment-cargo-version.sh patch
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the **release branch** with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y.Z to X.Y.Z+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
|
||||
### Verify release automation success
|
||||
1. Go to [Solana Releases](https://github.com/solana-labs/solana/releases) and click on the latest release that you just published. Verify that all of the build artifacts are present. This can take up to 90 minutes after creating the tag.
|
||||
1. The `solana-secondary` Buildkite pipeline handles creating the binary tarballs and updated crates. Look for a job under the tag name of the release: https://buildkite.com/solana-labs/solana-secondary
|
||||
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
|
||||
|
||||
### Update documentation
|
||||
TODO: Documentation update procedure is WIP as we move to gitbook
|
||||
|
||||
Document the new recommended version by updating `book/src/running-replicator.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
|
||||
|
||||
#### Publish updated Book
|
||||
We maintain three copies of the "book" as official documentation:
|
||||
|
||||
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
|
||||
https://solana-labs.github.io/book/
|
||||
|
||||
2) "Book-edge" tracks the tip of the master branch and updates automatically.
|
||||
https://solana-labs.github.io/book-edge/
|
||||
|
||||
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
|
||||
https://solana-labs.github.io/book-beta/
|
||||
|
||||
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
|
||||
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
|
||||
```bash
|
||||
PUBLISH_BOOK_TAG=v0.16.6
|
||||
```
|
||||
https://buildkite.com/solana-labs/manual-update-book
|
||||
|
||||
### Update software on testnet.solana.com
|
||||
|
||||
The testnet running on testnet.solana.com is set to use a fixed release tag
|
||||
which is set in the Buildkite testnet-management pipeline.
|
||||
This tag needs to be updated and the testnet restarted after a new release
|
||||
tag is created.
|
||||
|
||||
#### Update testnet schedules
|
||||
|
||||
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
|
||||
testnet-management -> Pipeline Settings -> Schedules
|
||||
Or just click here:
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules
|
||||
|
||||
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
|
||||
|
||||
On each schedule:
|
||||
1. Set TESTNET_TAG environment variable to the desired release tag.
|
||||
1. Example, TESTNET_TAG=v0.13.2
|
||||
1. Set the Build Branch to the branch that TESTNET_TAG is from.
|
||||
1. Example: v0.13
|
||||
|
||||
#### Restart the testnet
|
||||
|
||||
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
|
||||
|
||||
1. Go to https://buildkite.com/solana-labs/testnet-management
|
||||
2. Click "New Build" and use the following settings, then click "Create Build"
|
||||
1. Commit: HEAD
|
||||
1. Branch: [channel branch as set in the schedules]
|
||||
1. Environment Variables:
|
||||
```
|
||||
TESTNET=testnet
|
||||
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
|
||||
TESTNET_OP=create-and-start
|
||||
```
|
||||
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
testnet.solana.com is available
|
||||
field in `/Cargo.toml` prefixed by `v` (ie, `<branchname>.X`).
|
||||
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. ...
|
||||
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 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
|
||||
|
2
banking_bench/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "0.19.0-pre0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
@@ -1,319 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate solana_core;
|
||||
extern crate crossbeam_channel;
|
||||
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::bank_forks::BankForks;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::PohRecorder;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_core::service::Service;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::iter;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn check_txs(
|
||||
receiver: &Arc<Receiver<WorkingBankEntry>>,
|
||||
ref_tx_count: usize,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
) -> bool {
|
||||
let mut total = 0;
|
||||
let now = Instant::now();
|
||||
let mut no_bank = false;
|
||||
loop {
|
||||
if let Ok((_bank, (entry, _tick_count))) = receiver.recv_timeout(Duration::from_millis(10))
|
||||
{
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
if now.elapsed().as_secs() > 60 {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
trace!("no bank");
|
||||
no_bank = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !no_bank {
|
||||
assert!(total >= ref_tx_count);
|
||||
}
|
||||
no_bank
|
||||
}
|
||||
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
|
||||
(0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
new.message.account_keys[1] = Pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Config {
|
||||
packets_per_batch: usize,
|
||||
chunk_len: usize,
|
||||
num_threads: usize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn get_transactions_index(&self, chunk_index: usize) -> usize {
|
||||
chunk_index * (self.chunk_len / self.num_threads) * self.packets_per_batch
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_as_usize(bytes: &[u8]) -> usize {
|
||||
bytes[0] as usize | (bytes[1] as usize) << 8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
const CHUNKS: usize = 8 * 2;
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisBlockInfo {
|
||||
genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_block);
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, txes);
|
||||
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_block.hash());
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_block.hash(),
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blocktree = Arc::new(
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blocktree);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let chunk_len = verified.len() / CHUNKS;
|
||||
let mut start = 0;
|
||||
|
||||
// This is so that the signal_receiver does not go out of scope after the closure.
|
||||
// If it is dropped before poh_service, then poh_service will error when
|
||||
// calling send() on the channel.
|
||||
let signal_receiver = Arc::new(signal_receiver);
|
||||
let signal_receiver2 = signal_receiver.clone();
|
||||
let mut total = 0;
|
||||
let mut tx_total = 0;
|
||||
let mut txs_processed = 0;
|
||||
let mut root = 1;
|
||||
let collector = Pubkey::new_rand();
|
||||
const ITERS: usize = 1_000;
|
||||
let config = Config {
|
||||
packets_per_batch: PACKETS_PER_BATCH,
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
for _ in 0..ITERS {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
for (i, v) in verified[start..start + chunk_len]
|
||||
.chunks(chunk_len / num_threads)
|
||||
.enumerate()
|
||||
{
|
||||
let mut byte = 0;
|
||||
let index = config.get_transactions_index(start + i);
|
||||
if index < transactions.len() {
|
||||
byte = bytes_as_usize(transactions[index].signatures[0].as_ref());
|
||||
}
|
||||
trace!(
|
||||
"sending... {}..{} {} v.len: {} sig: {} transactions.len: {} index: {}",
|
||||
start + i,
|
||||
start + chunk_len,
|
||||
timestamp(),
|
||||
v.len(),
|
||||
byte,
|
||||
transactions.len(),
|
||||
index,
|
||||
);
|
||||
for xv in v {
|
||||
sent += xv.0.packets.len();
|
||||
}
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
let start_tx_index = config.get_transactions_index(start);
|
||||
let end_tx_index = config.get_transactions_index(start + chunk_len);
|
||||
for tx in &transactions[start_tx_index..end_tx_index] {
|
||||
loop {
|
||||
if bank.get_signature_status(&tx.signatures[0]).is_some() {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(&signal_receiver2, txes / CHUNKS, &poh_recorder) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
bank.transaction_count(),
|
||||
txs_processed
|
||||
);
|
||||
assert!(txs_processed < bank.transaction_count());
|
||||
txs_processed = bank.transaction_count();
|
||||
tx_total += duration_as_us(&now.elapsed());
|
||||
|
||||
let mut poh_time = Measure::start("poh_time");
|
||||
poh_recorder.lock().unwrap().reset(
|
||||
bank.last_blockhash(),
|
||||
bank.slot(),
|
||||
Some((bank.slot(), bank.slot() + 1)),
|
||||
);
|
||||
poh_time.stop();
|
||||
|
||||
let mut new_bank_time = Measure::start("new_bank");
|
||||
let new_bank = Bank::new_from_parent(&bank, &collector, bank.slot() + 1);
|
||||
new_bank_time.stop();
|
||||
|
||||
let mut insert_time = Measure::start("insert_time");
|
||||
bank_forks.insert(new_bank);
|
||||
bank = bank_forks.working_bank();
|
||||
insert_time.stop();
|
||||
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
bank_forks.set_root(root, &None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
"new_bank_time: {}us insert_time: {}us poh_time: {}us",
|
||||
new_bank_time.as_us(),
|
||||
insert_time.as_us(),
|
||||
poh_time.as_us(),
|
||||
);
|
||||
} else {
|
||||
tx_total += duration_as_us(&now.elapsed());
|
||||
}
|
||||
|
||||
// This signature clear may not actually clear the signatures
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
total += duration_as_us(&now.elapsed());
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
txes / CHUNKS,
|
||||
sent,
|
||||
);
|
||||
|
||||
if bank.slot() > 0 && bank.slot() % 16 == 0 {
|
||||
for tx in transactions.iter_mut() {
|
||||
tx.message.recent_blockhash = bank.last_blockhash();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
}
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
total / ITERS as u64,
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
tx_total / ITERS as u64,
|
||||
);
|
||||
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
poh_service.join().unwrap();
|
||||
sleep(Duration::from_secs(1));
|
||||
debug!("waited for poh_service");
|
||||
}
|
||||
let _unused = Blocktree::destroy(&ledger_path);
|
||||
}
|
4
bench-exchange/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.19.0-pre0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde = "1.0.101"
|
||||
serde_derive = "1.0.101"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.19.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.19.0-pre0" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.0"
|
@@ -1,479 +0,0 @@
|
||||
# token-exchange
|
||||
Solana Token Exchange Bench
|
||||
|
||||
If you can't wait; jump to [Running the exchange](#Running-the-exchange) to
|
||||
learn how to start and interact with the exchange.
|
||||
|
||||
### Table of Contents
|
||||
[Overview](#Overview)<br>
|
||||
[Premise](#Premise)<br>
|
||||
[Exchange startup](#Exchange-startup)<br>
|
||||
[Order Requests](#Trade-requests)<br>
|
||||
[Order Cancellations](#Trade-cancellations)<br>
|
||||
[Trade swap](#Trade-swap)<br>
|
||||
[Exchange program operations](#Exchange-program-operations)<br>
|
||||
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
|
||||
[Investor strategies](#Investor-strategies)<br>
|
||||
[Running the exchange](#Running-the-exchange)<br>
|
||||
|
||||
## Overview
|
||||
|
||||
An exchange is a marketplace where one asset can be traded for another. This
|
||||
demo demonstrates one way to host an exchange on the Solana blockchain by
|
||||
emulating a currency exchange.
|
||||
|
||||
The assets are virtual tokens held by investors who may post order requests to
|
||||
the exchange. A Matcher monitors the exchange and posts swap requests for
|
||||
matching orders. All the transactions can execute concurrently.
|
||||
|
||||
## Premise
|
||||
|
||||
- Exchange
|
||||
- An exchange is a marketplace where one asset can be traded for another.
|
||||
The exchange in this demo is the on-chain program that implements the
|
||||
tokens and the policies for trading those tokens.
|
||||
- Token
|
||||
- A virtual asset that can be owned, traded, and holds virtual intrinsic value
|
||||
compared to other assets. There are four types of tokens in this demo, A,
|
||||
B, C, D. Each one may be traded for another.
|
||||
- Token account
|
||||
- An account owned by the exchange that holds a quantity of one type of token.
|
||||
- Account request
|
||||
- A request to create a token account
|
||||
- Token request
|
||||
- A request to deposit tokens of a particular type into a token account.
|
||||
- Asset pair
|
||||
- A struct with fields Base and Quote, representing the two assets which make up a
|
||||
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
|
||||
numerator and the Quote is the denominator for pricing purposes.
|
||||
- Order side
|
||||
- Describes which side of the market an investor wants to place a trade on. Options
|
||||
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
|
||||
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
|
||||
for the Quote asset.
|
||||
- Price ratio
|
||||
- An expression of the relative prices of two tokens. Calculated with the Base
|
||||
Asset as the numerator and the Quote Asset as the denominator. Ratios are
|
||||
represented as fixed point numbers. The fixed point scaler is defined in
|
||||
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
|
||||
- Order request
|
||||
- A Solana transaction sent by a trader to the exchange to submit an order.
|
||||
Order requests are made up of the token pair, the order side (bid or ask),
|
||||
quantity of the primary token, the price ratio, and the two token accounts
|
||||
to be credited/deducted. An example trade request looks like "T AB 5 2"
|
||||
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
deducted and 10 B tokens credited to the trade initiator's token accounts.
|
||||
Successful order requests result in an order.
|
||||
- Order
|
||||
- The result of a successful order request. orders are stored in
|
||||
accounts owned by the submitter of the order request. They can only be
|
||||
canceled by their owner but can be used by anyone in a trade swap. They
|
||||
contain the same information as the order request.
|
||||
- Price spread
|
||||
- The difference between the two matching orders. The spread is the
|
||||
profit of the Matcher initiating the swap request.
|
||||
- Match requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Match request
|
||||
- A request to fill two complementary orders (bid/ask), resulting if successful,
|
||||
in a trade being created.
|
||||
- Trade
|
||||
- A successful trade is created from two matching orders that meet
|
||||
swap requirements which are submitted in a Match Request by a Matcher and
|
||||
executed by the exchange. A trade may not wholly satisfy one or both of the
|
||||
orders in which case the orders are adjusted appropriately. Upon execution,
|
||||
tokens are distributed to the traders' accounts and any overlap or
|
||||
"negative spread" between orders is deposited into the Matcher's profit
|
||||
account. All successful trades are recorded in the data of a new solana
|
||||
account for posterity.
|
||||
- Investor
|
||||
- Individual investors who hold a number of tokens and wish to trade them on
|
||||
the exchange. Investors operate as Solana thin clients who own a set of
|
||||
accounts containing tokens and/or order requests. Investors post
|
||||
transactions to the exchange in order to request tokens and post or cancel
|
||||
order requests.
|
||||
- Matcher
|
||||
- An agent who facilitates trading between investors. Matchers operate as
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Matcher issues a swap request to the exchange.
|
||||
Matchers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Matchers also
|
||||
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
|
||||
information on demand via a public network port.
|
||||
- Transaction fees
|
||||
- Solana transaction fees are paid for by the transaction submitters who are
|
||||
the Investors and Matchers.
|
||||
|
||||
## Exchange startup
|
||||
|
||||
The exchange is up and running when it reaches a state where it can take
|
||||
investors' trades and Matchers' match requests. To achieve this state the
|
||||
following must occur in order:
|
||||
|
||||
- Start the Solana blockchain
|
||||
- Start the thin-client
|
||||
- The Matcher subscribes to change notifications for all the accounts owned by
|
||||
the exchange program id. The subscription is managed via Solana's JSON RPC
|
||||
interface.
|
||||
- The Matcher starts responding to queries for bid/ask price and OHLCV
|
||||
|
||||
The Matcher responding successfully to price and OHLCV requests is the signal to
|
||||
the investors that trades submitted after that point will be analyzed. <!--This
|
||||
is not ideal, and instead investors should be able to submit trades at any time,
|
||||
and the Matcher could come and go without missing a trade. One way to achieve
|
||||
this is for the Matcher to read the current state of all accounts looking for all
|
||||
open orders.-->
|
||||
|
||||
Investors will initially query the exchange to discover their current balance
|
||||
for each type of token. If the investor does not already have an account for
|
||||
each type of token, they will submit account requests. Matcher as well will
|
||||
request accounts to hold the tokens they earn by initiating trade swaps.
|
||||
|
||||
```rust
|
||||
/// Supported token types
|
||||
pub enum Token {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
}
|
||||
|
||||
/// Supported token pairs
|
||||
pub enum TokenPair {
|
||||
AB,
|
||||
AC,
|
||||
AD,
|
||||
BC,
|
||||
BD,
|
||||
CD,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
}
|
||||
|
||||
/// Token accounts are populated with this structure
|
||||
pub struct TokenAccountInfo {
|
||||
/// Investor who owns this account
|
||||
pub owner: Pubkey,
|
||||
/// Current number of tokens this account holds
|
||||
pub tokens: Tokens,
|
||||
}
|
||||
```
|
||||
|
||||
For this demo investors or Matcher can request more tokens from the exchange at
|
||||
any time by submitting token requests. In non-demos, an exchange of this type
|
||||
would provide another way to exchange a 3rd party asset into tokens.
|
||||
|
||||
To request tokens, investors submit transfer requests:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
}
|
||||
```
|
||||
|
||||
## Order Requests
|
||||
|
||||
When an investor decides to exchange a token of one type for another, they
|
||||
submit a transaction to the Solana Blockchain containing an order request, which,
|
||||
if successful, is turned into an order. orders do not expire but are
|
||||
cancellable. <!-- orders should have a timestamp to enable trade
|
||||
expiration --> When an order is created, tokens are deducted from a token
|
||||
account and the order acts as an escrow. The tokens are held until the
|
||||
order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
of `tokens` are deducted from the primary account, if `From` then `tokens`
|
||||
multiplied by `price` are deducted from the secondary account. orders are
|
||||
no longer valid when the number of `tokens` goes to zero, at which point they
|
||||
can no longer be used. <!-- Could support refilling orders, so order
|
||||
accounts are refilled rather than accumulating -->
|
||||
|
||||
```rust
|
||||
/// Direction of the exchange between two tokens in a pair
|
||||
pub enum Direction {
|
||||
/// Trade first token type (primary) in the pair 'To' the second
|
||||
To,
|
||||
/// Trade first token type in the pair 'From' the second (secondary)
|
||||
From,
|
||||
}
|
||||
|
||||
pub struct OrderRequestInfo {
|
||||
/// Direction of trade
|
||||
pub direction: Direction,
|
||||
|
||||
/// Token pair to trade
|
||||
pub pair: TokenPair,
|
||||
|
||||
/// Number of tokens to exchange; refers to the primary or the secondary depending on the direction
|
||||
pub tokens: u64,
|
||||
|
||||
/// The price ratio the primary price over the secondary price. The primary price is fixed
|
||||
/// and equal to the variable `SCALER`.
|
||||
pub price: u64,
|
||||
|
||||
/// Token account to deposit tokens on successful swap
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
}
|
||||
|
||||
/// Trade accounts are populated with this structure
|
||||
pub struct TradeOrderInfo {
|
||||
/// Owner of the order
|
||||
pub owner: Pubkey,
|
||||
/// Direction of the exchange
|
||||
pub direction: Direction,
|
||||
/// Token pair indicating two tokens to exchange, first is primary
|
||||
pub pair: TokenPair,
|
||||
/// Number of tokens to exchange; primary or secondary depending on direction
|
||||
pub tokens: u64,
|
||||
/// Scaled price of the secondary token given the primary is equal to the scale value
|
||||
/// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens
|
||||
pub price: u64,
|
||||
/// account which the tokens were source from. The trade account holds the tokens in escrow
|
||||
/// until either one or more part of a swap or the trade is canceled.
|
||||
pub src_account: Pubkey,
|
||||
/// account which the tokens the tokens will be deposited into on a successful trade
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
```
|
||||
|
||||
## Order cancellations
|
||||
|
||||
An investor may cancel a trade at anytime, but only trades they own. If the
|
||||
cancellation is successful, any tokens held in escrow are returned to the
|
||||
account from which they came.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
}
|
||||
```
|
||||
|
||||
## Trade swaps
|
||||
|
||||
The Matcher is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The order table is used to identify
|
||||
matching orders which could be fulfilled. When a match is found the
|
||||
Matcher should issue a swap request. Swap requests may not satisfy the entirety
|
||||
of either order, but the exchange will greedily fulfill it. Any leftover tokens
|
||||
in either account will keep the order valid for further swap requests in
|
||||
the future.
|
||||
|
||||
Matching orders are defined by the following swap requirements:
|
||||
|
||||
- Opposite polarity (one `To` and one `From`)
|
||||
- Operate on the same token pair
|
||||
- The price ratio of the `From` order is greater than or equal to the `To` order
|
||||
- There are sufficient tokens to perform the trade
|
||||
|
||||
Orders can be written in the following format:
|
||||
|
||||
`investor direction pair quantity price-ratio`
|
||||
|
||||
For example:
|
||||
|
||||
- `1 T AB 2 1`
|
||||
- Investor 1 wishes to exchange 2 A tokens to B tokens at a ratio of 1 A to 1
|
||||
B
|
||||
- `2 F AC 6 1.2`
|
||||
- Investor 2 wishes to exchange A tokens from 6 B tokens at a ratio of 1 A
|
||||
from 1.2 B
|
||||
|
||||
An order table could look something like the following. Notice how the columns
|
||||
are sorted low to high and high to low, respectively. Prices are dramatic and
|
||||
whole for clarity.
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 2 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 3 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 4 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
As part of a successful swap request, the exchange will credit tokens to the
|
||||
Matcher's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Matcher's profit for initiating the trade.
|
||||
|
||||
The Matcher would initiate the following swap on the order table above:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
|
||||
- Matcher takes 8 B tokens as profit
|
||||
|
||||
Both row 1 trades are fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Row 1 From is not fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 6 6 | 2 F AB 1 8 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
|
||||
| 4 | | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Row 1 To is now fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 5 6 | 2 F AB 2 7 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following last swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 3 6 | 2 F AB 3 5 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 1 5 |
|
||||
| 3 | 1 T AB 2 10 | |
|
||||
|
||||
At this point the lowest To's price is larger than the largest From's price so
|
||||
no more swaps would be initiated until new orders came in.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
|
||||
/// Swap accounts are populated with this structure
|
||||
pub struct TradeSwapInfo {
|
||||
/// Pair swapped
|
||||
pub pair: TokenPair,
|
||||
/// `To` order
|
||||
pub to_trade_order: Pubkey,
|
||||
/// `From` order
|
||||
pub from_trade_order: Pubkey,
|
||||
/// Number of primary tokens exchanged
|
||||
pub primary_tokens: u64,
|
||||
/// Price the primary tokens were exchanged for
|
||||
pub primary_price: u64,
|
||||
/// Number of secondary tokens exchanged
|
||||
pub secondary_tokens: u64,
|
||||
/// Price the secondary tokens were exchanged for
|
||||
pub secondary_price: u64,
|
||||
}
|
||||
```
|
||||
|
||||
## Exchange program operations
|
||||
|
||||
Putting all the commands together from above, the following operations will be
|
||||
supported by the on-chain exchange program:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
```
|
||||
|
||||
## Quotes and OHLCV
|
||||
|
||||
The Matcher will provide current bid/ask price quotes based on trade actively and
|
||||
also provide OHLCV based on some time window. The details of how the bid/ask
|
||||
price quotes are calculated are yet to be decided.
|
||||
|
||||
## Investor strategies
|
||||
|
||||
To make a compelling demo, the investors needs to provide interesting trade
|
||||
behavior. Something as simple as a randomly twiddled baseline would be a
|
||||
minimum starting point.
|
||||
|
||||
## Running the exchange
|
||||
|
||||
The exchange bench posts trades and swaps matches as fast as it can.
|
||||
|
||||
You might want to bump the duration up
|
||||
to 60 seconds and the batch size to 1000 for better numbers. You can modify those
|
||||
in client_demo/src/demo.rs::test_exchange_local_cluster.
|
||||
|
||||
The following command runs the bench:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
||||
|
||||
To also see the cluster messages:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
@@ -1,971 +0,0 @@
|
||||
#![allow(clippy::useless_attribute)]
|
||||
|
||||
use crate::order_book::*;
|
||||
use itertools::izip;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_client::perf_utils::{sample_txs, SampleStats};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_drone::drone::request_airdrop_transaction;
|
||||
use solana_exchange_api::exchange_instruction;
|
||||
use solana_exchange_api::exchange_state::*;
|
||||
use solana_exchange_api::id;
|
||||
use solana_genesis::PrimordialAccountDetails;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_sdk::client::Client;
|
||||
use solana_sdk::client::SyncClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::timing::{duration_as_ms, duration_as_s};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_sdk::{system_instruction, system_program};
|
||||
use std::cmp;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::{sleep, Builder};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// TODO Chunk length as specified results in a bunch of failures, divide by 10 helps...
|
||||
// Assume 4MB network buffers, and 512 byte packets
|
||||
const FUND_CHUNK_LEN: usize = 4 * 1024 * 1024 / 512;
|
||||
|
||||
// Maximum system transfers per transaction
|
||||
const MAX_TRANSFERS_PER_TX: u64 = 4;
|
||||
|
||||
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<Transaction>>>>;
|
||||
|
||||
pub struct Config {
|
||||
pub identity: Keypair,
|
||||
pub threads: 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 read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
identity: Keypair::new(),
|
||||
threads: 4,
|
||||
duration: Duration::new(u64::max_value(), 0),
|
||||
transfer_delay: 0,
|
||||
fund_amount: 100_000,
|
||||
batch_size: 10,
|
||||
chunk_size: 10,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_client_accounts_file(
|
||||
client_ids_and_stake_file: &str,
|
||||
batch_size: usize,
|
||||
account_groups: usize,
|
||||
fund_amount: u64,
|
||||
) {
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_KEYPAIR_GROUPS: u64 = 2;
|
||||
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
|
||||
|
||||
let keypairs = generate_keypairs(total_keys);
|
||||
|
||||
let mut accounts = HashMap::new();
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
PrimordialAccountDetails {
|
||||
balance: fund_amount,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
data: String::new(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
|
||||
where
|
||||
T: 'static + Client + Send + Sync,
|
||||
{
|
||||
let Config {
|
||||
identity,
|
||||
threads,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
} = config;
|
||||
|
||||
info!(
|
||||
"Exchange client: threads {} duration {} fund_amount {}",
|
||||
threads,
|
||||
duration_as_s(&duration),
|
||||
fund_amount
|
||||
);
|
||||
info!(
|
||||
"Exchange client: transfer delay {} batch size {} chunk size {}",
|
||||
transfer_delay, batch_size, chunk_size
|
||||
);
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_KEYPAIR_GROUPS: u64 = 2;
|
||||
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
|
||||
|
||||
let mut signer_keypairs = if read_from_client_file {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
let accounts: HashMap<String, PrimordialAccountDetails> =
|
||||
serde_yaml::from_reader(file).unwrap();
|
||||
accounts
|
||||
.into_iter()
|
||||
.map(|(keypair, _)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
Keypair::from_bytes(&bytes).unwrap()
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
info!("Generating {:?} signer keys", total_keys);
|
||||
generate_keypairs(total_keys)
|
||||
};
|
||||
|
||||
let trader_signers: Vec<_> = signer_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
let swapper_signers: Vec<_> = signer_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
|
||||
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
|
||||
let client = clients[0].as_ref();
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Fund trader accounts");
|
||||
fund_keys(client, &identity, &trader_signers, fund_amount);
|
||||
info!("Fund swapper accounts");
|
||||
fund_keys(client, &identity, &swapper_signers, fund_amount);
|
||||
}
|
||||
|
||||
info!("Generating {:?} account keys", total_keys);
|
||||
let mut account_keypairs = generate_keypairs(total_keys);
|
||||
let src_pubkeys: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
let profit_pubkeys: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
info!("Create {:?} source token accounts", src_pubkeys.len());
|
||||
create_token_accounts(client, &trader_signers, &src_pubkeys);
|
||||
info!("Create {:?} profit token accounts", profit_pubkeys.len());
|
||||
create_token_accounts(client, &swapper_signers, &profit_pubkeys);
|
||||
|
||||
// Collect the max transaction rate and total tx count seen (single node only)
|
||||
let sample_stats = Arc::new(RwLock::new(Vec::new()));
|
||||
let sample_period = 1; // in seconds
|
||||
info!("Sampling clients for tps every {} s", sample_period);
|
||||
info!(
|
||||
"Requesting and swapping trades with {} ms delay per thread...",
|
||||
transfer_delay
|
||||
);
|
||||
|
||||
let exit_signal = Arc::new(AtomicBool::new(false));
|
||||
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let total_txs_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 total_txs_sent_count = total_txs_sent_count.clone();
|
||||
let client = clients[0].clone();
|
||||
Builder::new()
|
||||
.name("solana-exchange-transfer".to_string())
|
||||
.spawn(move || {
|
||||
do_tx_transfers(&exit_signal, &shared_txs, &total_txs_sent_count, &client)
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
trace!("Start swapper thread");
|
||||
let (swapper_sender, swapper_receiver) = channel();
|
||||
let swapper_thread = {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let shared_txs = shared_txs.clone();
|
||||
let client = clients[0].clone();
|
||||
Builder::new()
|
||||
.name("solana-exchange-swapper".to_string())
|
||||
.spawn(move || {
|
||||
swapper(
|
||||
&exit_signal,
|
||||
&swapper_receiver,
|
||||
&shared_txs,
|
||||
&swapper_signers,
|
||||
&profit_pubkeys,
|
||||
transfer_delay,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
&client,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
trace!("Start trader thread");
|
||||
let trader_thread = {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let shared_txs = shared_txs.clone();
|
||||
let client = clients[0].clone();
|
||||
Builder::new()
|
||||
.name("solana-exchange-trader".to_string())
|
||||
.spawn(move || {
|
||||
trader(
|
||||
&exit_signal,
|
||||
&swapper_sender,
|
||||
&shared_txs,
|
||||
&trader_signers,
|
||||
&src_pubkeys,
|
||||
transfer_delay,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
&client,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let sample_threads: Vec<_> = clients
|
||||
.iter()
|
||||
.map(|client| {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let sample_stats = sample_stats.clone();
|
||||
let client = client.clone();
|
||||
Builder::new()
|
||||
.name("solana-exchange-sample".to_string())
|
||||
.spawn(move || sample_txs(&exit_signal, &sample_stats, sample_period, &client))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
sleep(duration);
|
||||
|
||||
info!("Stopping threads");
|
||||
exit_signal.store(true, Ordering::Relaxed);
|
||||
info!("Wait for trader thread");
|
||||
let _ = trader_thread.join();
|
||||
info!("Waiting for swapper thread");
|
||||
let _ = swapper_thread.join();
|
||||
info!("Wait for tx threads");
|
||||
for t in s_threads {
|
||||
let _ = t.join();
|
||||
}
|
||||
info!("Wait for sample threads");
|
||||
for t in sample_threads {
|
||||
let _ = t.join();
|
||||
}
|
||||
|
||||
compute_and_report_stats(
|
||||
&sample_stats,
|
||||
total_txs_sent_count.load(Ordering::Relaxed) as u64,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_tx_transfers<T>(
|
||||
exit_signal: &Arc<AtomicBool>,
|
||||
shared_txs: &SharedTransactions,
|
||||
total_txs_sent_count: &Arc<AtomicUsize>,
|
||||
client: &Arc<T>,
|
||||
) where
|
||||
T: Client,
|
||||
{
|
||||
loop {
|
||||
let txs;
|
||||
{
|
||||
let mut shared_txs_wl = shared_txs.write().unwrap();
|
||||
txs = shared_txs_wl.pop_front();
|
||||
}
|
||||
if let Some(txs0) = txs {
|
||||
let n = txs0.len();
|
||||
|
||||
let now = Instant::now();
|
||||
for tx in txs0 {
|
||||
client.async_send_transaction(tx).expect("Transfer");
|
||||
}
|
||||
let duration = now.elapsed();
|
||||
|
||||
total_txs_sent_count.fetch_add(n, Ordering::Relaxed);
|
||||
datapoint_info!(
|
||||
"bench-exchange-do_tx_transfers",
|
||||
("duration", duration_as_ms(&duration), i64),
|
||||
("count", n, i64)
|
||||
);
|
||||
}
|
||||
if exit_signal.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TradeInfo {
|
||||
trade_account: Pubkey,
|
||||
order_info: OrderInfo,
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn swapper<T>(
|
||||
exit_signal: &Arc<AtomicBool>,
|
||||
receiver: &Receiver<Vec<TradeInfo>>,
|
||||
shared_txs: &SharedTransactions,
|
||||
signers: &[Arc<Keypair>],
|
||||
profit_pubkeys: &[Pubkey],
|
||||
transfer_delay: u64,
|
||||
batch_size: usize,
|
||||
chunk_size: usize,
|
||||
account_groups: usize,
|
||||
client: &Arc<T>,
|
||||
) where
|
||||
T: Client,
|
||||
{
|
||||
let mut order_book = OrderBook::default();
|
||||
let mut account_group: usize = 0;
|
||||
|
||||
let mut txs = 0;
|
||||
let mut total_txs = 0;
|
||||
let mut now = Instant::now();
|
||||
let start_time = now;
|
||||
let mut total_elapsed = start_time.elapsed();
|
||||
|
||||
// Chunks may have been dropped and we don't want to wait a long time
|
||||
// for each time, Back-off each time we fail to confirm a chunk
|
||||
const CHECK_TX_TIMEOUT_MAX_MS: u64 = 15000;
|
||||
const CHECK_TX_DELAY_MS: u64 = 100;
|
||||
let mut max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
|
||||
|
||||
// If we dump too many chunks maybe we are just waiting on a back-log
|
||||
// rather than a series of dropped packets, reset to max waits
|
||||
const MAX_DUMPS: u64 = 50;
|
||||
let mut dumps = 0;
|
||||
|
||||
'outer: loop {
|
||||
if let Ok(trade_infos) = receiver.try_recv() {
|
||||
let mut tries = 0;
|
||||
let mut trade_index = 0;
|
||||
while client
|
||||
.get_balance(&trade_infos[trade_index].trade_account)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
tries += 1;
|
||||
if tries >= max_tries {
|
||||
if exit_signal.load(Ordering::Relaxed) {
|
||||
break 'outer;
|
||||
}
|
||||
error!("Give up and dump batch");
|
||||
if dumps >= MAX_DUMPS {
|
||||
error!("Max batches dumped, reset wait back-off");
|
||||
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
|
||||
dumps = 0;
|
||||
} else {
|
||||
dumps += 1;
|
||||
max_tries /= 2;
|
||||
}
|
||||
continue 'outer;
|
||||
}
|
||||
debug!("{} waiting for trades batch to clear", tries);
|
||||
sleep(Duration::from_millis(CHECK_TX_DELAY_MS));
|
||||
trade_index = thread_rng().gen_range(0, trade_infos.len());
|
||||
}
|
||||
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
|
||||
dumps = 0;
|
||||
|
||||
trade_infos.iter().for_each(|info| {
|
||||
order_book
|
||||
.push(info.trade_account, info.order_info)
|
||||
.expect("Failed to push to order_book");
|
||||
});
|
||||
let mut swaps = Vec::new();
|
||||
while let Some((to, from)) = order_book.pop() {
|
||||
swaps.push((to, from));
|
||||
if swaps.len() >= batch_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let swaps_size = swaps.len();
|
||||
|
||||
let mut to_swap = vec![];
|
||||
let start = account_group * swaps_size as usize;
|
||||
let end = account_group * swaps_size as usize + batch_size as usize;
|
||||
for (signer, swap, profit) in izip!(
|
||||
signers[start..end].iter(),
|
||||
swaps,
|
||||
profit_pubkeys[start..end].iter(),
|
||||
) {
|
||||
to_swap.push((signer, swap, profit));
|
||||
}
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.expect("Failed to get blockhash");
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
.map(|(signer, swap, profit)| {
|
||||
let s: &Keypair = &signer;
|
||||
let owner = &signer.pubkey();
|
||||
Transaction::new_signed_instructions(
|
||||
&[s],
|
||||
vec![exchange_instruction::swap_request(
|
||||
owner,
|
||||
&swap.0.pubkey,
|
||||
&swap.1.pubkey,
|
||||
&profit,
|
||||
)],
|
||||
blockhash,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
txs += to_swap_txs.len() as u64;
|
||||
total_txs += to_swap_txs.len() as u64;
|
||||
total_elapsed = start_time.elapsed();
|
||||
let duration = now.elapsed();
|
||||
if duration_as_s(&duration) >= 1_f32 {
|
||||
now = Instant::now();
|
||||
let tps = txs as f32 / duration_as_s(&duration);
|
||||
info!(
|
||||
"Swapper {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
|
||||
tps,
|
||||
txs,
|
||||
total_txs,
|
||||
total_elapsed.as_secs(),
|
||||
);
|
||||
txs = 0;
|
||||
}
|
||||
|
||||
datapoint_info!("bench-exchange-swaps", ("count", to_swap_txs.len(), i64));
|
||||
|
||||
let chunks: Vec<_> = to_swap_txs.chunks(chunk_size).collect();
|
||||
{
|
||||
let mut shared_txs_wl = shared_txs.write().unwrap();
|
||||
for chunk in chunks {
|
||||
shared_txs_wl.push_back(chunk.to_vec());
|
||||
}
|
||||
}
|
||||
// Throttle the swapper so it doesn't try to catchup unbridled
|
||||
sleep(Duration::from_millis(transfer_delay / 2));
|
||||
}
|
||||
|
||||
if exit_signal.load(Ordering::Relaxed) {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"Swapper sent {} at {:9.2} TPS",
|
||||
total_txs,
|
||||
total_txs as f32 / duration_as_s(&total_elapsed)
|
||||
);
|
||||
assert_eq!(
|
||||
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn trader<T>(
|
||||
exit_signal: &Arc<AtomicBool>,
|
||||
sender: &Sender<Vec<TradeInfo>>,
|
||||
shared_txs: &SharedTransactions,
|
||||
signers: &[Arc<Keypair>],
|
||||
srcs: &[Pubkey],
|
||||
transfer_delay: u64,
|
||||
batch_size: usize,
|
||||
chunk_size: usize,
|
||||
account_groups: usize,
|
||||
client: &Arc<T>,
|
||||
) where
|
||||
T: Client,
|
||||
{
|
||||
// TODO Hard coded for now
|
||||
let pair = AssetPair::default();
|
||||
let tokens = 1;
|
||||
let price = 1000;
|
||||
let mut account_group: usize = 0;
|
||||
|
||||
let mut txs = 0;
|
||||
let mut total_txs = 0;
|
||||
let mut now = Instant::now();
|
||||
let start_time = now;
|
||||
let mut total_elapsed = start_time.elapsed();
|
||||
|
||||
loop {
|
||||
let trade_keys = generate_keypairs(batch_size as u64);
|
||||
|
||||
let mut trades = vec![];
|
||||
let mut trade_infos = vec![];
|
||||
let start = account_group * batch_size as usize;
|
||||
let end = account_group * batch_size as usize + batch_size as usize;
|
||||
let mut side = OrderSide::Ask;
|
||||
for (signer, trade, src) in izip!(
|
||||
signers[start..end].iter(),
|
||||
trade_keys,
|
||||
srcs[start..end].iter(),
|
||||
) {
|
||||
side = if side == OrderSide::Ask {
|
||||
OrderSide::Bid
|
||||
} else {
|
||||
OrderSide::Ask
|
||||
};
|
||||
let order_info = OrderInfo {
|
||||
/// Owner of the trade order
|
||||
owner: Pubkey::default(), // don't care
|
||||
side,
|
||||
pair,
|
||||
tokens,
|
||||
price,
|
||||
tokens_settled: 0,
|
||||
};
|
||||
trade_infos.push(TradeInfo {
|
||||
trade_account: trade.pubkey(),
|
||||
order_info,
|
||||
});
|
||||
trades.push((signer, trade.pubkey(), side, src));
|
||||
}
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.expect("Failed to get blockhash");
|
||||
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
let trades_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(signer, trade, side, src)| {
|
||||
let s: &Keypair = &signer;
|
||||
let owner = &signer.pubkey();
|
||||
let space = mem::size_of::<ExchangeState>() as u64;
|
||||
Transaction::new_signed_instructions(
|
||||
&[s],
|
||||
vec![
|
||||
system_instruction::create_account(owner, trade, 1, space, &id()),
|
||||
exchange_instruction::trade_request(
|
||||
owner, trade, *side, pair, tokens, price, src,
|
||||
),
|
||||
],
|
||||
blockhash,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
{
|
||||
txs += chunk_size as u64;
|
||||
total_txs += chunk_size as u64;
|
||||
total_elapsed = start_time.elapsed();
|
||||
let duration = now.elapsed();
|
||||
if duration_as_s(&duration) >= 1_f32 {
|
||||
now = Instant::now();
|
||||
let tps = txs as f32 / duration_as_s(&duration);
|
||||
info!(
|
||||
"Trader {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
|
||||
tps,
|
||||
txs,
|
||||
total_txs,
|
||||
total_elapsed.as_secs(),
|
||||
);
|
||||
txs = 0;
|
||||
}
|
||||
|
||||
datapoint_info!("bench-exchange-trades", ("count", trades_txs.len(), i64));
|
||||
|
||||
{
|
||||
let mut shared_txs_wl = shared_txs
|
||||
.write()
|
||||
.expect("Failed to send tx to transfer threads");
|
||||
shared_txs_wl.push_back(trades_txs);
|
||||
}
|
||||
}
|
||||
if transfer_delay > 0 {
|
||||
sleep(Duration::from_millis(transfer_delay));
|
||||
}
|
||||
});
|
||||
|
||||
if exit_signal.load(Ordering::Relaxed) {
|
||||
info!(
|
||||
"Trader sent {} at {:9.2} TPS",
|
||||
total_txs,
|
||||
total_txs as f32 / duration_as_s(&total_elapsed)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO chunk the trade infos and send them when the batch is sent
|
||||
sender
|
||||
.send(trade_infos)
|
||||
.expect("Failed to send trades to swapper");
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transfer<T>(sync_client: &T, tx: &Transaction) -> bool
|
||||
where
|
||||
T: SyncClient + ?Sized,
|
||||
{
|
||||
for s in &tx.signatures {
|
||||
if let Ok(Some(r)) = sync_client.get_signature_status(s) {
|
||||
match r {
|
||||
Ok(_) => {
|
||||
return true;
|
||||
}
|
||||
Err(e) => {
|
||||
info!("error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
client: &T,
|
||||
tx: &Transaction,
|
||||
amount: u64,
|
||||
) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client.get_balance(a).unwrap_or(0) >= amount {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
let total = lamports * (dests.len() as u64 + 1);
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
|
||||
|
||||
info!(
|
||||
" Funding {} keys with {} lamports each",
|
||||
dests.len(),
|
||||
lamports
|
||||
);
|
||||
while !notfunded.is_empty() {
|
||||
if funded.is_empty() {
|
||||
panic!("No funded accounts left to fund remaining");
|
||||
}
|
||||
let mut new_funded: Vec<(&Keypair, u64)> = vec![];
|
||||
let mut to_fund = vec![];
|
||||
debug!(" Creating from... {}", funded.len());
|
||||
for f in &mut funded {
|
||||
let max_units = cmp::min(
|
||||
cmp::min(notfunded.len() as u64, MAX_TRANSFERS_PER_TX),
|
||||
(f.1 - lamports) / lamports,
|
||||
);
|
||||
if max_units == 0 {
|
||||
continue;
|
||||
}
|
||||
let per_unit = ((f.1 - lamports) / lamports / max_units) * lamports;
|
||||
f.1 -= per_unit * max_units;
|
||||
let start = notfunded.len() - max_units as usize;
|
||||
let moves: Vec<_> = notfunded[start..]
|
||||
.iter()
|
||||
.map(|k| (k.pubkey(), per_unit))
|
||||
.collect();
|
||||
notfunded[start..]
|
||||
.iter()
|
||||
.for_each(|k| new_funded.push((k, per_unit)));
|
||||
notfunded.truncate(start);
|
||||
if !moves.is_empty() {
|
||||
to_fund.push((f.0, moves));
|
||||
}
|
||||
}
|
||||
|
||||
to_fund.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
|
||||
#[allow(clippy::clone_double_ref)] // sigh
|
||||
let mut to_fund_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(k, m)| {
|
||||
(
|
||||
k.clone(),
|
||||
Transaction::new_unsigned_instructions(system_instruction::transfer_many(
|
||||
&k.pubkey(),
|
||||
&m,
|
||||
)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut retries = 0;
|
||||
let amount = chunk[0].1[0].1;
|
||||
while !to_fund_txs.is_empty() {
|
||||
let receivers = to_fund_txs
|
||||
.iter()
|
||||
.fold(0, |len, (_, tx)| len + tx.message().instructions.len());
|
||||
|
||||
debug!(
|
||||
" {} to {} in {} txs",
|
||||
if retries == 0 {
|
||||
" Transferring"
|
||||
} else {
|
||||
" Retrying"
|
||||
},
|
||||
receivers,
|
||||
to_fund_txs.len(),
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator) =
|
||||
client.get_recent_blockhash().expect("blockhash");
|
||||
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
tx.sign(&[*k], blockhash);
|
||||
});
|
||||
to_fund_txs.iter().for_each(|(_, tx)| {
|
||||
client.async_send_transaction(tx.clone()).expect("transfer");
|
||||
});
|
||||
|
||||
let mut waits = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, &tx, amount));
|
||||
if to_fund_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
debug!(
|
||||
" {} transactions outstanding, {:?} waits",
|
||||
to_fund_txs.len(),
|
||||
waits
|
||||
);
|
||||
waits += 1;
|
||||
if waits >= 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !to_fund_txs.is_empty() {
|
||||
retries += 1;
|
||||
debug!(" Retry {:?}", retries);
|
||||
if retries >= 10 {
|
||||
error!("fund_keys: Too many retries ({}), give up", retries);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
funded.append(&mut new_funded);
|
||||
funded.retain(|(k, b)| {
|
||||
client.get_balance(&k.pubkey()).unwrap_or(0) > lamports && *b > lamports
|
||||
});
|
||||
debug!(" Funded: {} left: {}", funded.len(), notfunded.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
|
||||
let mut notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = signers.iter().zip(accounts).collect();
|
||||
|
||||
while !notfunded.is_empty() {
|
||||
notfunded.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
|
||||
let mut to_create_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(signer, new)| {
|
||||
let owner_pubkey = &signer.pubkey();
|
||||
let space = mem::size_of::<ExchangeState>() as u64;
|
||||
let create_ix =
|
||||
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
|
||||
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
|
||||
(
|
||||
signer,
|
||||
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let accounts = to_create_txs
|
||||
.iter()
|
||||
.fold(0, |len, (_, tx)| len + tx.message().instructions.len() / 2);
|
||||
|
||||
debug!(
|
||||
" Creating {} accounts in {} txs",
|
||||
accounts,
|
||||
to_create_txs.len(),
|
||||
);
|
||||
|
||||
let mut retries = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.expect("Failed to get blockhash");
|
||||
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
let kp: &Keypair = k;
|
||||
tx.sign(&[kp], blockhash);
|
||||
});
|
||||
to_create_txs.iter().for_each(|(_, tx)| {
|
||||
client.async_send_transaction(tx.clone()).expect("transfer");
|
||||
});
|
||||
|
||||
let mut waits = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_create_txs.retain(|(_, tx)| !verify_transfer(client, &tx));
|
||||
if to_create_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
debug!(
|
||||
" {} transactions outstanding, waits {:?}",
|
||||
to_create_txs.len(),
|
||||
waits
|
||||
);
|
||||
waits += 1;
|
||||
if waits >= 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !to_create_txs.is_empty() {
|
||||
retries += 1;
|
||||
debug!(" Retry {:?}", retries);
|
||||
if retries >= 20 {
|
||||
error!(
|
||||
"create_token_accounts: Too many retries ({}), give up",
|
||||
retries
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut new_notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = vec![];
|
||||
for f in ¬funded {
|
||||
if client.get_balance(&f.1).unwrap_or(0) == 0 {
|
||||
new_notfunded.push(*f)
|
||||
}
|
||||
}
|
||||
notfunded = new_notfunded;
|
||||
debug!(" Left: {}", notfunded.len());
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, total_txs_sent: u64) {
|
||||
let mut max_txs = 0;
|
||||
let mut max_elapsed = Duration::new(0, 0);
|
||||
info!("| Max TPS | Total Transactions");
|
||||
info!("+---------------+--------------------");
|
||||
|
||||
for (_sock, stats) in maxes.read().unwrap().iter() {
|
||||
let maybe_flag = match stats.txs {
|
||||
0 => "!!!!!",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
info!("| {:13.2} | {} {}", stats.tps, stats.txs, maybe_flag);
|
||||
|
||||
if stats.elapsed > max_elapsed {
|
||||
max_elapsed = stats.elapsed;
|
||||
}
|
||||
if stats.txs > max_txs {
|
||||
max_txs = stats.txs;
|
||||
}
|
||||
}
|
||||
info!("+---------------+--------------------");
|
||||
|
||||
if max_txs >= total_txs_sent {
|
||||
info!(
|
||||
"Warning: Average TPS might be under reported, there were no txs sent for a portion of the duration"
|
||||
);
|
||||
max_txs = total_txs_sent;
|
||||
}
|
||||
info!(
|
||||
"{} txs outstanding when test ended (lag) ({:.2}%)",
|
||||
total_txs_sent - max_txs,
|
||||
(total_txs_sent - max_txs) as f64 / total_txs_sent as f64 * 100_f64
|
||||
);
|
||||
info!(
|
||||
"\tAverage TPS: {:.2}",
|
||||
max_txs as f32 / max_elapsed.as_secs() as f32
|
||||
);
|
||||
}
|
||||
|
||||
fn generate_keypairs(num: u64) -> Vec<Keypair> {
|
||||
let mut seed = [0_u8; 32];
|
||||
seed.copy_from_slice(&Keypair::new().pubkey().as_ref());
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
rnd.gen_n_keypairs(num)
|
||||
}
|
||||
|
||||
pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
|
||||
let balance = client.get_balance(&id.pubkey());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
return;
|
||||
}
|
||||
|
||||
let amount_to_drop = amount - balance;
|
||||
|
||||
info!(
|
||||
"Airdropping {:?} lamports from {} for {}",
|
||||
amount_to_drop,
|
||||
drone_addr,
|
||||
id.pubkey(),
|
||||
);
|
||||
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let signature = client.async_send_transaction(transaction).unwrap();
|
||||
|
||||
for _ in 0..30 {
|
||||
if let Ok(Some(_)) = client.get_signature_status(&signature) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
if client.get_balance(&id.pubkey()).unwrap_or(0) >= amount {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
|
||||
err, drone_addr, amount
|
||||
);
|
||||
}
|
||||
};
|
||||
debug!(" Retry...");
|
||||
tries += 1;
|
||||
if tries > 50 {
|
||||
error!("airdrop_lamports: Too many retries ({}), give up", tries);
|
||||
exit(1);
|
||||
}
|
||||
sleep(Duration::from_secs(2));
|
||||
}
|
||||
}
|
@@ -1,218 +0,0 @@
|
||||
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub drone_addr: SocketAddr,
|
||||
pub identity: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub transfer_delay: u64,
|
||||
pub fund_amount: u64,
|
||||
pub batch_size: usize,
|
||||
pub chunk_size: usize,
|
||||
pub account_groups: usize,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
|
||||
identity: Keypair::new(),
|
||||
num_nodes: 1,
|
||||
threads: 4,
|
||||
duration: Duration::new(u64::max_value(), 0),
|
||||
transfer_delay: 0,
|
||||
fund_amount: 100_000,
|
||||
batch_size: 100,
|
||||
chunk_size: 100,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:8001")
|
||||
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("drone")
|
||||
.short("d")
|
||||
.long("drone")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the drone; defaults to 127.0.0.1:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.long("threads")
|
||||
.value_name("<threads>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Number of threads submitting transactions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.default_value("60")
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("transfer-delay")
|
||||
.long("transfer-delay")
|
||||
.value_name("<delay>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("0")
|
||||
.help("Delay between each chunk"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fund-amount")
|
||||
.long("fund-amount")
|
||||
.value_name("<fund>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("100000")
|
||||
.help("Number of lamports to fund to each signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch-size")
|
||||
.long("batch-size")
|
||||
.value_name("<batch>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1000")
|
||||
.help("Number of transactions before the signer rolls over"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("chunk-size")
|
||||
.long("chunk-size")
|
||||
.value_name("<cunk>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("500")
|
||||
.help("Number of transactions to generate and send at a time"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("account-groups")
|
||||
.long("account-groups")
|
||||
.value_name("<groups>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("10")
|
||||
.help("Number of account groups to cycle for each batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse drone address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
} else {
|
||||
args.identity = {
|
||||
let seed = [42_u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
rnd.gen_keypair()
|
||||
};
|
||||
}
|
||||
args.threads = value_t!(matches.value_of("threads"), usize).expect("Failed to parse threads");
|
||||
args.num_nodes =
|
||||
value_t!(matches.value_of("num-nodes"), usize).expect("Failed to parse num-nodes");
|
||||
let duration = value_t!(matches.value_of("duration"), u64).expect("Failed to parse duration");
|
||||
args.duration = Duration::from_secs(duration);
|
||||
args.transfer_delay =
|
||||
value_t!(matches.value_of("transfer-delay"), u64).expect("Failed to parse transfer-delay");
|
||||
args.fund_amount =
|
||||
value_t!(matches.value_of("fund-amount"), u64).expect("Failed to parse fund-amount");
|
||||
args.batch_size =
|
||||
value_t!(matches.value_of("batch-size"), usize).expect("Failed to parse batch-size");
|
||||
args.chunk_size =
|
||||
value_t!(matches.value_of("chunk-size"), usize).expect("Failed to parse chunk-size");
|
||||
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
|
||||
.expect("Failed to parse account-groups");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
args
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
mod order_book;
|
@@ -1,83 +0,0 @@
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
||||
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-exchange");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
drone_addr,
|
||||
identity,
|
||||
threads,
|
||||
num_nodes,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
..
|
||||
} = cli_config;
|
||||
|
||||
let config = Config {
|
||||
identity,
|
||||
threads,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
};
|
||||
|
||||
if write_to_client_file {
|
||||
create_client_accounts_file(
|
||||
&config.client_ids_and_stake_file,
|
||||
config.batch_size,
|
||||
config.account_groups,
|
||||
config.fund_amount,
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
info!("{} nodes found", num_clients);
|
||||
if num_clients < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Funding keypair: {}", config.identity.pubkey());
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&drone_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
}
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::*;
|
||||
use solana_exchange_api::exchange_state::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
other.info.price.cmp(&self.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for ToOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.info.price.cmp(&other.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for FromOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderBook {
|
||||
// TODO scale to x token types
|
||||
to_ab: BinaryHeap<ToOrder>,
|
||||
from_ab: BinaryHeap<FromOrder>,
|
||||
}
|
||||
impl fmt::Display for OrderBook {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"+-Order Book--------------------------+-------------------------------------+"
|
||||
)?;
|
||||
for (i, it) in self
|
||||
.to_ab
|
||||
.iter()
|
||||
.zip_longest(self.from_ab.iter())
|
||||
.enumerate()
|
||||
{
|
||||
match it {
|
||||
Both(to, from) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | F AB {:8} for {:8}/{:8} |{}",
|
||||
to.info.tokens,
|
||||
SCALER,
|
||||
to.info.price,
|
||||
from.info.tokens,
|
||||
SCALER,
|
||||
from.info.price,
|
||||
i
|
||||
)?,
|
||||
Left(to) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | |{}",
|
||||
to.info.tokens, SCALER, to.info.price, i
|
||||
)?,
|
||||
Right(from) => writeln!(
|
||||
f,
|
||||
"| | F AB {:8} for {:8}/{:8} |{}",
|
||||
from.info.tokens, SCALER, from.info.price, i
|
||||
)?,
|
||||
}
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"+-------------------------------------+-------------------------------------+"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderBook {
|
||||
// TODO
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.side, info.tokens, info.price)?;
|
||||
match info.side {
|
||||
OrderSide::Ask => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
OrderSide::Bid => {
|
||||
self.from_ab.push(FromOrder { pubkey, info });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<(ToOrder, FromOrder)> {
|
||||
if let Some(pair) = Self::pop_pair(&mut self.to_ab, &mut self.from_ab) {
|
||||
return Some(pair);
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn get_num_outstanding(&self) -> (usize, usize) {
|
||||
(self.to_ab.len(), self.from_ab.len())
|
||||
}
|
||||
|
||||
fn pop_pair(
|
||||
to_ab: &mut BinaryHeap<ToOrder>,
|
||||
from_ab: &mut BinaryHeap<FromOrder>,
|
||||
) -> Option<(ToOrder, FromOrder)> {
|
||||
let to = to_ab.peek()?;
|
||||
let from = from_ab.peek()?;
|
||||
if from.info.price < to.info.price {
|
||||
debug!("Trade not viable");
|
||||
return None;
|
||||
}
|
||||
let to = to_ab.pop()?;
|
||||
let from = from_ab.pop()?;
|
||||
Some((to, from))
|
||||
}
|
||||
}
|
2
bench-streamer/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/farf/
|
@@ -2,13 +2,16 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.19.0-pre0"
|
||||
version = "0.12.3"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
clap = "2.32.0"
|
||||
solana = { path = "../core", version = "0.12.3" }
|
||||
solana-logger = { path = "../logger", version = "0.12.3" }
|
||||
solana-netutil = { path = "../netutil", version = "0.12.3" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@@ -1,8 +1,7 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana_core::packet::PacketsRecycler;
|
||||
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana_core::result::Result;
|
||||
use solana_core::streamer::{receiver, PacketReceiver};
|
||||
use clap::{App, Arg};
|
||||
use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana::result::Result;
|
||||
use solana::streamer::{receiver, PacketReceiver};
|
||||
use std::cmp::max;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
@@ -15,19 +14,19 @@ use std::time::SystemTime;
|
||||
|
||||
fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut msgs = Packets::default();
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in msgs.packets.iter_mut() {
|
||||
let msgs = SharedPackets::default();
|
||||
let msgs_ = msgs.clone();
|
||||
msgs.write().unwrap().packets.resize(10, Packet::default());
|
||||
for w in &mut msgs.write().unwrap().packets {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
}
|
||||
let msgs = Arc::new(msgs);
|
||||
spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
let mut num = 0;
|
||||
for p in &msgs.packets {
|
||||
for p in &msgs_.read().unwrap().packets {
|
||||
let a = p.meta.addr();
|
||||
assert!(p.meta.size < BLOB_SIZE);
|
||||
send.send_to(&p.data[..p.meta.size], &a).unwrap();
|
||||
@@ -44,7 +43,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
}
|
||||
let timer = Duration::new(1, 0);
|
||||
if let Ok(msgs) = r.recv_timeout(timer) {
|
||||
rvs.fetch_add(msgs.packets.len(), Ordering::Relaxed);
|
||||
rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -52,9 +51,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
fn main() -> Result<()> {
|
||||
let mut num_sockets = 1usize;
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
let matches = App::new("solana-bench-streamer")
|
||||
.arg(
|
||||
Arg::with_name("num-recv-sockets")
|
||||
.long("num-recv-sockets")
|
||||
@@ -75,7 +72,6 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_netutil::bind_to(port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@@ -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
@@ -1,4 +0,0 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
@@ -2,34 +2,21 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.19.0-pre0"
|
||||
version = "0.12.3"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde = "1.0.101"
|
||||
serde_derive = "1.0.101"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.19.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.19.0-pre0" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.19.0-pre0" }
|
||||
clap = "2.32.0"
|
||||
rayon = "1.0.3"
|
||||
serde_json = "1.0.39"
|
||||
solana = { path = "../core", version = "0.12.3" }
|
||||
solana-client = { path = "../client", version = "0.12.3" }
|
||||
solana-drone = { path = "../drone", version = "0.12.3" }
|
||||
solana-logger = { path = "../logger", version = "0.12.3" }
|
||||
solana-metrics = { path = "../metrics", version = "0.12.3" }
|
||||
solana-sdk = { path = "../sdk", version = "0.12.3" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.2.0"
|
||||
serial_test_derive = "0.2.0"
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@@ -2,16 +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};
|
||||
|
||||
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = 64 * 1024;
|
||||
|
||||
/// 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,
|
||||
@@ -20,18 +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 num_lamports_per_account: u64,
|
||||
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,
|
||||
@@ -40,27 +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,
|
||||
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
|
||||
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")
|
||||
@@ -68,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")
|
||||
@@ -86,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")
|
||||
@@ -101,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")
|
||||
@@ -126,39 +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",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_lamports_per_account")
|
||||
.long("num-lamports-per-account")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"Number of lamports per account.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a clap `ArgMatches` structure into a `Config`
|
||||
@@ -169,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)
|
||||
});
|
||||
@@ -215,27 +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");
|
||||
|
||||
if let Some(v) = matches.value_of("num_lamports_per_account") {
|
||||
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
args.converge_only = matches.is_present("converge-only");
|
||||
args.reject_extra_nodes = matches.is_present("reject-extra-nodes");
|
||||
|
||||
args
|
||||
}
|
||||
|
@@ -1,2 +0,0 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
@@ -1,126 +1,251 @@
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
|
||||
use solana_bench_tps::cli;
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_genesis::PrimordialAccountDetails;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_program;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
mod bench;
|
||||
mod cli;
|
||||
|
||||
/// 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 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::VecDeque;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::thread::Builder;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_filter("solana=info");
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cfg = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
network_addr: network,
|
||||
drone_addr,
|
||||
id,
|
||||
threads,
|
||||
thread_batch_sleep_ms,
|
||||
num_nodes,
|
||||
duration,
|
||||
tx_count,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
num_lamports_per_account,
|
||||
..
|
||||
} = &cli_config;
|
||||
sustained,
|
||||
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(),
|
||||
PrimordialAccountDetails {
|
||||
balance: num_lamports_per_account,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
data: String::new(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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, PrimordialAccountDetails> =
|
||||
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, primordial_account)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = primordial_account.balance;
|
||||
});
|
||||
// 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();
|
||||
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
cli_config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
|
||||
|
||||
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
@@ -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);
|
||||
}
|
261
benches/banking_stage.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
#[macro_use]
|
||||
extern crate solana;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
|
||||
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 ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blocktree = Arc::new(
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blocktree);
|
||||
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();
|
||||
}
|
||||
Blocktree::destroy(&ledger_path).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 ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blocktree = Arc::new(
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blocktree);
|
||||
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();
|
||||
}
|
||||
Blocktree::destroy(&ledger_path).unwrap();
|
||||
}
|
194
benches/blocktree.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
#![feature(test)]
|
||||
use rand;
|
||||
|
||||
extern crate test;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana;
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
|
||||
use solana::entry::{make_large_test_entries, make_tiny_test_entries, EntrySlice};
|
||||
use solana::packet::{Blob, BLOB_HEADER_SIZE};
|
||||
use test::Bencher;
|
||||
|
||||
// Given some blobs and a ledger at ledger_path, benchmark writing the blobs to the ledger
|
||||
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &str) {
|
||||
let blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
let num_blobs = blobs.len();
|
||||
|
||||
bench.iter(move || {
|
||||
for blob in blobs.iter_mut() {
|
||||
let index = blob.index();
|
||||
|
||||
blocktree
|
||||
.put_data_blob_bytes(
|
||||
blob.slot(),
|
||||
index,
|
||||
&blob.data[..BLOB_HEADER_SIZE + blob.size()],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
blob.set_index(index + num_blobs as u64);
|
||||
}
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
// Insert some blobs into the ledger in preparation for read benchmarks
|
||||
fn setup_read_bench(
|
||||
blocktree: &mut Blocktree,
|
||||
num_small_blobs: u64,
|
||||
num_large_blobs: u64,
|
||||
slot: u64,
|
||||
) {
|
||||
// Make some big and small entries
|
||||
let mut entries = make_large_test_entries(num_large_blobs as usize);
|
||||
entries.extend(make_tiny_test_entries(num_small_blobs as usize));
|
||||
|
||||
// Convert the entries to blobs, write the blobs to the ledger
|
||||
let mut blobs = entries.to_blobs();
|
||||
for (index, b) in blobs.iter_mut().enumerate() {
|
||||
b.set_index(index as u64);
|
||||
b.set_slot(slot);
|
||||
}
|
||||
blocktree
|
||||
.write_blobs(&blobs)
|
||||
.expect("Expectd successful insertion of blobs into ledger");
|
||||
}
|
||||
|
||||
// Write small blobs to the ledger
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_write_small(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_tiny_test_entries(num_entries);
|
||||
let mut blobs = entries.to_blobs();
|
||||
for (index, b) in blobs.iter_mut().enumerate() {
|
||||
b.set_index(index as u64);
|
||||
}
|
||||
bench_write_blobs(bench, &mut blobs, &ledger_path);
|
||||
}
|
||||
|
||||
// Write big blobs to the ledger
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_write_big(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_large_test_entries(num_entries);
|
||||
let mut blobs = entries.to_blobs();
|
||||
for (index, b) in blobs.iter_mut().enumerate() {
|
||||
b.set_index(index as u64);
|
||||
}
|
||||
|
||||
bench_write_blobs(bench, &mut blobs, &ledger_path);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_read_sequential(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let mut blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
// Insert some big and small blobs into the ledger
|
||||
let num_small_blobs = 32 * 1024;
|
||||
let num_large_blobs = 32 * 1024;
|
||||
let total_blobs = num_small_blobs + num_large_blobs;
|
||||
let slot = 0;
|
||||
setup_read_bench(&mut blocktree, num_small_blobs, num_large_blobs, slot);
|
||||
|
||||
let num_reads = total_blobs / 15;
|
||||
let mut rng = rand::thread_rng();
|
||||
bench.iter(move || {
|
||||
// Generate random starting point in the range [0, total_blobs - 1], read num_reads blobs sequentially
|
||||
let start_index = rng.gen_range(0, num_small_blobs + num_large_blobs);
|
||||
for i in start_index..start_index + num_reads {
|
||||
let _ = blocktree.get_data_blob(slot, i as u64 % total_blobs);
|
||||
}
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_read_random(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let mut blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
// Insert some big and small blobs into the ledger
|
||||
let num_small_blobs = 32 * 1024;
|
||||
let num_large_blobs = 32 * 1024;
|
||||
let total_blobs = num_small_blobs + num_large_blobs;
|
||||
let slot = 0;
|
||||
setup_read_bench(&mut blocktree, num_small_blobs, num_large_blobs, slot);
|
||||
|
||||
let num_reads = total_blobs / 15;
|
||||
|
||||
// Generate a num_reads sized random sample of indexes in range [0, total_blobs - 1],
|
||||
// simulating random reads
|
||||
let mut rng = rand::thread_rng();
|
||||
let indexes: Vec<usize> = (0..num_reads)
|
||||
.map(|_| rng.gen_range(0, total_blobs) as usize)
|
||||
.collect();
|
||||
bench.iter(move || {
|
||||
for i in indexes.iter() {
|
||||
let _ = blocktree.get_data_blob(slot, *i as u64);
|
||||
}
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_insert_data_blob_small(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_tiny_test_entries(num_entries);
|
||||
let mut blobs = entries.to_blobs();
|
||||
|
||||
blobs.shuffle(&mut thread_rng());
|
||||
|
||||
bench.iter(move || {
|
||||
for blob in blobs.iter_mut() {
|
||||
let index = blob.index();
|
||||
blob.set_index(index + num_entries as u64);
|
||||
}
|
||||
blocktree.write_blobs(&blobs).unwrap();
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_insert_data_blob_big(bench: &mut Bencher) {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
let num_entries = 32 * 1024;
|
||||
let entries = make_large_test_entries(num_entries);
|
||||
let mut shared_blobs = entries.to_shared_blobs();
|
||||
shared_blobs.shuffle(&mut thread_rng());
|
||||
|
||||
bench.iter(move || {
|
||||
for blob in shared_blobs.iter_mut() {
|
||||
let index = blob.read().unwrap().index();
|
||||
blocktree.write_shared_blobs(vec![blob.clone()]).unwrap();
|
||||
blob.write().unwrap().set_index(index + num_entries as u64);
|
||||
}
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
//#![feature(test)]
|
||||
//
|
||||
//extern crate solana_core;
|
||||
//extern crate solana;
|
||||
//extern crate test;
|
||||
//
|
||||
//use solana_core::chacha::chacha_cbc_encrypt_files;
|
||||
//use solana::chacha::chacha_cbc_encrypt_files;
|
||||
//use std::fs::remove_file;
|
||||
//use std::fs::File;
|
||||
//use std::io::Write;
|
@@ -2,7 +2,7 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana::gen_keys::GenKeys;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
24
benches/ledger.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
#![feature(test)]
|
||||
|
||||
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::SystemTransaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
|
||||
let zero = Hash::default();
|
||||
let one = hash(&zero.as_ref());
|
||||
let keypair = Keypair::new();
|
||||
let tx0 = SystemTransaction::new_move(&keypair, &keypair.pubkey(), 1, one, 0);
|
||||
let transactions = vec![tx0; 10];
|
||||
let entries = next_entries(&zero, 1, transactions);
|
||||
|
||||
bencher.iter(|| {
|
||||
let blobs = entries.to_blobs();
|
||||
assert_eq!(reconstruct_entries_from_blobs(blobs).unwrap().0, entries);
|
||||
});
|
||||
}
|
21
benches/sigverify.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::to_packets;
|
||||
use solana::sigverify;
|
||||
use solana::test_tx::test_tx;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify(bencher: &mut Bencher) {
|
||||
let tx = test_tx();
|
||||
|
||||
// generate packet vector
|
||||
let batches = to_packets(&vec![tx; 128]);
|
||||
|
||||
// verify packets
|
||||
bencher.iter(|| {
|
||||
let _ans = sigverify::ed25519_verify(&batches);
|
||||
})
|
||||
}
|
1
book/.gitattributes
vendored
@@ -1 +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 |
|
||||
| |
|
||||
`------------`
|
||||
|
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)" -B svg
|
||||
|
||||
if [[ -n $CI ]]; then
|
||||
# In CI confirm that no svgs need to be built
|
||||
git diff --exit-code
|
||||
fi
|
@@ -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,41 +1,31 @@
|
||||
BOB_SRCS=$(wildcard art/*.bob)
|
||||
MSC_SRCS=$(wildcard art/*.msc)
|
||||
MD_SRCS=$(wildcard src/*.md)
|
||||
|
||||
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/.gitbook/assets/%.svg) $(MSC_SRCS:art/%.msc=src/.gitbook/assets/%.svg)
|
||||
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
|
||||
|
||||
svg: $(SVG_IMGS)
|
||||
|
||||
test: $(TEST_STAMP)
|
||||
|
||||
open: $(TEST_STAMP)
|
||||
open: all
|
||||
mdbook build --open
|
||||
|
||||
watch: $(SVG_IMGS)
|
||||
mdbook watch
|
||||
|
||||
src/.gitbook/assets/%.svg: art/%.bob
|
||||
src/img/%.svg: art/%.bob
|
||||
@mkdir -p $(@D)
|
||||
svgbob < $< > $@
|
||||
|
||||
src/.gitbook/assets/%.svg: art/%.msc
|
||||
@mkdir -p $(@D)
|
||||
mscgen -T svg -i $< -o $@
|
||||
|
||||
src/%.md: %.md
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
$(TEST_STAMP): $(TARGET)
|
||||
src/tests.ok: $(SVG_IMGS) $(MD_SRCS)
|
||||
mdbook test
|
||||
touch $@
|
||||
|
||||
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
|
||||
html/index.html: src/tests.ok
|
||||
mdbook build
|
||||
|
||||
clean:
|
||||
|
@@ -1,183 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="544" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="544" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="8" y2="184"/>
|
||||
<line x1="4" x2="540" y1="8" y2="8"/>
|
||||
<line x1="4" x2="540" y1="184" y2="184"/>
|
||||
<line x1="540" x2="540" y1="8" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="28" x2="28" y1="232" y2="296"/>
|
||||
<line x1="28" x2="108" y1="232" y2="232"/>
|
||||
<line x1="28" x2="196" y1="296" y2="296"/>
|
||||
<line x1="108" x2="164" y1="232" y2="232"/>
|
||||
<line x1="164" x2="196" y1="232" y2="232"/>
|
||||
<line x1="196" x2="196" y1="232" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="40" y2="104"/>
|
||||
<line x1="36" x2="180" y1="40" y2="40"/>
|
||||
<line x1="36" x2="108" y1="104" y2="104"/>
|
||||
<line x1="108" x2="108" y1="104" y2="176"/>
|
||||
<line x1="108" x2="124" y1="104" y2="104"/>
|
||||
<line x1="124" x2="124" y1="104" y2="136"/>
|
||||
<line x1="124" x2="180" y1="104" y2="104"/>
|
||||
<line x1="124" x2="364" y1="136" y2="136"/>
|
||||
<line x1="180" x2="180" y1="40" y2="56"/>
|
||||
<line x1="180" x2="180" y1="56" y2="88"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="356" y1="56" y2="56"/>
|
||||
<line x1="180" x2="180" y1="88" y2="104"/>
|
||||
<line x1="180" x2="184" y1="88" y2="88"/>
|
||||
<line x1="364" x2="364" y1="136" y2="152"/>
|
||||
<line x1="364" x2="364" y1="152" y2="176"/>
|
||||
<line x1="364" x2="420" y1="152" y2="152"/>
|
||||
<line x1="420" x2="420" y1="104" y2="152"/>
|
||||
<line x1="420" x2="436" y1="104" y2="104"/>
|
||||
<line x1="436" x2="436" y1="104" y2="176"/>
|
||||
<line x1="436" x2="508" y1="104" y2="104"/>
|
||||
<line x1="508" x2="508" y1="40" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="108" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="164" x2="164" y1="152" y2="176"/>
|
||||
<line x1="164" x2="364" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="164" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="188" y1="88" y2="88"/>
|
||||
<line x1="192" x2="364" y1="88" y2="88"/>
|
||||
<line x1="364" x2="364" y1="56" y2="88"/>
|
||||
<line x1="364" x2="364" y1="88" y2="104"/>
|
||||
<line x1="364" x2="420" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="232" y2="296"/>
|
||||
<line x1="348" x2="364" y1="232" y2="232"/>
|
||||
<line x1="348" x2="516" y1="296" y2="296"/>
|
||||
<line x1="364" x2="436" y1="232" y2="232"/>
|
||||
<line x1="436" x2="516" y1="232" y2="232"/>
|
||||
<line x1="516" x2="516" y1="232" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="40" y2="56"/>
|
||||
<line x1="364" x2="508" y1="40" y2="40"/>
|
||||
<line x1="364" x2="360" y1="56" y2="56"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="364" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="436" x2="436" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="57" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="76">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="76">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="268">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="44">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="44">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="76">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="76">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="481" y="268">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,322 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="400" width="856" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="400" width="856" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="8" y2="152"/>
|
||||
<line x1="4" x2="852" y1="8" y2="8"/>
|
||||
<line x1="4" x2="852" y1="152" y2="152"/>
|
||||
<line x1="852" x2="852" y1="8" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="248" y2="392"/>
|
||||
<line x1="4" x2="852" y1="248" y2="248"/>
|
||||
<line x1="4" x2="852" y1="392" y2="392"/>
|
||||
<line x1="852" x2="852" y1="248" y2="392"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="60" x2="60" y1="56" y2="120"/>
|
||||
<line x1="60" x2="196" y1="56" y2="56"/>
|
||||
<line x1="60" x2="84" y1="120" y2="120"/>
|
||||
<line x1="84" x2="84" y1="120" y2="144"/>
|
||||
<line x1="84" x2="196" y1="120" y2="120"/>
|
||||
<line x1="196" x2="196" y1="56" y2="72"/>
|
||||
<line x1="196" x2="196" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="252" y1="72" y2="72"/>
|
||||
<line x1="196" x2="196" y1="104" y2="120"/>
|
||||
<line x1="196" x2="200" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="60" x2="60" y1="296" y2="360"/>
|
||||
<line x1="60" x2="84" y1="296" y2="296"/>
|
||||
<line x1="60" x2="196" y1="360" y2="360"/>
|
||||
<line x1="84" x2="196" y1="296" y2="296"/>
|
||||
<line x1="196" x2="196" y1="296" y2="312"/>
|
||||
<line x1="196" x2="196" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="252" y1="312" y2="312"/>
|
||||
<line x1="196" x2="196" y1="344" y2="360"/>
|
||||
<line x1="196" x2="200" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="84" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="208" x2="204" y1="104" y2="104"/>
|
||||
<line x1="208" x2="260" y1="104" y2="104"/>
|
||||
<line x1="260" x2="260" y1="72" y2="104"/>
|
||||
<line x1="260" x2="260" y1="104" y2="120"/>
|
||||
<line x1="260" x2="284" y1="120" y2="120"/>
|
||||
<line x1="284" x2="284" y1="120" y2="144"/>
|
||||
<line x1="284" x2="396" y1="120" y2="120"/>
|
||||
<line x1="396" x2="396" y1="104" y2="120"/>
|
||||
<line x1="396" x2="400" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="208" x2="204" y1="344" y2="344"/>
|
||||
<line x1="208" x2="260" y1="344" y2="344"/>
|
||||
<line x1="260" x2="260" y1="312" y2="344"/>
|
||||
<line x1="260" x2="260" y1="344" y2="360"/>
|
||||
<line x1="260" x2="396" y1="360" y2="360"/>
|
||||
<line x1="396" x2="396" y1="344" y2="360"/>
|
||||
<line x1="396" x2="400" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="56" y2="72"/>
|
||||
<line x1="260" x2="396" y1="56" y2="56"/>
|
||||
<line x1="260" x2="256" y1="72" y2="72"/>
|
||||
<line x1="396" x2="396" y1="56" y2="72"/>
|
||||
<line x1="396" x2="396" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="452" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="296" y2="312"/>
|
||||
<line x1="260" x2="284" y1="296" y2="296"/>
|
||||
<line x1="260" x2="256" y1="312" y2="312"/>
|
||||
<line x1="284" x2="396" y1="296" y2="296"/>
|
||||
<line x1="396" x2="396" y1="296" y2="312"/>
|
||||
<line x1="396" x2="396" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="452" y1="312" y2="312"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="284" x2="284" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="104" y2="104"/>
|
||||
<line x1="408" x2="460" y1="104" y2="104"/>
|
||||
<line x1="460" x2="460" y1="72" y2="104"/>
|
||||
<line x1="460" x2="460" y1="104" y2="120"/>
|
||||
<line x1="460" x2="508" y1="120" y2="120"/>
|
||||
<line x1="508" x2="508" y1="120" y2="144"/>
|
||||
<line x1="508" x2="596" y1="120" y2="120"/>
|
||||
<line x1="596" x2="596" y1="104" y2="120"/>
|
||||
<line x1="596" x2="600" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="344" y2="344"/>
|
||||
<line x1="408" x2="460" y1="344" y2="344"/>
|
||||
<line x1="460" x2="460" y1="312" y2="344"/>
|
||||
<line x1="460" x2="460" y1="344" y2="360"/>
|
||||
<line x1="460" x2="596" y1="360" y2="360"/>
|
||||
<line x1="596" x2="596" y1="344" y2="360"/>
|
||||
<line x1="596" x2="600" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="56" y2="72"/>
|
||||
<line x1="460" x2="596" y1="56" y2="56"/>
|
||||
<line x1="460" x2="456" y1="72" y2="72"/>
|
||||
<line x1="596" x2="596" y1="56" y2="72"/>
|
||||
<line x1="596" x2="596" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="596" x2="652" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="296" y2="312"/>
|
||||
<line x1="460" x2="508" y1="296" y2="296"/>
|
||||
<line x1="460" x2="456" y1="312" y2="312"/>
|
||||
<line x1="508" x2="596" y1="296" y2="296"/>
|
||||
<line x1="596" x2="596" y1="296" y2="312"/>
|
||||
<line x1="596" x2="596" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="596" x2="652" y1="312" y2="312"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="508" x2="508" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="608" x2="604" y1="104" y2="104"/>
|
||||
<line x1="608" x2="660" y1="104" y2="104"/>
|
||||
<line x1="660" x2="660" y1="72" y2="104"/>
|
||||
<line x1="660" x2="660" y1="104" y2="120"/>
|
||||
<line x1="660" x2="684" y1="120" y2="120"/>
|
||||
<line x1="684" x2="684" y1="120" y2="144"/>
|
||||
<line x1="684" x2="796" y1="120" y2="120"/>
|
||||
<line x1="796" x2="796" y1="56" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="608" x2="604" y1="344" y2="344"/>
|
||||
<line x1="608" x2="660" y1="344" y2="344"/>
|
||||
<line x1="660" x2="660" y1="312" y2="344"/>
|
||||
<line x1="660" x2="660" y1="344" y2="360"/>
|
||||
<line x1="660" x2="796" y1="360" y2="360"/>
|
||||
<line x1="796" x2="796" y1="296" y2="360"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="660" x2="660" y1="56" y2="72"/>
|
||||
<line x1="660" x2="796" y1="56" y2="56"/>
|
||||
<line x1="660" x2="656" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="660" x2="660" y1="296" y2="312"/>
|
||||
<line x1="660" x2="684" y1="296" y2="296"/>
|
||||
<line x1="660" x2="656" y1="312" y2="312"/>
|
||||
<line x1="684" x2="796" y1="296" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="684" x2="684" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="684" x2="684" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="92">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="332">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="28">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="92">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="332">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="28">
|
||||
Above
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="268">
|
||||
Below
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="561" y="92">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="561" y="332">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="689" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="689" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="761" y="92">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="761" y="332">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -1,138 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="240" width="544" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="240" width="544" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="104" y2="232"/>
|
||||
<line x1="4" x2="108" y1="104" y2="104"/>
|
||||
<line x1="4" x2="540" y1="232" y2="232"/>
|
||||
<line x1="108" x2="436" y1="104" y2="104"/>
|
||||
<line x1="436" x2="540" y1="104" y2="104"/>
|
||||
<line x1="540" x2="540" y1="104" y2="232"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="136" y2="200"/>
|
||||
<line x1="36" x2="180" y1="136" y2="136"/>
|
||||
<line x1="36" x2="180" y1="200" y2="200"/>
|
||||
<line x1="180" x2="180" y1="136" y2="152"/>
|
||||
<line x1="180" x2="180" y1="152" y2="184"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="356" y1="152" y2="152"/>
|
||||
<line x1="180" x2="180" y1="184" y2="200"/>
|
||||
<line x1="180" x2="184" y1="184" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="108" y1="40" y2="92"/>
|
||||
<line x1="108" x2="212" y1="40" y2="40"/>
|
||||
<line x1="212" x2="212" y1="8" y2="40"/>
|
||||
<line x1="212" x2="332" y1="8" y2="8"/>
|
||||
<line x1="212" x2="212" y1="40" y2="72"/>
|
||||
<line x1="212" x2="332" y1="72" y2="72"/>
|
||||
<line x1="332" x2="332" y1="8" y2="40"/>
|
||||
<line x1="332" x2="332" y1="40" y2="72"/>
|
||||
<line x1="332" x2="436" y1="40" y2="40"/>
|
||||
<line marker-end="url(#triangle)" x1="436" x2="436" y1="40" y2="92"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="188" y1="184" y2="184"/>
|
||||
<line x1="192" x2="364" y1="184" y2="184"/>
|
||||
<line x1="364" x2="364" y1="152" y2="184"/>
|
||||
<line x1="364" x2="364" y1="184" y2="200"/>
|
||||
<line x1="364" x2="508" y1="200" y2="200"/>
|
||||
<line x1="508" x2="508" y1="136" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="136" y2="152"/>
|
||||
<line x1="364" x2="508" y1="136" y2="136"/>
|
||||
<line x1="364" x2="360" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="172">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="172">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="44">
|
||||
Leader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="140">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="172">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="172">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,192 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="288" width="736" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="288" width="736" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="216" y2="280"/>
|
||||
<line x1="4" x2="156" y1="216" y2="216"/>
|
||||
<line x1="4" x2="172" y1="280" y2="280"/>
|
||||
<line x1="156" x2="172" y1="216" y2="216"/>
|
||||
<line x1="172" x2="172" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="104" y2="168"/>
|
||||
<line x1="124" x2="204" y1="104" y2="104"/>
|
||||
<line x1="124" x2="156" y1="168" y2="168"/>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="168" y2="204"/>
|
||||
<line x1="156" x2="204" y1="168" y2="168"/>
|
||||
<line x1="204" x2="292" y1="104" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="204" x2="204" y1="168" y2="204"/>
|
||||
<line x1="204" x2="292" y1="168" y2="168"/>
|
||||
<line x1="292" x2="292" y1="104" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="216" y2="280"/>
|
||||
<line x1="188" x2="204" y1="216" y2="216"/>
|
||||
<line x1="188" x2="356" y1="280" y2="280"/>
|
||||
<line x1="204" x2="356" y1="216" y2="216"/>
|
||||
<line x1="356" x2="356" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="204" x2="204" y1="40" y2="92"/>
|
||||
<line x1="204" x2="276" y1="40" y2="40"/>
|
||||
<line x1="276" x2="276" y1="8" y2="40"/>
|
||||
<line x1="276" x2="444" y1="8" y2="8"/>
|
||||
<line x1="276" x2="276" y1="40" y2="72"/>
|
||||
<line x1="276" x2="444" y1="72" y2="72"/>
|
||||
<line x1="444" x2="444" y1="8" y2="40"/>
|
||||
<line x1="444" x2="444" y1="40" y2="72"/>
|
||||
<line x1="444" x2="532" y1="40" y2="40"/>
|
||||
<line marker-end="url(#triangle)" x1="532" x2="532" y1="40" y2="92"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="380" x2="380" y1="216" y2="280"/>
|
||||
<line x1="380" x2="532" y1="216" y2="216"/>
|
||||
<line x1="380" x2="548" y1="280" y2="280"/>
|
||||
<line x1="532" x2="548" y1="216" y2="216"/>
|
||||
<line x1="548" x2="548" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="444" x2="444" y1="104" y2="168"/>
|
||||
<line x1="444" x2="532" y1="104" y2="104"/>
|
||||
<line x1="444" x2="532" y1="168" y2="168"/>
|
||||
<line x1="532" x2="612" y1="104" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="532" x2="532" y1="168" y2="204"/>
|
||||
<line x1="532" x2="580" y1="168" y2="168"/>
|
||||
<line marker-end="url(#triangle)" x1="580" x2="580" y1="168" y2="204"/>
|
||||
<line x1="580" x2="612" y1="168" y2="168"/>
|
||||
<line x1="612" x2="612" y1="104" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="216" y2="280"/>
|
||||
<line x1="564" x2="580" y1="216" y2="216"/>
|
||||
<line x1="564" x2="732" y1="280" y2="280"/>
|
||||
<line x1="580" x2="732" y1="216" y2="216"/>
|
||||
<line x1="732" x2="732" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="252">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="153" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="140">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="44">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="252">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="44">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="252">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="577" y="140">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="593" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="697" y="252">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,330 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="208" width="768" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="208" width="768" x="0" y="0"/>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="76" x2="76" y1="32" y2="172"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="24" y2="56"/>
|
||||
<line x1="124" x2="164" y1="24" y2="24"/>
|
||||
<line x1="124" x2="124" y1="56" y2="88"/>
|
||||
<line x1="124" x2="164" y1="56" y2="56"/>
|
||||
<line x1="124" x2="124" y1="88" y2="120"/>
|
||||
<line x1="124" x2="164" y1="88" y2="88"/>
|
||||
<line x1="124" x2="124" y1="120" y2="152"/>
|
||||
<line x1="124" x2="164" y1="120" y2="120"/>
|
||||
<line x1="124" x2="124" y1="152" y2="184"/>
|
||||
<line x1="124" x2="164" y1="152" y2="152"/>
|
||||
<line x1="124" x2="164" y1="184" y2="184"/>
|
||||
<line x1="164" x2="164" y1="24" y2="56"/>
|
||||
<line x1="164" x2="164" y1="56" y2="88"/>
|
||||
<line x1="164" x2="164" y1="88" y2="120"/>
|
||||
<line x1="164" x2="164" y1="120" y2="152"/>
|
||||
<line x1="164" x2="164" y1="152" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="200" x2="208" y1="128" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="236" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="240" y1="96" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="272" y1="64" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="280" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="280" y1="128" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="316" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="360" y1="136" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="368" x2="376" y1="96" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="376" x2="384" y1="48" y2="64"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="376" x2="388" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="388" x2="388" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="416" x2="424" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="420" y1="104" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="460" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="640" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="108">
|
||||
time
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="44">
|
||||
L1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="76">
|
||||
L2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="108">
|
||||
L3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="140">
|
||||
L4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="172">
|
||||
L5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="140">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="209" y="108">
|
||||
E3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="76">
|
||||
E2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="140">
|
||||
E4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="108">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="172">
|
||||
E5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="313" y="44">
|
||||
E1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="108">
|
||||
E3'
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="76">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="12">
|
||||
validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="60">
|
||||
vote(E1)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="92">
|
||||
vote(E2)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="124">
|
||||
slash(E3)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="156">
|
||||
vote(E4)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="188">
|
||||
hang
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="553" y="188">
|
||||
on
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="577" y="188">
|
||||
to
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="593" y="12">
|
||||
action
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="188">
|
||||
E4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="625" y="188">
|
||||
and
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="657" y="188">
|
||||
E5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="681" y="188">
|
||||
for
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="713" y="188">
|
||||
more...
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -1,92 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="144" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="144" width="48" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="24" y1="88" y2="80"/>
|
||||
<line x1="20" x2="20" y1="96" y2="88"/>
|
||||
<line x1="20" x2="20" y1="96" y2="128"/>
|
||||
<line x1="24" x2="40" y1="80" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="48" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="140">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="44">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="108">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,92 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="176" width="40" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="176" width="40" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="48" y2="128"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="32" y1="48" y2="80"/>
|
||||
<line x1="36" x2="32" y1="88" y2="80"/>
|
||||
<line x1="36" x2="36" y1="96" y2="88"/>
|
||||
<line x1="36" x2="36" y1="96" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="44">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="140">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="172">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,122 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="208" width="96" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="208" width="96" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="24" y1="88" y2="80"/>
|
||||
<line x1="20" x2="20" y1="96" y2="88"/>
|
||||
<line x1="20" x2="20" y1="96" y2="128"/>
|
||||
<line x1="24" x2="40" y1="80" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="48" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="48" x2="64" y1="16" y2="48"/>
|
||||
<line x1="68" x2="64" y1="56" y2="48"/>
|
||||
<line x1="68" x2="68" y1="64" y2="56"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="68" x2="68" y1="80" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="72" x2="80" y1="80" y2="96"/>
|
||||
<line x1="80" x2="88" y1="96" y2="112"/>
|
||||
<line x1="92" x2="88" y1="120" y2="112"/>
|
||||
<line x1="92" x2="92" y1="128" y2="120"/>
|
||||
<line x1="92" x2="92" y1="128" y2="192"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="140">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="44">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="108">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="76">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="172">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="204">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 269 KiB |
@@ -1,238 +0,0 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1"
|
||||
width="1320px" height="487px"
|
||||
viewBox="0 0 1320 487"
|
||||
xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges"
|
||||
stroke-width="1" text-rendering="geometricPrecision">
|
||||
<polygon fill="white" points="101,7 161,7 161,16 101,16"/>
|
||||
<text x="132" y="16" textLength="59" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
VoteSigner
|
||||
</text>
|
||||
<polygon fill="white" points="371,7 419,7 419,16 371,16"/>
|
||||
<text x="396" y="16" textLength="47" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
Validator
|
||||
</text>
|
||||
<polygon fill="white" points="639,7 679,7 679,16 639,16"/>
|
||||
<text x="660" y="16" textLength="38" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
Cluster
|
||||
</text>
|
||||
<polygon fill="white" points="901,7 945,7 945,16 901,16"/>
|
||||
<text x="924" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
StakerX
|
||||
</text>
|
||||
<polygon fill="white" points="1165,7 1209,7 1209,16 1165,16"/>
|
||||
<text x="1188" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
StakerY
|
||||
</text>
|
||||
<line x1="132" y1="22" x2="132" y2="39" stroke="black"/>
|
||||
<line x1="396" y1="22" x2="396" y2="39" stroke="black"/>
|
||||
<line x1="660" y1="22" x2="660" y2="39" stroke="black"/>
|
||||
<line x1="924" y1="22" x2="924" y2="39" stroke="black"/>
|
||||
<line x1="1188" y1="22" x2="1188" y2="39" stroke="black"/>
|
||||
<line x1="132" y1="39" x2="132" y2="67" stroke="black"/>
|
||||
<line x1="396" y1="39" x2="396" y2="67" stroke="black"/>
|
||||
<line x1="660" y1="39" x2="660" y2="67" stroke="black"/>
|
||||
<line x1="924" y1="39" x2="924" y2="67" stroke="black"/>
|
||||
<line x1="1188" y1="39" x2="1188" y2="67" stroke="black"/>
|
||||
<polygon fill="white" points="272,39 520,39 520,61 272,61"/>
|
||||
<line x1="272" y1="39" x2="520" y2="39" stroke="black"/>
|
||||
<line x1="272" y1="61" x2="520" y2="61" stroke="black"/>
|
||||
<line x1="272" y1="39" x2="272" y2="61" stroke="black"/>
|
||||
<line x1="520" y1="39" x2="520" y2="61" stroke="black"/>
|
||||
<polygon fill="white" points="379,46 411,46 411,55 379,55"/>
|
||||
<text x="380" y="55" textLength="30" font-family="Helvetica" font-size="12" fill="black">
|
||||
boot..
|
||||
</text>
|
||||
<line x1="132" y1="67" x2="132" y2="106" stroke="black"/>
|
||||
<line x1="396" y1="67" x2="396" y2="106" stroke="black"/>
|
||||
<line x1="660" y1="67" x2="660" y2="106" stroke="black"/>
|
||||
<line x1="924" y1="67" x2="924" y2="106" stroke="black"/>
|
||||
<line x1="1188" y1="67" x2="1188" y2="106" stroke="black"/>
|
||||
<line x1="132" y1="82" x2="396" y2="82" stroke="black"/>
|
||||
<line x1="132" y1="84" x2="396" y2="84" stroke="black"/>
|
||||
<polygon fill="black" points="396,83 386,89 386,77"/>
|
||||
<polygon fill="black" points="132,83 142,89 142,77"/>
|
||||
<polygon fill="white" points="242,68 284,68 284,77 242,77"/>
|
||||
<text x="243" y="77" textLength="40" font-family="Helvetica" font-size="12" fill="black">
|
||||
register
|
||||
</text>
|
||||
<polygon fill="white" points="262,79 264,79 264,88 262,88"/>
|
||||
<text x="263" y="88" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="237,90 289,90 289,99 237,99"/>
|
||||
<text x="238" y="99" textLength="50" font-family="Helvetica" font-size="12" fill="black">
|
||||
(optional)
|
||||
</text>
|
||||
<line x1="132" y1="106" x2="132" y2="134" stroke="black"/>
|
||||
<line x1="396" y1="106" x2="396" y2="134" stroke="black"/>
|
||||
<line x1="660" y1="106" x2="660" y2="134" stroke="black"/>
|
||||
<line x1="924" y1="106" x2="924" y2="134" stroke="black"/>
|
||||
<line x1="1188" y1="106" x2="1188" y2="134" stroke="black"/>
|
||||
<line x1="396" y1="117" x2="660" y2="117" stroke="black"/>
|
||||
<polygon fill="black" points="660,117 650,123 650,111"/>
|
||||
<polygon fill="white" points="441,107 613,107 613,116 441,116"/>
|
||||
<text x="442" y="116" textLength="170" font-family="Helvetica" font-size="12" fill="black">
|
||||
VoteState::Initialize(VoteSigner)
|
||||
</text>
|
||||
<line x1="132" y1="134" x2="132" y2="162" stroke="black"/>
|
||||
<line x1="396" y1="134" x2="396" y2="162" stroke="black"/>
|
||||
<line x1="660" y1="134" x2="660" y2="162" stroke="black"/>
|
||||
<line x1="924" y1="134" x2="924" y2="162" stroke="black"/>
|
||||
<line x1="1188" y1="134" x2="1188" y2="162" stroke="black"/>
|
||||
<line x1="924" y1="145" x2="660" y2="145" stroke="black"/>
|
||||
<polygon fill="black" points="660,145 670,151 670,139"/>
|
||||
<polygon fill="white" points="706,135 877,135 877,144 706,144"/>
|
||||
<text x="707" y="144" textLength="169" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::Delegate(Validator)
|
||||
</text>
|
||||
<line x1="132" y1="162" x2="132" y2="190" stroke="black"/>
|
||||
<line x1="396" y1="162" x2="396" y2="190" stroke="black"/>
|
||||
<line x1="660" y1="162" x2="660" y2="190" stroke="black"/>
|
||||
<line x1="924" y1="162" x2="924" y2="190" stroke="black"/>
|
||||
<line x1="1188" y1="162" x2="1188" y2="190" stroke="black"/>
|
||||
<line x1="1188" y1="173" x2="660" y2="173" stroke="black"/>
|
||||
<polygon fill="black" points="660,173 670,179 670,167"/>
|
||||
<polygon fill="white" points="838,163 1009,163 1009,172 838,172"/>
|
||||
<text x="839" y="172" textLength="169" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::Delegate(Validator)
|
||||
</text>
|
||||
<line x1="132" y1="190" x2="132" y2="207" stroke="black"/>
|
||||
<line x1="396" y1="190" x2="396" y2="207" stroke="black"/>
|
||||
<line x1="660" y1="190" x2="660" y2="207" stroke="black"/>
|
||||
<line x1="924" y1="190" x2="924" y2="207" stroke="black"/>
|
||||
<line x1="1188" y1="190" x2="1188" y2="207" stroke="black"/>
|
||||
<line x1="132" y1="207" x2="132" y2="246" stroke="black"/>
|
||||
<line x1="396" y1="207" x2="396" y2="246" stroke="black"/>
|
||||
<line x1="660" y1="207" x2="660" y2="246" stroke="black"/>
|
||||
<line x1="924" y1="207" x2="924" y2="246" stroke="black"/>
|
||||
<line x1="1188" y1="207" x2="1188" y2="246" stroke="black"/>
|
||||
<polygon fill="white" points="272,207 784,207 784,240 272,240"/>
|
||||
<line x1="272" y1="207" x2="784" y2="207" stroke="black"/>
|
||||
<line x1="272" y1="240" x2="784" y2="240" stroke="black"/>
|
||||
<line x1="272" y1="207" x2="272" y2="240" stroke="black"/>
|
||||
<line x1="784" y1="207" x2="784" y2="240" stroke="black"/>
|
||||
<polygon fill="white" points="526,208 528,208 528,217 526,217"/>
|
||||
<text x="527" y="217" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="506,219 549,219 549,228 506,228"/>
|
||||
<text x="507" y="228" textLength="41" font-family="Helvetica" font-size="12" fill="black">
|
||||
validate
|
||||
</text>
|
||||
<polygon fill="white" points="526,230 528,230 528,239 526,239"/>
|
||||
<text x="527" y="239" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<line x1="132" y1="246" x2="132" y2="274" stroke="black"/>
|
||||
<line x1="396" y1="246" x2="396" y2="274" stroke="black"/>
|
||||
<line x1="660" y1="246" x2="660" y2="274" stroke="black"/>
|
||||
<line x1="924" y1="246" x2="924" y2="274" stroke="black"/>
|
||||
<line x1="1188" y1="246" x2="1188" y2="274" stroke="black"/>
|
||||
<line x1="396" y1="257" x2="132" y2="257" stroke="black"/>
|
||||
<polygon fill="black" points="132,257 142,263 142,251"/>
|
||||
<polygon fill="white" points="236,247 291,247 291,256 236,256"/>
|
||||
<text x="237" y="256" textLength="53" font-family="Helvetica" font-size="12" fill="black">
|
||||
sign(vote)
|
||||
</text>
|
||||
<line x1="132" y1="274" x2="132" y2="302" stroke="black"/>
|
||||
<line x1="396" y1="274" x2="396" y2="302" stroke="black"/>
|
||||
<line x1="660" y1="274" x2="660" y2="302" stroke="black"/>
|
||||
<line x1="924" y1="274" x2="924" y2="302" stroke="black"/>
|
||||
<line x1="1188" y1="274" x2="1188" y2="302" stroke="black"/>
|
||||
<line x1="132" y1="285" x2="396" y2="285" stroke="black" stroke-dasharray="2,2"/>
|
||||
<polygon fill="black" points="396,285 386,291 386,279"/>
|
||||
<polygon fill="white" points="232,275 295,275 295,284 232,284"/>
|
||||
<text x="233" y="284" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
signed vote
|
||||
</text>
|
||||
<line x1="132" y1="302" x2="132" y2="330" stroke="black"/>
|
||||
<line x1="396" y1="302" x2="396" y2="330" stroke="black"/>
|
||||
<line x1="660" y1="302" x2="660" y2="330" stroke="black"/>
|
||||
<line x1="924" y1="302" x2="924" y2="330" stroke="black"/>
|
||||
<line x1="1188" y1="302" x2="1188" y2="330" stroke="black"/>
|
||||
<line x1="396" y1="313" x2="660" y2="313" stroke="black"/>
|
||||
<polygon fill="black" points="660,313 650,319 650,307"/>
|
||||
<polygon fill="white" points="494,303 561,303 561,312 494,312"/>
|
||||
<text x="495" y="312" textLength="65" font-family="Helvetica" font-size="12" fill="black">
|
||||
gossip(vote)
|
||||
</text>
|
||||
<line x1="132" y1="330" x2="132" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="396" y1="330" x2="396" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="660" y1="330" x2="660" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="924" y1="330" x2="924" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="1188" y1="330" x2="1188" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="132" y1="347" x2="132" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="396" y1="347" x2="396" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="660" y1="347" x2="660" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="924" y1="347" x2="924" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="1188" y1="347" x2="1188" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="132" y1="364" x2="132" y2="414" stroke="black"/>
|
||||
<line x1="396" y1="364" x2="396" y2="414" stroke="black"/>
|
||||
<line x1="660" y1="364" x2="660" y2="414" stroke="black"/>
|
||||
<line x1="924" y1="364" x2="924" y2="414" stroke="black"/>
|
||||
<line x1="1188" y1="364" x2="1188" y2="414" stroke="black"/>
|
||||
<polygon fill="white" points="278,364 514,364 514,408 278,408"/>
|
||||
<polygon fill="white" points="278,364 278,408 272,386"/>
|
||||
<polygon fill="white" points="514,364 514,408 520,386"/>
|
||||
<line x1="278" y1="364" x2="514" y2="364" stroke="black"/>
|
||||
<line x1="278" y1="408" x2="514" y2="408" stroke="black"/>
|
||||
<line x1="278" y1="364" x2="272" y2="386" stroke="black"/>
|
||||
<line x1="272" y1="386" x2="278" y2="408" stroke="black"/>
|
||||
<line x1="514" y1="364" x2="520" y2="386" stroke="black"/>
|
||||
<line x1="520" y1="386" x2="514" y2="408" stroke="black"/>
|
||||
<polygon fill="white" points="394,365 396,365 396,374 394,374"/>
|
||||
<text x="395" y="374" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="383,376 408,376 408,385 383,385"/>
|
||||
<text x="384" y="385" textLength="23" font-family="Helvetica" font-size="12" fill="black">
|
||||
max
|
||||
</text>
|
||||
<polygon fill="white" points="375,387 415,387 415,396 375,396"/>
|
||||
<text x="376" y="396" textLength="38" font-family="Helvetica" font-size="12" fill="black">
|
||||
lockout
|
||||
</text>
|
||||
<polygon fill="white" points="394,398 396,398 396,407 394,407"/>
|
||||
<text x="395" y="407" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<line x1="132" y1="414" x2="132" y2="431" stroke="black"/>
|
||||
<line x1="396" y1="414" x2="396" y2="431" stroke="black"/>
|
||||
<line x1="660" y1="414" x2="660" y2="431" stroke="black"/>
|
||||
<line x1="924" y1="414" x2="924" y2="431" stroke="black"/>
|
||||
<line x1="1188" y1="414" x2="1188" y2="431" stroke="black"/>
|
||||
<line x1="132" y1="431" x2="132" y2="459" stroke="black"/>
|
||||
<line x1="396" y1="431" x2="396" y2="459" stroke="black"/>
|
||||
<line x1="660" y1="431" x2="660" y2="459" stroke="black"/>
|
||||
<line x1="924" y1="431" x2="924" y2="459" stroke="black"/>
|
||||
<line x1="1188" y1="431" x2="1188" y2="459" stroke="black"/>
|
||||
<line x1="924" y1="442" x2="660" y2="442" stroke="black"/>
|
||||
<polygon fill="black" points="660,442 670,448 670,436"/>
|
||||
<polygon fill="white" points="712,432 871,432 871,441 712,441"/>
|
||||
<text x="713" y="441" textLength="157" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::RedeemCredits()
|
||||
</text>
|
||||
<line x1="132" y1="459" x2="132" y2="487" stroke="black"/>
|
||||
<line x1="396" y1="459" x2="396" y2="487" stroke="black"/>
|
||||
<line x1="660" y1="459" x2="660" y2="487" stroke="black"/>
|
||||
<line x1="924" y1="459" x2="924" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="459" x2="1188" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="470" x2="660" y2="470" stroke="black"/>
|
||||
<polygon fill="black" points="660,470 670,476 670,464"/>
|
||||
<polygon fill="white" points="844,460 1003,460 1003,469 844,469"/>
|
||||
<text x="845" y="469" textLength="157" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::RedeemCredits()
|
||||
</text>
|
||||
<line x1="132" y1="481" x2="132" y2="487" stroke="black"/>
|
||||
<line x1="396" y1="481" x2="396" y2="487" stroke="black"/>
|
||||
<line x1="660" y1="481" x2="660" y2="487" stroke="black"/>
|
||||
<line x1="924" y1="481" x2="924" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="481" x2="1188" y2="487" stroke="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,346 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="160" width="848" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="160" width="848" x="0" y="0"/>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="0" x2="28" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="12" y2="52"/>
|
||||
<path d="M 4 52 A 4 4 0 0 0 8 56" fill="none"/>
|
||||
<path d="M 8 8 A 4 4 0 0 0 4 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="8" x2="104" y1="8" y2="8"/>
|
||||
<path d="M 108 12 A 4 4 0 0 0 104 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="8" x2="104" y1="56" y2="56"/>
|
||||
<path d="M 104 56 A 4 4 0 0 0 108 52" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="92" y2="116"/>
|
||||
<path d="M 36 116 A 4 4 0 0 0 40 120" fill="none"/>
|
||||
<path d="M 40 88 A 4 4 0 0 0 36 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="40" x2="160" y1="88" y2="88"/>
|
||||
<path d="M 164 92 A 4 4 0 0 0 160 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="40" x2="160" y1="120" y2="120"/>
|
||||
<path d="M 160 120 A 4 4 0 0 0 164 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="12" y2="24"/>
|
||||
<line x1="108" x2="108" y1="24" y2="52"/>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="140" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="156" x2="156" y1="12" y2="36"/>
|
||||
<path d="M 156 36 A 4 4 0 0 0 160 40" fill="none"/>
|
||||
<path d="M 160 8 A 4 4 0 0 0 156 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="160" x2="248" y1="8" y2="8"/>
|
||||
<path d="M 252 12 A 4 4 0 0 0 248 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="160" x2="248" y1="40" y2="40"/>
|
||||
<path d="M 248 40 A 4 4 0 0 0 252 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="164" x2="164" y1="92" y2="104"/>
|
||||
<line x1="164" x2="164" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="196" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="204" x2="204" y1="92" y2="116"/>
|
||||
<path d="M 204 116 A 4 4 0 0 0 208 120" fill="none"/>
|
||||
<path d="M 208 88 A 4 4 0 0 0 204 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="280" y1="88" y2="88"/>
|
||||
<path d="M 284 92 A 4 4 0 0 0 280 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="280" y1="120" y2="120"/>
|
||||
<path d="M 280 120 A 4 4 0 0 0 284 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="252" x2="252" y1="12" y2="24"/>
|
||||
<line x1="252" x2="252" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="252" x2="284" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="92" y2="104"/>
|
||||
<line x1="284" x2="284" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="284" x2="316" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="292" x2="292" y1="12" y2="36"/>
|
||||
<path d="M 292 36 A 4 4 0 0 0 296 40" fill="none"/>
|
||||
<path d="M 296 8 A 4 4 0 0 0 292 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="296" x2="416" y1="8" y2="8"/>
|
||||
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="296" x2="416" y1="40" y2="40"/>
|
||||
<path d="M 416 40 A 4 4 0 0 0 420 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="92" y2="132"/>
|
||||
<path d="M 324 132 A 4 4 0 0 0 328 136" fill="none"/>
|
||||
<path d="M 328 88 A 4 4 0 0 0 324 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="424" y1="88" y2="88"/>
|
||||
<path d="M 428 92 A 4 4 0 0 0 424 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="424" y1="136" y2="136"/>
|
||||
<path d="M 424 136 A 4 4 0 0 0 428 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="12" y2="24"/>
|
||||
<line x1="420" x2="420" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="420" x2="452" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="428" x2="428" y1="92" y2="104"/>
|
||||
<line x1="428" x2="428" y1="104" y2="132"/>
|
||||
<line marker-end="url(#triangle)" x1="428" x2="460" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="12" y2="36"/>
|
||||
<path d="M 460 36 A 4 4 0 0 0 464 40" fill="none"/>
|
||||
<path d="M 464 8 A 4 4 0 0 0 460 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="576" y1="8" y2="8"/>
|
||||
<path d="M 580 12 A 4 4 0 0 0 576 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="576" y1="40" y2="40"/>
|
||||
<path d="M 576 40 A 4 4 0 0 0 580 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="468" x2="468" y1="92" y2="116"/>
|
||||
<path d="M 468 116 A 4 4 0 0 0 472 120" fill="none"/>
|
||||
<path d="M 472 88 A 4 4 0 0 0 468 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="472" x2="608" y1="88" y2="88"/>
|
||||
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="472" x2="608" y1="120" y2="120"/>
|
||||
<path d="M 608 120 A 4 4 0 0 0 612 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="580" x2="580" y1="12" y2="24"/>
|
||||
<line x1="580" x2="580" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="580" x2="612" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="612" x2="612" y1="92" y2="104"/>
|
||||
<line x1="612" x2="612" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="612" x2="636" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="620" x2="620" y1="12" y2="36"/>
|
||||
<path d="M 620 36 A 4 4 0 0 0 624 40" fill="none"/>
|
||||
<path d="M 624 8 A 4 4 0 0 0 620 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="624" x2="808" y1="8" y2="8"/>
|
||||
<path d="M 812 12 A 4 4 0 0 0 808 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="624" x2="808" y1="40" y2="40"/>
|
||||
<path d="M 808 40 A 4 4 0 0 0 812 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="644" x2="644" y1="92" y2="116"/>
|
||||
<path d="M 644 116 A 4 4 0 0 0 648 120" fill="none"/>
|
||||
<path d="M 648 88 A 4 4 0 0 0 644 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="648" x2="784" y1="88" y2="88"/>
|
||||
<path d="M 788 92 A 4 4 0 0 0 784 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="648" x2="784" y1="120" y2="120"/>
|
||||
<path d="M 784 120 A 4 4 0 0 0 788 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="788" x2="788" y1="92" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="812" x2="812" y1="12" y2="24"/>
|
||||
<line x1="812" x2="812" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="812" x2="844" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="28">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="28">
|
||||
verify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="44">
|
||||
TVU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="108">
|
||||
load
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="28">
|
||||
sigverify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="108">
|
||||
execute
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="28">
|
||||
lock
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="108">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="345" y="28">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="124">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="108">
|
||||
record
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="28">
|
||||
validate
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="481" y="108">
|
||||
commit
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="537" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="545" y="28">
|
||||
fee
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="633" y="28">
|
||||
allocate
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="657" y="108">
|
||||
unlock
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="705" y="28">
|
||||
new
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="713" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="737" y="28">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,237 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="320" width="560" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="320" width="560" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="20" y1="140" y2="196"/>
|
||||
<path d="M 20 196 A 4 4 0 0 0 24 200" fill="none"/>
|
||||
<path d="M 24 136 A 4 4 0 0 0 20 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="104" y1="136" y2="136"/>
|
||||
<path d="M 108 140 A 4 4 0 0 0 104 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="104" y1="200" y2="200"/>
|
||||
<path d="M 104 200 A 4 4 0 0 0 108 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="140" y2="152"/>
|
||||
<line x1="108" x2="108" y1="152" y2="184"/>
|
||||
<line x1="108" x2="176" y1="152" y2="152"/>
|
||||
<line x1="108" x2="108" y1="184" y2="196"/>
|
||||
<line x1="108" x2="176" y1="184" y2="184"/>
|
||||
<path d="M 176 152 A 4 4 0 0 0 180 148" fill="none"/>
|
||||
<path d="M 180 188 A 4 4 0 0 0 176 184" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="108" y2="148"/>
|
||||
<path d="M 184 104 A 4 4 0 0 0 180 108" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="188" y2="244"/>
|
||||
<path d="M 180 244 A 4 4 0 0 0 184 248" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="252" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="252" y1="248" y2="248"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="28" y2="96"/>
|
||||
<path d="M 232 24 A 4 4 0 0 0 228 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="112" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="256" y2="308"/>
|
||||
<path d="M 228 308 A 4 4 0 0 0 232 312" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="552" y1="24" y2="24"/>
|
||||
<path d="M 556 28 A 4 4 0 0 0 552 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="552" y1="312" y2="312"/>
|
||||
<path d="M 552 312 A 4 4 0 0 0 556 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="76" y2="132"/>
|
||||
<path d="M 260 132 A 4 4 0 0 0 264 136" fill="none"/>
|
||||
<path d="M 264 72 A 4 4 0 0 0 260 76" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="220" y2="276"/>
|
||||
<path d="M 260 276 A 4 4 0 0 0 264 280" fill="none"/>
|
||||
<path d="M 264 216 A 4 4 0 0 0 260 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="72" y2="72"/>
|
||||
<path d="M 364 76 A 4 4 0 0 0 360 72" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="136" y2="136"/>
|
||||
<path d="M 360 136 A 4 4 0 0 0 364 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="316" y1="216" y2="216"/>
|
||||
<line x1="316" x2="316" y1="188" y2="216"/>
|
||||
<line x1="316" x2="360" y1="216" y2="216"/>
|
||||
<path d="M 320 184 A 4 4 0 0 0 316 188" fill="none"/>
|
||||
<path d="M 364 220 A 4 4 0 0 0 360 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="280" y2="280"/>
|
||||
<path d="M 360 280 A 4 4 0 0 0 364 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="320" x2="448" y1="184" y2="184"/>
|
||||
<path d="M 448 184 A 4 4 0 0 0 452 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="76" y2="104"/>
|
||||
<line x1="364" x2="364" y1="104" y2="132"/>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="388" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="220" y2="248"/>
|
||||
<line x1="364" x2="364" y1="248" y2="276"/>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="388" y1="248" y2="248"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="76" y2="132"/>
|
||||
<path d="M 396 132 A 4 4 0 0 0 400 136" fill="none"/>
|
||||
<path d="M 400 72 A 4 4 0 0 0 396 76" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="220" y2="276"/>
|
||||
<path d="M 396 276 A 4 4 0 0 0 400 280" fill="none"/>
|
||||
<path d="M 400 216 A 4 4 0 0 0 396 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="496" y1="72" y2="72"/>
|
||||
<path d="M 500 76 A 4 4 0 0 0 496 72" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="496" y1="136" y2="136"/>
|
||||
<path d="M 496 136 A 4 4 0 0 0 500 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="504" y1="216" y2="216"/>
|
||||
<path d="M 508 220 A 4 4 0 0 0 504 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="504" y1="280" y2="280"/>
|
||||
<path d="M 504 280 A 4 4 0 0 0 508 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="452" x2="452" y1="160" y2="148"/>
|
||||
<line x1="452" x2="452" y1="160" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="500" x2="500" y1="76" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="220" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="556" x2="556" y1="28" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="172">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="108">
|
||||
Verifier
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="252">
|
||||
Loader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="329" y="44">
|
||||
Solana
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="172">
|
||||
LoadAccounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="385" y="44">
|
||||
Runtime
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="252">
|
||||
Interpreter
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="108">
|
||||
Accounts
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 6.2 KiB |
@@ -1,163 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="288" width="432" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="288" width="432" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="248" y2="280"/>
|
||||
<line x1="12" x2="140" y1="248" y2="248"/>
|
||||
<line x1="12" x2="140" y1="280" y2="280"/>
|
||||
<line x1="140" x2="140" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="112" x2="126" y1="240" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="88" y2="120"/>
|
||||
<line x1="124" x2="268" y1="88" y2="88"/>
|
||||
<line x1="124" x2="268" y1="120" y2="120"/>
|
||||
<line x1="268" x2="268" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="168" y2="200"/>
|
||||
<line x1="124" x2="180" y1="168" y2="168"/>
|
||||
<line x1="124" x2="180" y1="200" y2="200"/>
|
||||
<line x1="180" x2="180" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="160" x2="174" y1="160" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="172" x2="172" y1="248" y2="280"/>
|
||||
<line x1="172" x2="300" y1="248" y2="248"/>
|
||||
<line x1="172" x2="300" y1="280" y2="280"/>
|
||||
<line x1="300" x2="300" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="178" y1="224" y2="212"/>
|
||||
<line x1="184" x2="192" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="212" x2="212" y1="168" y2="200"/>
|
||||
<line x1="212" x2="428" y1="168" y2="168"/>
|
||||
<line x1="212" x2="428" y1="200" y2="200"/>
|
||||
<line x1="428" x2="428" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="224" x2="218" y1="144" y2="132"/>
|
||||
<line x1="224" x2="232" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="8" y2="40"/>
|
||||
<line x1="236" x2="340" y1="8" y2="8"/>
|
||||
<line x1="236" x2="340" y1="40" y2="40"/>
|
||||
<line x1="340" x2="340" y1="8" y2="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="248" x2="262" y1="80" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="308" x2="308" y1="88" y2="120"/>
|
||||
<line x1="308" x2="420" y1="88" y2="88"/>
|
||||
<line x1="308" x2="420" y1="120" y2="120"/>
|
||||
<line x1="420" x2="420" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="320" x2="314" y1="64" y2="52"/>
|
||||
<line x1="320" x2="328" y1="64" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="268">
|
||||
Hash(Account1)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="108">
|
||||
Bank-Diff-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="268">
|
||||
Hash(Account2)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="188">
|
||||
Previous
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="28">
|
||||
Bank-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="297" y="188">
|
||||
Bank-Diff-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="108">
|
||||
Block-Merkle
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,203 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="584" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="584" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="248" y2="280"/>
|
||||
<line x1="12" x2="156" y1="248" y2="248"/>
|
||||
<line x1="12" x2="156" y1="280" y2="280"/>
|
||||
<line x1="156" x2="156" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="128" x2="142" y1="240" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="168" y2="200"/>
|
||||
<line x1="148" x2="212" y1="168" y2="168"/>
|
||||
<line x1="148" x2="212" y1="200" y2="200"/>
|
||||
<line x1="212" x2="212" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="88" y2="120"/>
|
||||
<line x1="180" x2="292" y1="88" y2="88"/>
|
||||
<line x1="180" x2="292" y1="120" y2="120"/>
|
||||
<line x1="292" x2="292" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="248" y2="280"/>
|
||||
<line x1="188" x2="332" y1="248" y2="248"/>
|
||||
<line x1="188" x2="332" y1="280" y2="280"/>
|
||||
<line x1="332" x2="332" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="206" y1="160" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="212" x2="212" y1="224" y2="212"/>
|
||||
<line x1="212" x2="212" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="264" x2="278" y1="80" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="8" y2="40"/>
|
||||
<line x1="284" x2="412" y1="8" y2="8"/>
|
||||
<line x1="284" x2="412" y1="40" y2="40"/>
|
||||
<line x1="412" x2="412" y1="8" y2="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="248" y2="280"/>
|
||||
<line x1="364" x2="508" y1="248" y2="248"/>
|
||||
<line x1="364" x2="508" y1="280" y2="280"/>
|
||||
<line x1="508" x2="508" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="88" y2="120"/>
|
||||
<line x1="396" x2="508" y1="88" y2="88"/>
|
||||
<line x1="396" x2="508" y1="120" y2="120"/>
|
||||
<line x1="508" x2="508" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="416" x2="410" y1="64" y2="52"/>
|
||||
<line x1="416" x2="424" y1="64" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="468" x2="468" y1="168" y2="200"/>
|
||||
<line x1="468" x2="532" y1="168" y2="168"/>
|
||||
<line x1="468" x2="532" y1="200" y2="200"/>
|
||||
<line x1="532" x2="532" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="476" x2="476" y1="224" y2="212"/>
|
||||
<line x1="476" x2="476" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="480" x2="474" y1="144" y2="132"/>
|
||||
<line x1="480" x2="488" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="536" x2="530" y1="224" y2="212"/>
|
||||
<line x1="536" x2="544" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="540" x2="540" y1="248" y2="280"/>
|
||||
<line x1="540" x2="572" y1="248" y2="248"/>
|
||||
<line x1="540" x2="572" y1="280" y2="280"/>
|
||||
<line x1="572" x2="572" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="268">
|
||||
Hash(T1,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="97" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="193" y="108">
|
||||
Entry-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="201" y="268">
|
||||
Hash(T2,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="273" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="28">
|
||||
Block-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="268">
|
||||
Hash(T3,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="108">
|
||||
Entry-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="553" y="268">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,312 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="696" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="696" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="140" y2="164"/>
|
||||
<path d="M 12 164 A 4 4 0 0 0 16 168" fill="none"/>
|
||||
<path d="M 16 136 A 4 4 0 0 0 12 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="88" y1="136" y2="136"/>
|
||||
<path d="M 92 140 A 4 4 0 0 0 88 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="88" y1="168" y2="168"/>
|
||||
<path d="M 88 168 A 4 4 0 0 0 92 164" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="92" x2="92" y1="140" y2="164"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="92" x2="124" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="92" y2="144"/>
|
||||
<path d="M 112 88 A 4 4 0 0 0 108 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="160" y2="212"/>
|
||||
<path d="M 108 212 A 4 4 0 0 0 112 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="356" y1="88" y2="88"/>
|
||||
<line x1="356" x2="396" y1="88" y2="88"/>
|
||||
<line x1="396" x2="560" y1="88" y2="88"/>
|
||||
<path d="M 564 92 A 4 4 0 0 0 560 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="380" y1="216" y2="216"/>
|
||||
<line x1="380" x2="560" y1="216" y2="216"/>
|
||||
<path d="M 560 216 A 4 4 0 0 0 564 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="124" y2="180"/>
|
||||
<path d="M 132 180 A 4 4 0 0 0 136 184" fill="none"/>
|
||||
<path d="M 136 120 A 4 4 0 0 0 132 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="192" y1="120" y2="120"/>
|
||||
<path d="M 196 124 A 4 4 0 0 0 192 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="192" y1="184" y2="184"/>
|
||||
<path d="M 192 184 A 4 4 0 0 0 196 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="196" x2="196" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="212" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="220" x2="220" y1="124" y2="180"/>
|
||||
<path d="M 220 180 A 4 4 0 0 0 224 184" fill="none"/>
|
||||
<path d="M 224 120 A 4 4 0 0 0 220 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="312" y1="120" y2="120"/>
|
||||
<path d="M 316 124 A 4 4 0 0 0 312 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="312" y1="184" y2="184"/>
|
||||
<path d="M 312 184 A 4 4 0 0 0 316 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="316" x2="332" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="28" y2="52"/>
|
||||
<path d="M 324 52 A 4 4 0 0 0 328 56" fill="none"/>
|
||||
<path d="M 328 24 A 4 4 0 0 0 324 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="432" y1="24" y2="24"/>
|
||||
<path d="M 436 28 A 4 4 0 0 0 432 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="396" y1="56" y2="56"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="396" y1="56" y2="108"/>
|
||||
<line x1="396" x2="432" y1="56" y2="56"/>
|
||||
<path d="M 432 56 A 4 4 0 0 0 436 52" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="340" x2="340" y1="124" y2="180"/>
|
||||
<path d="M 340 180 A 4 4 0 0 0 344 184" fill="none"/>
|
||||
<path d="M 344 120 A 4 4 0 0 0 340 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="344" x2="356" y1="120" y2="120"/>
|
||||
<line x1="356" x2="356" y1="80" y2="120"/>
|
||||
<line x1="356" x2="416" y1="120" y2="120"/>
|
||||
<path d="M 420 124 A 4 4 0 0 0 416 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="344" x2="380" y1="184" y2="184"/>
|
||||
<line marker-end="url(#triangle)" x1="380" x2="380" y1="184" y2="252"/>
|
||||
<line x1="380" x2="416" y1="184" y2="184"/>
|
||||
<path d="M 416 184 A 4 4 0 0 0 420 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="356" x2="356" y1="80" y2="68"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="356" x2="356" y1="268" y2="292"/>
|
||||
<path d="M 356 292 A 4 4 0 0 0 360 296" fill="none"/>
|
||||
<path d="M 360 264 A 4 4 0 0 0 356 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="360" x2="408" y1="264" y2="264"/>
|
||||
<path d="M 412 268 A 4 4 0 0 0 408 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="360" x2="408" y1="296" y2="296"/>
|
||||
<path d="M 408 296 A 4 4 0 0 0 412 292" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="412" x2="412" y1="268" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="420" x2="436" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="28" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="444" x2="444" y1="124" y2="180"/>
|
||||
<path d="M 444 180 A 4 4 0 0 0 448 184" fill="none"/>
|
||||
<path d="M 448 120 A 4 4 0 0 0 444 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="536" y1="120" y2="120"/>
|
||||
<path d="M 540 124 A 4 4 0 0 0 536 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="536" y1="184" y2="184"/>
|
||||
<path d="M 536 184 A 4 4 0 0 0 540 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="540" x2="540" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="540" x2="580" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="92" y2="144"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="160" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="124" y2="180"/>
|
||||
<path d="M 588 180 A 4 4 0 0 0 592 184" fill="none"/>
|
||||
<path d="M 592 120 A 4 4 0 0 0 588 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="592" x2="688" y1="120" y2="120"/>
|
||||
<path d="M 692 124 A 4 4 0 0 0 688 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="592" x2="688" y1="184" y2="184"/>
|
||||
<path d="M 688 184 A 4 4 0 0 0 692 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="692" x2="692" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="156">
|
||||
Clients
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="108">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="140">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="233" y="140">
|
||||
SigVerify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="233" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="44">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="140">
|
||||
Banking
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="44">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="284">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="140">
|
||||
Broadcast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="140">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="156">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,311 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="352" width="616" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="352" width="616" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="156" y2="196"/>
|
||||
<path d="M 12 196 A 4 4 0 0 0 16 200" fill="none"/>
|
||||
<path d="M 16 152 A 4 4 0 0 0 12 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="112" y1="152" y2="152"/>
|
||||
<path d="M 116 156 A 4 4 0 0 0 112 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="112" y1="200" y2="200"/>
|
||||
<path d="M 112 200 A 4 4 0 0 0 116 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="116" x2="116" y1="156" y2="168"/>
|
||||
<line x1="116" x2="116" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="116" x2="164" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="92" y2="160"/>
|
||||
<path d="M 152 88 A 4 4 0 0 0 148 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="176" y2="244"/>
|
||||
<path d="M 148 244 A 4 4 0 0 0 152 248" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="152" x2="444" y1="88" y2="88"/>
|
||||
<line x1="444" x2="608" y1="88" y2="88"/>
|
||||
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="152" x2="220" y1="248" y2="248"/>
|
||||
<line x1="220" x2="308" y1="248" y2="248"/>
|
||||
<line x1="308" x2="444" y1="248" y2="248"/>
|
||||
<line x1="444" x2="608" y1="248" y2="248"/>
|
||||
<path d="M 608 248 A 4 4 0 0 0 612 244" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="172" x2="172" y1="140" y2="196"/>
|
||||
<path d="M 172 196 A 4 4 0 0 0 176 200" fill="none"/>
|
||||
<path d="M 176 136 A 4 4 0 0 0 172 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="176" x2="232" y1="136" y2="136"/>
|
||||
<path d="M 236 140 A 4 4 0 0 0 232 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="176" x2="232" y1="200" y2="200"/>
|
||||
<path d="M 232 200 A 4 4 0 0 0 236 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="212" x2="212" y1="300" y2="340"/>
|
||||
<path d="M 212 340 A 4 4 0 0 0 216 344" fill="none"/>
|
||||
<path d="M 216 296 A 4 4 0 0 0 212 300" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="312" y1="344" y2="344"/>
|
||||
<path d="M 312 344 A 4 4 0 0 0 316 340" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="220" x2="220" y1="224" y2="212"/>
|
||||
<line x1="220" x2="220" y1="224" y2="296"/>
|
||||
<line x1="220" x2="216" y1="296" y2="296"/>
|
||||
<line x1="220" x2="312" y1="296" y2="296"/>
|
||||
<path d="M 316 300 A 4 4 0 0 0 312 296" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="140" y2="168"/>
|
||||
<line x1="236" x2="236" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="236" x2="260" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="140" y2="196"/>
|
||||
<path d="M 268 196 A 4 4 0 0 0 272 200" fill="none"/>
|
||||
<path d="M 272 136 A 4 4 0 0 0 268 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="136" y2="136"/>
|
||||
<path d="M 372 140 A 4 4 0 0 0 368 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="200" y2="200"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="200" y2="284"/>
|
||||
<line x1="308" x2="368" y1="200" y2="200"/>
|
||||
<path d="M 368 200 A 4 4 0 0 0 372 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="300" y2="340"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="140" y2="168"/>
|
||||
<line x1="372" x2="372" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="372" x2="396" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="404" x2="404" y1="12" y2="36"/>
|
||||
<path d="M 404 36 A 4 4 0 0 0 408 40" fill="none"/>
|
||||
<path d="M 408 8 A 4 4 0 0 0 404 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="404" x2="404" y1="140" y2="196"/>
|
||||
<path d="M 404 196 A 4 4 0 0 0 408 200" fill="none"/>
|
||||
<path d="M 408 136 A 4 4 0 0 0 404 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="472" y1="8" y2="8"/>
|
||||
<path d="M 476 12 A 4 4 0 0 0 472 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="472" y1="40" y2="40"/>
|
||||
<path d="M 472 40 A 4 4 0 0 0 476 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="444" y1="136" y2="136"/>
|
||||
<line x1="444" x2="444" y1="64" y2="136"/>
|
||||
<line x1="444" x2="472" y1="136" y2="136"/>
|
||||
<path d="M 476 140 A 4 4 0 0 0 472 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="444" y1="200" y2="200"/>
|
||||
<line marker-end="url(#triangle)" x1="444" x2="444" y1="200" y2="284"/>
|
||||
<line x1="444" x2="472" y1="200" y2="200"/>
|
||||
<path d="M 472 200 A 4 4 0 0 0 476 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="300" y2="324"/>
|
||||
<path d="M 420 324 A 4 4 0 0 0 424 328" fill="none"/>
|
||||
<path d="M 424 296 A 4 4 0 0 0 420 300" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="472" y1="296" y2="296"/>
|
||||
<path d="M 476 300 A 4 4 0 0 0 472 296" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="472" y1="328" y2="328"/>
|
||||
<path d="M 472 328 A 4 4 0 0 0 476 324" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="444" x2="444" y1="64" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="12" y2="36"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="140" y2="168"/>
|
||||
<line x1="476" x2="476" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="476" x2="500" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="300" y2="324"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="140" y2="196"/>
|
||||
<path d="M 508 196 A 4 4 0 0 0 512 200" fill="none"/>
|
||||
<path d="M 512 136 A 4 4 0 0 0 508 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="584" y1="136" y2="136"/>
|
||||
<path d="M 588 140 A 4 4 0 0 0 584 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="584" y1="200" y2="200"/>
|
||||
<path d="M 584 200 A 4 4 0 0 0 588 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="140" y2="196"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="612" x2="612" y1="92" y2="244"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="172">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="188">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="108">
|
||||
TVU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="156">
|
||||
Blob
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="172">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="188">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="316">
|
||||
Gossip
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="332">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="156">
|
||||
Retransmit
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="28">
|
||||
Leader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="156">
|
||||
Replay
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="433" y="316">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="521" y="156">
|
||||
Storage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="521" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.7 KiB |
@@ -1,496 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="960" width="528" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="960" width="528" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="188" y2="212"/>
|
||||
<path d="M 12 212 A 4 4 0 0 0 16 216" fill="none"/>
|
||||
<path d="M 16 184 A 4 4 0 0 0 12 188" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="184" y2="184"/>
|
||||
<path d="M 84 188 A 4 4 0 0 0 80 184" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="216" y2="216"/>
|
||||
<path d="M 80 216 A 4 4 0 0 0 84 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="20" x2="20" y1="748" y2="788"/>
|
||||
<path d="M 20 788 A 4 4 0 0 0 24 792" fill="none"/>
|
||||
<path d="M 24 744 A 4 4 0 0 0 20 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="80" y1="744" y2="744"/>
|
||||
<path d="M 84 748 A 4 4 0 0 0 80 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="80" y1="792" y2="792"/>
|
||||
<path d="M 80 792 A 4 4 0 0 0 84 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="188" y2="200"/>
|
||||
<line x1="84" x2="84" y1="200" y2="212"/>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="200" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="748" y2="760"/>
|
||||
<line x1="84" x2="84" y1="760" y2="788"/>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="124" y2="192"/>
|
||||
<path d="M 112 120 A 4 4 0 0 0 108 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="208" y2="436"/>
|
||||
<path d="M 108 436 A 4 4 0 0 0 112 440" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="700" y2="752"/>
|
||||
<path d="M 112 696 A 4 4 0 0 0 108 700" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="768" y2="836"/>
|
||||
<path d="M 108 836 A 4 4 0 0 0 112 840" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="120" y2="120"/>
|
||||
<path d="M 396 124 A 4 4 0 0 0 392 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="440" y2="440"/>
|
||||
<path d="M 392 440 A 4 4 0 0 0 396 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="696" y2="696"/>
|
||||
<path d="M 396 700 A 4 4 0 0 0 392 696" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="840" y2="840"/>
|
||||
<path d="M 392 840 A 4 4 0 0 0 396 836" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="172" y2="212"/>
|
||||
<path d="M 132 212 A 4 4 0 0 0 136 216" fill="none"/>
|
||||
<path d="M 136 168 A 4 4 0 0 0 132 172" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="268" y2="308"/>
|
||||
<path d="M 132 308 A 4 4 0 0 0 136 312" fill="none"/>
|
||||
<path d="M 136 264 A 4 4 0 0 0 132 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="748" y2="788"/>
|
||||
<path d="M 132 788 A 4 4 0 0 0 136 792" fill="none"/>
|
||||
<path d="M 136 744 A 4 4 0 0 0 132 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="168" y2="168"/>
|
||||
<path d="M 228 172 A 4 4 0 0 0 224 168" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="164" y1="216" y2="216"/>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="164" y1="216" y2="252"/>
|
||||
<line x1="164" x2="224" y1="216" y2="216"/>
|
||||
<path d="M 224 216 A 4 4 0 0 0 228 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="264" y2="264"/>
|
||||
<path d="M 228 268 A 4 4 0 0 0 224 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="312" y2="312"/>
|
||||
<path d="M 224 312 A 4 4 0 0 0 228 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="744" y2="744"/>
|
||||
<path d="M 228 748 A 4 4 0 0 0 224 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="792" y2="792"/>
|
||||
<path d="M 224 792 A 4 4 0 0 0 228 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="172" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="268" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="748" y2="760"/>
|
||||
<line x1="228" x2="228" y1="760" y2="788"/>
|
||||
<line marker-end="url(#triangle)" x1="228" x2="260" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="240" x2="236" y1="280" y2="280"/>
|
||||
<line marker-end="url(#triangle)" x1="240" x2="260" y1="280" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="28" y2="68"/>
|
||||
<path d="M 268 68 A 4 4 0 0 0 272 72" fill="none"/>
|
||||
<path d="M 272 24 A 4 4 0 0 0 268 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="172" y2="212"/>
|
||||
<path d="M 268 212 A 4 4 0 0 0 272 216" fill="none"/>
|
||||
<path d="M 272 168 A 4 4 0 0 0 268 172" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="268" y2="308"/>
|
||||
<path d="M 268 308 A 4 4 0 0 0 272 312" fill="none"/>
|
||||
<path d="M 272 264 A 4 4 0 0 0 268 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="364" y2="404"/>
|
||||
<path d="M 268 404 A 4 4 0 0 0 272 408" fill="none"/>
|
||||
<path d="M 272 360 A 4 4 0 0 0 268 364" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="492" y2="532"/>
|
||||
<path d="M 268 532 A 4 4 0 0 0 272 536" fill="none"/>
|
||||
<path d="M 272 488 A 4 4 0 0 0 268 492" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="604" y2="644"/>
|
||||
<path d="M 268 644 A 4 4 0 0 0 272 648" fill="none"/>
|
||||
<path d="M 272 600 A 4 4 0 0 0 268 604" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="748" y2="788"/>
|
||||
<path d="M 268 788 A 4 4 0 0 0 272 792" fill="none"/>
|
||||
<path d="M 272 744 A 4 4 0 0 0 268 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="892" y2="932"/>
|
||||
<path d="M 268 932 A 4 4 0 0 0 272 936" fill="none"/>
|
||||
<path d="M 272 888 A 4 4 0 0 0 268 892" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="24" y2="24"/>
|
||||
<path d="M 372 28 A 4 4 0 0 0 368 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="72" y2="72"/>
|
||||
<line x1="308" x2="308" y1="72" y2="112"/>
|
||||
<line x1="308" x2="368" y1="72" y2="72"/>
|
||||
<path d="M 368 72 A 4 4 0 0 0 372 68" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="168" y2="168"/>
|
||||
<path d="M 372 172 A 4 4 0 0 0 368 168" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="216" y2="216"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="216" y2="252"/>
|
||||
<line x1="308" x2="368" y1="216" y2="216"/>
|
||||
<path d="M 368 216 A 4 4 0 0 0 372 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="264" y2="264"/>
|
||||
<path d="M 372 268 A 4 4 0 0 0 368 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="312" y2="312"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="312" y2="348"/>
|
||||
<line x1="308" x2="368" y1="312" y2="312"/>
|
||||
<path d="M 368 312 A 4 4 0 0 0 372 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="360" y2="360"/>
|
||||
<path d="M 372 364 A 4 4 0 0 0 368 360" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="408" y2="408"/>
|
||||
<line x1="308" x2="308" y1="408" y2="432"/>
|
||||
<line x1="308" x2="368" y1="408" y2="408"/>
|
||||
<path d="M 368 408 A 4 4 0 0 0 372 404" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="488" y2="488"/>
|
||||
<path d="M 372 492 A 4 4 0 0 0 368 488" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="536" y2="536"/>
|
||||
<path d="M 368 536 A 4 4 0 0 0 372 532" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="600" y2="600"/>
|
||||
<path d="M 372 604 A 4 4 0 0 0 368 600" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="332" y1="648" y2="648"/>
|
||||
<line x1="332" x2="332" y1="648" y2="688"/>
|
||||
<line x1="332" x2="368" y1="648" y2="648"/>
|
||||
<path d="M 368 648 A 4 4 0 0 0 372 644" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="300" y1="744" y2="744"/>
|
||||
<line x1="300" x2="300" y1="704" y2="744"/>
|
||||
<line x1="300" x2="368" y1="744" y2="744"/>
|
||||
<path d="M 372 748 A 4 4 0 0 0 368 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="316" y1="792" y2="792"/>
|
||||
<line x1="316" x2="316" y1="792" y2="832"/>
|
||||
<line x1="316" x2="368" y1="792" y2="792"/>
|
||||
<path d="M 368 792 A 4 4 0 0 0 372 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="888" y2="888"/>
|
||||
<path d="M 372 892 A 4 4 0 0 0 368 888" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="936" y2="936"/>
|
||||
<path d="M 368 936 A 4 4 0 0 0 372 932" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="300" x2="300" y1="672" y2="660"/>
|
||||
<line x1="300" x2="300" y1="672" y2="688"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="128" y2="156"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="448" y2="476"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="316" x2="316" y1="848" y2="876"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="332" x2="332" y1="704" y2="732"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="28" y2="68"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="172" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="268" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="364" y2="404"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="492" y2="532"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="604" y2="644"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="748" y2="788"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="892" y2="932"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="384" x2="380" y1="760" y2="760"/>
|
||||
<line marker-end="url(#triangle)" x1="384" x2="412" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="124" y2="436"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="700" y2="752"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="768" y2="836"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="748" y2="788"/>
|
||||
<path d="M 420 788 A 4 4 0 0 0 424 792" fill="none"/>
|
||||
<path d="M 424 744 A 4 4 0 0 0 420 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="520" y1="744" y2="744"/>
|
||||
<path d="M 524 748 A 4 4 0 0 0 520 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="520" y1="792" y2="792"/>
|
||||
<path d="M 520 792 A 4 4 0 0 0 524 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="524" x2="524" y1="748" y2="788"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="204">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="764">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="140">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="716">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="188">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="204">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="284">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="764">
|
||||
SigVerify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="44">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="60">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="188">
|
||||
Repair
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="204">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="284">
|
||||
Blockstore
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="380">
|
||||
Multicast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="396">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="508">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="524">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="620">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="636">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="764">
|
||||
Banking
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="908">
|
||||
Banktree
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="433" y="764">
|
||||
Blockstore
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,456 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="480" width="592" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="480" width="592" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="60" y2="116"/>
|
||||
<path d="M 12 116 A 4 4 0 0 0 16 120" fill="none"/>
|
||||
<path d="M 16 56 A 4 4 0 0 0 12 60" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="56" y2="56"/>
|
||||
<path d="M 84 60 A 4 4 0 0 0 80 56" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="52" y1="120" y2="120"/>
|
||||
<line x1="52" x2="52" y1="120" y2="420"/>
|
||||
<line x1="52" x2="80" y1="120" y2="120"/>
|
||||
<path d="M 52 420 A 4 4 0 0 0 56 424" fill="none"/>
|
||||
<path d="M 80 120 A 4 4 0 0 0 84 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="56" x2="124" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="60" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="96" x2="92" y1="104" y2="104"/>
|
||||
<line x1="96" x2="132" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="12" y2="64"/>
|
||||
<path d="M 112 8 A 4 4 0 0 0 108 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="112" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="432" y2="468"/>
|
||||
<path d="M 108 468 A 4 4 0 0 0 112 472" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="416" y1="8" y2="8"/>
|
||||
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="416" y1="472" y2="472"/>
|
||||
<path d="M 416 472 A 4 4 0 0 0 420 468" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="236" y2="276"/>
|
||||
<path d="M 124 276 A 4 4 0 0 0 128 280" fill="none"/>
|
||||
<path d="M 128 232 A 4 4 0 0 0 124 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="128" x2="156" y1="232" y2="232"/>
|
||||
<line x1="156" x2="156" y1="144" y2="232"/>
|
||||
<line x1="156" x2="184" y1="232" y2="232"/>
|
||||
<path d="M 188 236 A 4 4 0 0 0 184 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="128" x2="184" y1="280" y2="280"/>
|
||||
<path d="M 184 280 A 4 4 0 0 0 188 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="60" y2="116"/>
|
||||
<path d="M 132 116 A 4 4 0 0 0 136 120" fill="none"/>
|
||||
<path d="M 136 56 A 4 4 0 0 0 132 60" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="412" y2="436"/>
|
||||
<path d="M 132 436 A 4 4 0 0 0 136 440" fill="none"/>
|
||||
<path d="M 136 408 A 4 4 0 0 0 132 412" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="288" y1="56" y2="56"/>
|
||||
<path d="M 292 60 A 4 4 0 0 0 288 56" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="288" y1="120" y2="120"/>
|
||||
<path d="M 288 120 A 4 4 0 0 0 292 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="156" y1="408" y2="408"/>
|
||||
<line x1="156" x2="156" y1="304" y2="408"/>
|
||||
<line x1="156" x2="176" y1="408" y2="408"/>
|
||||
<path d="M 180 412 A 4 4 0 0 0 176 408" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="176" y1="440" y2="440"/>
|
||||
<path d="M 176 440 A 4 4 0 0 0 180 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="144" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="304" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="412" y2="424"/>
|
||||
<line x1="180" x2="180" y1="424" y2="436"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="220" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="200" x2="196" y1="248" y2="248"/>
|
||||
<line x1="200" x2="212" y1="248" y2="248"/>
|
||||
<line x1="212" x2="212" y1="236" y2="248"/>
|
||||
<line x1="212" x2="212" y1="248" y2="276"/>
|
||||
<path d="M 212 276 A 4 4 0 0 0 216 280" fill="none"/>
|
||||
<path d="M 216 232 A 4 4 0 0 0 212 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="204" x2="204" y1="156" y2="180"/>
|
||||
<path d="M 204 180 A 4 4 0 0 0 208 184" fill="none"/>
|
||||
<path d="M 208 152 A 4 4 0 0 0 204 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="336" y1="152" y2="152"/>
|
||||
<path d="M 340 156 A 4 4 0 0 0 336 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="336" y1="184" y2="184"/>
|
||||
<path d="M 336 184 A 4 4 0 0 0 340 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="252" y1="232" y2="232"/>
|
||||
<line x1="252" x2="252" y1="208" y2="232"/>
|
||||
<line x1="252" x2="280" y1="232" y2="232"/>
|
||||
<path d="M 284 236 A 4 4 0 0 0 280 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="280" y1="280" y2="280"/>
|
||||
<path d="M 280 280 A 4 4 0 0 0 284 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="412" y2="452"/>
|
||||
<path d="M 228 452 A 4 4 0 0 0 232 456" fill="none"/>
|
||||
<path d="M 232 408 A 4 4 0 0 0 228 412" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="292" y1="408" y2="408"/>
|
||||
<line x1="292" x2="292" y1="384" y2="408"/>
|
||||
<line x1="292" x2="320" y1="408" y2="408"/>
|
||||
<path d="M 324 412 A 4 4 0 0 0 320 408" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="320" y1="456" y2="456"/>
|
||||
<path d="M 320 456 A 4 4 0 0 0 324 452" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="252" x2="252" y1="208" y2="196"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="252" x2="252" y1="332" y2="356"/>
|
||||
<path d="M 252 356 A 4 4 0 0 0 256 360" fill="none"/>
|
||||
<path d="M 256 328 A 4 4 0 0 0 252 332" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="256" x2="276" y1="328" y2="328"/>
|
||||
<line x1="276" x2="276" y1="304" y2="328"/>
|
||||
<line x1="276" x2="344" y1="328" y2="328"/>
|
||||
<path d="M 348 332 A 4 4 0 0 0 344 328" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="256" x2="344" y1="360" y2="360"/>
|
||||
<path d="M 344 360 A 4 4 0 0 0 348 356" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="276" x2="276" y1="304" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="292" x2="292" y1="60" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="292" x2="292" y1="384" y2="372"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="300" x2="300" y1="236" y2="276"/>
|
||||
<path d="M 300 276 A 4 4 0 0 0 304 280" fill="none"/>
|
||||
<path d="M 304 232 A 4 4 0 0 0 300 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="392" y1="232" y2="232"/>
|
||||
<path d="M 396 236 A 4 4 0 0 0 392 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="324" y1="280" y2="280"/>
|
||||
<line marker-end="url(#triangle)" x1="324" x2="324" y1="280" y2="316"/>
|
||||
<line x1="324" x2="392" y1="280" y2="280"/>
|
||||
<path d="M 392 280 A 4 4 0 0 0 396 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="412" y2="424"/>
|
||||
<line x1="324" x2="324" y1="424" y2="452"/>
|
||||
<line marker-end="url(#triangle)" x1="324" x2="452" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="340" x2="340" y1="156" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="332" y2="356"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="352" x2="348" y1="168" y2="168"/>
|
||||
<line x1="352" x2="436" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="248" y2="248"/>
|
||||
<line x1="408" x2="460" y1="248" y2="248"/>
|
||||
<line x1="460" x2="460" y1="220" y2="248"/>
|
||||
<line x1="460" x2="460" y1="248" y2="292"/>
|
||||
<path d="M 460 292 A 4 4 0 0 0 464 296" fill="none"/>
|
||||
<path d="M 464 216 A 4 4 0 0 0 460 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="12" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="176" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="256" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="432" y2="468"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="156" y2="240"/>
|
||||
<path d="M 440 152 A 4 4 0 0 0 436 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="256" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="432" y2="452"/>
|
||||
<path d="M 436 452 A 4 4 0 0 0 440 456" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="440" x2="584" y1="152" y2="152"/>
|
||||
<path d="M 588 156 A 4 4 0 0 0 584 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="440" x2="584" y1="456" y2="456"/>
|
||||
<path d="M 584 456 A 4 4 0 0 0 588 452" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="364" y2="436"/>
|
||||
<path d="M 460 436 A 4 4 0 0 0 464 440" fill="none"/>
|
||||
<path d="M 464 360 A 4 4 0 0 0 460 364" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="216" y2="216"/>
|
||||
<path d="M 564 220 A 4 4 0 0 0 560 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="296" y2="296"/>
|
||||
<path d="M 560 296 A 4 4 0 0 0 564 292" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="360" y2="360"/>
|
||||
<path d="M 564 364 A 4 4 0 0 0 560 360" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="440" y2="440"/>
|
||||
<path d="M 560 440 A 4 4 0 0 0 564 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="220" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="364" y2="436"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="156" y2="452"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="92">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="129" y="28">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="252">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="268">
|
||||
Forks
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="92">
|
||||
JSON
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="428">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="92">
|
||||
RPC
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="92">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="172">
|
||||
Gossip
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="252">
|
||||
Replay
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="268">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="241" y="428">
|
||||
Broadcast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="241" y="444">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="265" y="348">
|
||||
Blocktree
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="273" y="172">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="313" y="252">
|
||||
BlobFetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="268">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="172">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="252">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="268">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="396">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="412">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,81 +1,59 @@
|
||||
# Table of contents
|
||||
# Solana Architecture
|
||||
|
||||
* [Introduction](introduction.md)
|
||||
* [Terminology](terminology.md)
|
||||
* [Getting Started](getting-started/README.md)
|
||||
* [Testnet Participation](getting-started/testnet-participation.md)
|
||||
* [Example Client: Web Wallet](getting-started/webwallet.md)
|
||||
* [Programming Model](programs/README.md)
|
||||
* [Example: Tic-Tac-Toe](programs/tictactoe.md)
|
||||
* [Drones](programs/drones.md)
|
||||
* [A Solana Cluster](cluster/README.md)
|
||||
* [Synchronization](cluster/synchronization.md)
|
||||
* [Leader Rotation](cluster/leader-rotation.md)
|
||||
* [Fork Generation](cluster/fork-generation.md)
|
||||
* [Managing Forks](cluster/managing-forks.md)
|
||||
* [Turbine Block Propagation](cluster/turbine-block-propagation.md)
|
||||
* [Ledger Replication](cluster/ledger-replication.md)
|
||||
* [Secure Vote Signing](cluster/vote-signing.md)
|
||||
* [Stake Delegation and Rewards](cluster/stake-delegation-and-rewards.md)
|
||||
* [Performance Metrics](cluster/performance-metrics.md)
|
||||
* [Anatomy of a Validator](validator/README.md)
|
||||
* [TPU](validator/tpu.md)
|
||||
* [TVU](validator/tvu/README.md)
|
||||
* [Blocktree](validator/tvu/blocktree.md)
|
||||
* [Gossip Service](validator/gossip.md)
|
||||
* [The Runtime](validator/runtime.md)
|
||||
* [Anatomy of a Transaction](transaction.md)
|
||||
* [Running a Validator](running-validator/README.md)
|
||||
* [Hardware Requirements](running-validator/validator-hardware.md)
|
||||
* [Choosing a Testnet](running-validator/validator-testnet.md)
|
||||
* [Installing the Validator Software](running-validator/validator-software.md)
|
||||
* [Starting a Validator](running-validator/validator-start.md)
|
||||
* [Staking](running-validator/validator-stake.md)
|
||||
* [Monitoring a Validator](running-validator/validator-monitor.md)
|
||||
* [Publishing Validator Info](running-validator/validator-info.md)
|
||||
* [Troubleshooting](running-validator/validator-troubleshoot.md)
|
||||
* [FAQ](running-validator/validator-faq.md)
|
||||
* [Running a Replicator](running-replicator.md)
|
||||
* [API Reference](api-reference/README.md)
|
||||
* [Transaction](api-reference/transaction-api.md)
|
||||
* [Instruction](api-reference/instruction-api.md)
|
||||
* [Blockstreamer](api-reference/blockstreamer.md)
|
||||
* [JSON RPC API](api-reference/jsonrpc-api.md)
|
||||
* [JavaScript API](api-reference/javascript-api.md)
|
||||
* [solana CLI](api-reference/cli.md)
|
||||
* [Accepted Design Proposals](proposals/README.md)
|
||||
* [Ledger Replication](proposals/ledger-replication-to-implement.md)
|
||||
* [Secure Vote Signing](proposals/vote-signing-to-implement.md)
|
||||
* [Staking Rewards](proposals/staking-rewards.md)
|
||||
* [Cluster Economics](proposals/ed_overview/README.md)
|
||||
* [Validation-client Economics](proposals/ed_overview/ed_validation_client_economics/README.md)
|
||||
* [State-validation Protocol-based Rewards](proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md)
|
||||
* [State-validation Transaction Fees](proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md)
|
||||
* [Replication-validation Transaction Fees](proposals/ed_overview/ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md)
|
||||
* [Validation Stake Delegation](proposals/ed_overview/ed_validation_client_economics/ed_vce_validation_stake_delegation.md)
|
||||
* [Replication-client Economics](proposals/ed_overview/ed_replication_client_economics/README.md)
|
||||
* [Storage-replication Rewards](proposals/ed_overview/ed_replication_client_economics/ed_rce_storage_replication_rewards.md)
|
||||
* [Replication-client Reward Auto-delegation](proposals/ed_overview/ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md)
|
||||
* [Economic Sustainability](proposals/ed_overview/ed_economic_sustainability.md)
|
||||
* [Attack Vectors](proposals/ed_overview/ed_attack_vectors.md)
|
||||
* [Economic Design MVP](proposals/ed_overview/ed_mvp.md)
|
||||
* [References](proposals/ed_overview/ed_references.md)
|
||||
* [Cluster Test Framework](proposals/cluster-test-framework.md)
|
||||
* [Validator](proposals/validator-proposal.md)
|
||||
* [Simple Payment and State Verification](proposals/simple-payment-and-state-verification.md)
|
||||
* [Cross-Program Invocation](proposals/cross-program-invocation.md)
|
||||
* [Implemented Design Proposals](implemented-proposals/README.md)
|
||||
* [Blocktree](implemented-proposals/blocktree.md)
|
||||
* [Cluster Software Installation and Updates](implemented-proposals/installer.md)
|
||||
* [Deterministic Transaction Fees](implemented-proposals/transaction-fees.md)
|
||||
* [Tower BFT](implemented-proposals/tower-bft.md)
|
||||
* [Leader-to-Leader Transition](implemented-proposals/leader-leader-transition.md)
|
||||
* [Leader-to-Validator Transition](implemented-proposals/leader-validator-transition.md)
|
||||
* [Passive Stake Delegation and Rewards](implemented-proposals/passive-stake-delegation-and-rewards.md)
|
||||
* [Persistent Account Storage](implemented-proposals/persistent-account-storage.md)
|
||||
* [Reliable Vote Transmission](implemented-proposals/reliable-vote-transmission.md)
|
||||
* [Repair Service](implemented-proposals/repair-service.md)
|
||||
* [Testing Programs](implemented-proposals/testing-programs.md)
|
||||
* [Credit-only Accounts](implemented-proposals/credit-only-credit-debit-accounts.md)
|
||||
* [Embedding the Move Langauge](implemented-proposals/embedding-move.md)
|
||||
- [Introduction](introduction.md)
|
||||
|
||||
- [Terminology](terminology.md)
|
||||
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Example: Web Wallet](webwallet.md)
|
||||
|
||||
- [Programming Model](programs.md)
|
||||
- [Example: Tic-Tac-Toe](tictactoe.md)
|
||||
- [Drones](drones.md)
|
||||
|
||||
- [A Solana Cluster](cluster.md)
|
||||
- [Synchronization](synchronization.md)
|
||||
- [Leader Rotation](leader-rotation.md)
|
||||
- [Fork Generation](fork-generation.md)
|
||||
- [Managing Forks](managing-forks.md)
|
||||
- [Data Plane Fanout](data-plane-fanout.md)
|
||||
- [Ledger Replication](ledger-replication.md)
|
||||
- [Secure Vote Signing](vote-signing.md)
|
||||
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
|
||||
|
||||
- [Anatomy of a Fullnode](fullnode.md)
|
||||
- [TPU](tpu.md)
|
||||
- [TVU](tvu.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Gossip Service](gossip.md)
|
||||
- [The Runtime](runtime.md)
|
||||
|
||||
- [API Reference](api-reference.md)
|
||||
- [Blockstreamer](blockstreamer.md)
|
||||
- [JSON RPC API](jsonrpc-api.md)
|
||||
- [JavaScript API](javascript-api.md)
|
||||
- [solana-wallet CLI](wallet.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)
|
||||
- [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md)
|
||||
- [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md)
|
||||
- [Validation Stake Delegation](ed_vce_validation_stake_delegation.md)
|
||||
- [Replication-client Economics](ed_replication_client_economics.md)
|
||||
- [Storage-replication Rewards](ed_rce_storage_replication_rewards.md)
|
||||
- [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md)
|
||||
- [Economic Sustainability](ed_economic_sustainability.md)
|
||||
- [Attack Vectors](ed_attack_vectors.md)
|
||||
- [References](ed_references.md)
|
||||
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
|
@@ -1,4 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
The following sections contain API references material you may find useful when developing applications utilizing a Solana cluster.
|
||||
|
@@ -1,28 +0,0 @@
|
||||
# Blockstreamer
|
||||
|
||||
Solana supports a node type called an _blockstreamer_. This fullnode variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
|
||||
|
||||
A blockstreamer runs without a vote signer, and can optionally stream ledger entries out to a Unix domain socket as they are processed. The JSON-RPC service still functions as on any other node.
|
||||
|
||||
To run a blockstreamer, include the argument `no-signer` and \(optional\) `blockstream` socket location:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
|
||||
```
|
||||
|
||||
The stream will output a series of JSON objects:
|
||||
|
||||
* An Entry event JSON object is sent when each ledger entry is processed, with the following fields:
|
||||
* `dt`, the system datetime, as RFC3339-formatted string
|
||||
* `t`, the event type, always "entry"
|
||||
* `s`, the slot height, as unsigned 64-bit integer
|
||||
* `h`, the tick height, as unsigned 64-bit integer
|
||||
* `entry`, the entry, as JSON object
|
||||
* A Block event JSON object is sent when a block is complete, with the following fields:
|
||||
* `dt`, the system datetime, as RFC3339-formatted string
|
||||
* `t`, the event type, always "block"
|
||||
* `s`, the slot height, as unsigned 64-bit integer
|
||||
* `h`, the tick height, as unsigned 64-bit integer
|
||||
* `l`, the slot leader id, as base-58 encoded string
|
||||
* `id`, the block id, as base-58 encoded string
|
||||
|
@@ -1,38 +0,0 @@
|
||||
# Instruction
|
||||
|
||||
For the purposes of building a [Transaction](../transaction.md), a more verbose instruction format is used:
|
||||
|
||||
* **Instruction:**
|
||||
* **program\_id:** The pubkey of the on-chain program that executes the
|
||||
|
||||
instruction
|
||||
|
||||
* **accounts:** An ordered list of accounts that should be passed to
|
||||
|
||||
the program processing the instruction, including metadata detailing
|
||||
|
||||
if an account is a signer of the transaction and if it is a credit
|
||||
|
||||
only account.
|
||||
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
|
||||
instruction
|
||||
|
||||
A more compact form is actually included in a `Transaction`:
|
||||
|
||||
* **CompiledInstruction:**
|
||||
* **program\_id\_index:** The index of the `program_id` in the
|
||||
|
||||
`account_keys` list
|
||||
|
||||
* **accounts:** An ordered list of indices into `account_keys`
|
||||
|
||||
specifying the accounds that should be passed to the program
|
||||
|
||||
processing the instruction.
|
||||
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
|
||||
instruction
|
||||
|
@@ -1,4 +0,0 @@
|
||||
# JavaScript API
|
||||
|
||||
See [solana-web3](https://solana-labs.github.io/solana-web3.js/).
|
||||
|
@@ -1,788 +0,0 @@
|
||||
# JSON RPC API
|
||||
|
||||
Solana nodes accept HTTP requests using the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
|
||||
|
||||
To interact with a Solana node inside a JavaScript application, use the [solana-web3.js](https://github.com/solana-labs/solana-web3.js) library, which gives a convenient interface for the RPC methods.
|
||||
|
||||
## RPC HTTP Endpoint
|
||||
|
||||
**Default port:** 8899 eg. [http://localhost:8899](http://localhost:8899), [http://192.168.1.88:8899](http://192.168.1.88:8899)
|
||||
|
||||
## RPC PubSub WebSocket Endpoint
|
||||
|
||||
**Default port:** 8900 eg. ws://localhost:8900, [http://192.168.1.88:8900](http://192.168.1.88:8900)
|
||||
|
||||
## Methods
|
||||
|
||||
* [confirmTransaction](jsonrpc-api.md#confirmtransaction)
|
||||
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
||||
* [getBalance](jsonrpc-api.md#getbalance)
|
||||
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
|
||||
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
|
||||
* [getGenesisBlockhash](jsonrpc-api.md#getgenesisblockhash)
|
||||
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
|
||||
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
|
||||
* [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash)
|
||||
* [getSignatureStatus](jsonrpc-api.md#getsignaturestatus)
|
||||
* [getSlot](jsonrpc-api.md#getslot)
|
||||
* [getSlotLeader](jsonrpc-api.md#getslotleader)
|
||||
* [getSlotsPerSegment](jsonrpc-api.md#getslotspersegment)
|
||||
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
|
||||
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
||||
* [getTotalSupply](jsonrpc-api.md#gettotalsupply)
|
||||
* [getVersion](jsonrpc-api.md#getversion)
|
||||
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
|
||||
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
|
||||
* [sendTransaction](jsonrpc-api.md#sendtransaction)
|
||||
* [startSubscriptionChannel](jsonrpc-api.md#startsubscriptionchannel)
|
||||
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
|
||||
* [accountSubscribe](jsonrpc-api.md#accountsubscribe)
|
||||
* [accountUnsubscribe](jsonrpc-api.md#accountunsubscribe)
|
||||
* [programSubscribe](jsonrpc-api.md#programsubscribe)
|
||||
* [programUnsubscribe](jsonrpc-api.md#programunsubscribe)
|
||||
* [signatureSubscribe](jsonrpc-api.md#signaturesubscribe)
|
||||
* [signatureUnsubscribe](jsonrpc-api.md#signatureunsubscribe)
|
||||
|
||||
## Request Formatting
|
||||
|
||||
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
|
||||
|
||||
* `jsonrpc`, set to `"2.0"`
|
||||
* `id`, a unique client-generated identifying integer
|
||||
* `method`, a string containing the method to be invoked
|
||||
* `params`, a JSON array of ordered parameter values
|
||||
|
||||
Example using curl:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
|
||||
```
|
||||
|
||||
The response output will be a JSON object with the following fields:
|
||||
|
||||
* `jsonrpc`, matching the request specification
|
||||
* `id`, matching the request identifier
|
||||
* `result`, requested data or success confirmation
|
||||
|
||||
Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
|
||||
|
||||
## Definitions
|
||||
|
||||
* Hash: A SHA-256 hash of a chunk of data.
|
||||
* Pubkey: The public key of a Ed25519 key-pair.
|
||||
* Signature: An Ed25519 signature of a chunk of data.
|
||||
* Transaction: A Solana instruction signed by a client key-pair.
|
||||
|
||||
## JSON RPC API Reference
|
||||
|
||||
### confirmTransaction
|
||||
|
||||
Returns a transaction receipt
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
#### Results:
|
||||
|
||||
* `boolean` - Transaction status, true if Transaction is confirmed
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":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
|
||||
|
||||
Returns the balance of the account of provided Pubkey
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - quantity, as a signed 64-bit integer
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":0,"id":1}
|
||||
```
|
||||
|
||||
### getClusterNodes
|
||||
|
||||
Returns information about all the nodes participating in the cluster
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
|
||||
* `pubkey` - Node public key, as base-58 encoded string
|
||||
* `gossip` - Gossip network address for the node
|
||||
* `tpu` - TPU network address for the node
|
||||
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
|
||||
```
|
||||
|
||||
### 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}
|
||||
```
|
||||
|
||||
### getGenesisBlockhash
|
||||
|
||||
Returns the genesis block hash
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","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` - the account Pubkey as base-58 encoded string and a JSON object, with the following sub fields:
|
||||
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `data`, array of bytes representing any data associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
|
||||
```
|
||||
|
||||
### getRecentBlockhash
|
||||
|
||||
Returns a recent block hash from the ledger, and a fee schedule that can be used to compute the cost of submitting a transaction using it.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
An array consisting of
|
||||
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
* `FeeCalculator object` - the fee schedule for this block hash
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
|
||||
```
|
||||
|
||||
### getSignatureStatus
|
||||
|
||||
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `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 [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
|
||||
```
|
||||
|
||||
### getSlot
|
||||
|
||||
Returns the current slot the node is processing
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `u64` - Current slot
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1234","id":1}
|
||||
```
|
||||
|
||||
### getSlotLeader
|
||||
|
||||
Returns the current slot leader
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `string` - Node Id as base-58 encoded string
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
||||
```
|
||||
|
||||
### getSlotsPerSegment
|
||||
|
||||
Returns the current storage segment size in terms of slots
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `u64` - Number of slots in a storage segment
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
```
|
||||
|
||||
### getStorageTurn
|
||||
|
||||
Returns the current storage turn's blockhash and slot
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
An array consisting of
|
||||
|
||||
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
|
||||
* `u64` - the current storage turn slot
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
|
||||
```
|
||||
|
||||
### getStorageTurnRate
|
||||
|
||||
Returns the current storage turn rate in terms of slots per turn
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `u64` - Number of slots in storage turn
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
```
|
||||
|
||||
### getNumBlocksSinceSignatureConfirmation
|
||||
|
||||
Returns the current number of blocks since signature has been confirmed.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - count, as unsigned 64-bit integer
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":8,"id":1}
|
||||
```
|
||||
|
||||
### getTransactionCount
|
||||
|
||||
Returns the current Transaction count from the ledger
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### 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":"getTransactionCount"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":268,"id":1}
|
||||
```
|
||||
|
||||
### 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}
|
||||
```
|
||||
|
||||
### getVersion
|
||||
|
||||
Returns the current solana versions running on the node
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
|
||||
* `solana-core`, software version of solana-core
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
|
||||
```
|
||||
|
||||
### getVoteAccounts
|
||||
|
||||
Returns the account info and associated stake for all the voting accounts in the current bank.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
The result field will be a JSON object of `current` and `delinquent` accounts, each containing an array of JSON objects with the following sub fields:
|
||||
|
||||
* `votePubkey` - Vote account public key, as base-58 encoded string
|
||||
* `nodePubkey` - Node public key, as base-58 encoded string
|
||||
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
|
||||
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
|
||||
* `commission`, an 8-bit integer used as a fraction \(commission/MAX\_U8\) for rewards payout
|
||||
* `lastVote` - Most recent slot voted on by this vote account
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
|
||||
```
|
||||
|
||||
### requestAirdrop
|
||||
|
||||
Requests an airdrop of lamports to a Pubkey
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
|
||||
* `integer` - lamports, as a signed 64-bit integer
|
||||
|
||||
#### Results:
|
||||
|
||||
* `string` - Transaction Signature of airdrop, as base-58 encoded string
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
|
||||
```
|
||||
|
||||
### sendTransaction
|
||||
|
||||
Creates new transaction
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `array` - array of octets containing a fully-signed Transaction
|
||||
|
||||
#### Results:
|
||||
|
||||
* `string` - Transaction Signature, as base-58 encoded string
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
|
||||
```
|
||||
|
||||
### Subscription Websocket
|
||||
|
||||
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
|
||||
|
||||
* Submit subscription requests to the websocket using the methods below
|
||||
* Multiple subscriptions may be active at once
|
||||
* All subscriptions take an optional `confirmations` parameter, which defines
|
||||
|
||||
how many confirmed blocks the node should wait before sending a notification.
|
||||
|
||||
The greater the number, the more likely the notification is to represent
|
||||
|
||||
consensus across the cluster, and the less likely it is to be affected by
|
||||
|
||||
forking or rollbacks. If unspecified, the default value is 0; the node will
|
||||
|
||||
send a notification as soon as it witnesses the event. The maximum
|
||||
|
||||
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
|
||||
|
||||
represents the economic finality of the chain.
|
||||
|
||||
### accountSubscribe
|
||||
|
||||
Subscribe to an account to receive notifications when the lamports or data for a given account public key changes
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - Subscription id \(needed to unsubscribe\)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
|
||||
```
|
||||
|
||||
### accountUnsubscribe
|
||||
|
||||
Unsubscribe from account change notifications
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `integer` - id of account Subscription to cancel
|
||||
|
||||
#### Results:
|
||||
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
### programSubscribe
|
||||
|
||||
Subscribe to a program to receive notifications when the lamports or data for a given account owned by the program changes
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - program\_id Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - Subscription id \(needed to unsubscribe\)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `object` - account info JSON object \(see [getAccountInfo](jsonrpc-api.md#getaccountinfo) for field details\)
|
||||
|
||||
```bash
|
||||
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
|
||||
```
|
||||
|
||||
### programUnsubscribe
|
||||
|
||||
Unsubscribe from program-owned account change notifications
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `integer` - id of account Subscription to cancel
|
||||
|
||||
#### Results:
|
||||
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
### signatureSubscribe
|
||||
|
||||
Subscribe to a transaction signature to receive notification when the transaction is confirmed On `signatureNotification`, the subscription is automatically cancelled
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `string` - Transaction Signature, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - subscription id \(needed to unsubscribe\)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
|
||||
```
|
||||
|
||||
### signatureUnsubscribe
|
||||
|
||||
Unsubscribe from signature confirmation notification
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `integer` - subscription id to cancel
|
||||
|
||||
#### Results:
|
||||
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
@@ -1,63 +0,0 @@
|
||||
# Transaction
|
||||
|
||||
## Components of a `Transaction`
|
||||
|
||||
* **Transaction:**
|
||||
* **message:** Defines the transaction
|
||||
* **header:** Details the account types of and signatures required by
|
||||
|
||||
the transaction
|
||||
|
||||
* **num\_required\_signatures:** The total number of signatures
|
||||
|
||||
required to make the transaction valid.
|
||||
|
||||
* **num\_credit\_only\_signed\_accounts:** The last
|
||||
|
||||
`num_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](https://github.com/solana-labs/solana/tree/6b18db969dd1616eff07de35e7b823c75339fea8/book/src/instruction.md) that are
|
||||
|
||||
run sequentially and committed in one atomic transaction if all
|
||||
|
||||
succeed.
|
||||
* **signatures:** A list of signatures applied to the transaction. The
|
||||
|
||||
list is always of length `num_required_signatures`, and the signature
|
||||
|
||||
at index `i` corresponds to the 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).
|
||||
|
@@ -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:
|
||||
|
@@ -9,29 +9,29 @@ naturally form as a result of leader rotation is described in
|
||||
described here is how a validator copes with those forks until blocks
|
||||
are finalized.
|
||||
|
||||
The blocktree allows a validator to record every shred it observes
|
||||
on the network, in any order, as long as the shred is signed by the expected
|
||||
The blocktree allows a validator to record every blob it observes
|
||||
on the network, in any order, as long as the blob is signed by the expected
|
||||
leader for a given slot.
|
||||
|
||||
Shreds are moved to a fork-able key space the tuple of `leader slot` + `shred
|
||||
Blobs are moved to a fork-able key space the tuple of `leader slot` + `blob
|
||||
index` (within the slot). This permits the skip-list structure of the Solana
|
||||
protocol to be stored in its entirety, without a-priori choosing which fork to
|
||||
follow, which Entries to persist or when to persist them.
|
||||
|
||||
Repair requests for recent shreds are served out of RAM or recent files and out
|
||||
of deeper storage for less recent shreds, as implemented by the store backing
|
||||
Repair requests for recent blobs are served out of RAM or recent files and out
|
||||
of deeper storage for less recent blobs, as implemented by the store backing
|
||||
Blocktree.
|
||||
|
||||
### Functionalities of Blocktree
|
||||
|
||||
1. Persistence: the Blocktree lives in the front of the nodes verification
|
||||
pipeline, right behind network receive and signature verification. If the
|
||||
shred received is consistent with the leader schedule (i.e. was signed by the
|
||||
blob received is consistent with the leader schedule (i.e. was signed by the
|
||||
leader for the indicated slot), it is immediately stored.
|
||||
2. Repair: repair is the same as window repair above, but able to serve any
|
||||
shred that's been received. Blocktree stores shreds with signatures,
|
||||
blob that's been received. Blocktree stores blobs with signatures,
|
||||
preserving the chain of origination.
|
||||
3. Forks: Blocktree supports random access of shreds, so can support a
|
||||
3. Forks: Blocktree supports random access of blobs, so can support a
|
||||
validator's need to rollback and replay from a Bank checkpoint.
|
||||
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
|
||||
ordered enumeration of entries from slot 0. The logic of the replay stage
|
||||
@@ -41,22 +41,22 @@ the Blocktree.
|
||||
### Blocktree Design
|
||||
|
||||
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated
|
||||
slot index and shred index for an entry, and the value is the entry data. Note shred indexes are zero-based for each slot (i.e. they're slot-relative).
|
||||
slot index and blob index for an entry, and the value is the entry data. Note blob indexes are zero-based for each slot (i.e. they're slot-relative).
|
||||
|
||||
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
|
||||
* `slot_index` - The index of this slot
|
||||
* `num_blocks` - The number of blocks in the slot (used for chaining to a previous slot)
|
||||
* `consumed` - The highest shred index `n`, such that for all `m < n`, there exists a shred in this slot with shred index equal to `n` (i.e. the highest consecutive shred index).
|
||||
* `received` - The highest received shred index for the slot
|
||||
* `consumed` - The highest blob index `n`, such that for all `m < n`, there exists a blob in this slot with blob index equal to `n` (i.e. the highest consecutive blob index).
|
||||
* `received` - The highest received blob index for the slot
|
||||
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
|
||||
the ledger to find possible fork points.
|
||||
* `last_index` - The index of the shred that is flagged as the last shred for this slot. This flag on a shred will be set by the leader for a slot when they are transmitting the last shred for a slot.
|
||||
* `last_index` - The index of the blob that is flagged as the last blob for this slot. This flag on a blob will be set by the leader for a slot when they are transmitting the last blob for a slot.
|
||||
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is_rooted for each slot with the following rules. Let slot(n) be the slot with index `n`, and slot(n).is_full() is true if the slot with index `n` has all the ticks expected for that slot. Let is_rooted(n) be the statement that "the slot(n).is_rooted is true". Then:
|
||||
|
||||
is_rooted(0)
|
||||
is_rooted(n+1) iff (is_rooted(n) and slot(n).is_full()
|
||||
|
||||
3. Chaining - When a shred for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the shred). We then know that this new slot chains to slot `x - num_blocks`.
|
||||
3. Chaining - When a blob for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the blob). We then know that this new slot chains to slot `x - num_blocks`.
|
||||
|
||||
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.
|
||||
|
||||
|
865
book/src/cli.md
@@ -1,865 +0,0 @@
|
||||
## solana CLI
|
||||
|
||||
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
|
||||
|
||||
### Examples
|
||||
|
||||
#### Get Pubkey
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana address
|
||||
|
||||
// Return
|
||||
<PUBKEY>
|
||||
```
|
||||
|
||||
#### Airdrop SOL/Lamports
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana airdrop 2
|
||||
|
||||
// Return
|
||||
"2.00000000 SOL"
|
||||
|
||||
// Command
|
||||
$ solana airdrop 123 --lamports
|
||||
|
||||
// Return
|
||||
"123 lamports"
|
||||
```
|
||||
|
||||
#### Get Balance
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana balance
|
||||
|
||||
// Return
|
||||
"3.00050001 SOL"
|
||||
```
|
||||
|
||||
#### Confirm Transaction
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana confirm <TX_SIGNATURE>
|
||||
|
||||
// Return
|
||||
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
|
||||
```
|
||||
|
||||
#### Deploy program
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana deploy <PATH>
|
||||
|
||||
// Return
|
||||
<PROGRAM_ID>
|
||||
```
|
||||
|
||||
#### Unconditional Immediate Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Post-Dated Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
*`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key*
|
||||
|
||||
#### Authorized Transfer
|
||||
|
||||
A third party must send a signature to unlock the lamports.
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Post-Dated and Authorized Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Multiple Witnesses
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Cancelable Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--cancelable
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Cancel Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana cancel <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Send Signature
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-signature <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Indicate Elapsed Time
|
||||
|
||||
Use the current system time:
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
Or specify some other arbitrary timestamp:
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```manpage
|
||||
solana 0.12.0
|
||||
|
||||
USAGE:
|
||||
solana [FLAGS] [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
SUBCOMMANDS:
|
||||
address Get your public key
|
||||
airdrop Request lamports
|
||||
authorize-voter Authorize a new vote signing keypair for the given vote account
|
||||
balance Get your balance
|
||||
cancel Cancel a transfer
|
||||
claim-storage-reward Redeem storage reward credits
|
||||
cluster-version Get the version of the cluster entrypoint
|
||||
confirm Confirm transaction by signature
|
||||
create-replicator-storage-account Create a replicator storage account
|
||||
create-storage-mining-pool-account Create mining pool account
|
||||
create-validator-storage-account Create a validator storage account
|
||||
create-vote-account Create a vote account
|
||||
deactivate-stake Deactivate the delegated stake from the stake account
|
||||
delegate-stake Delegate stake to a vote account
|
||||
deploy Deploy a program
|
||||
fees Display current cluster fees
|
||||
get Get wallet config settings
|
||||
get-slot Get current slot
|
||||
get-transaction-count Get current transaction count
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
pay Send a payment
|
||||
ping Submit transactions sequentially
|
||||
redeem-vote-credits Redeem credits in the stake account
|
||||
send-signature Send a signature to authorize a transfer
|
||||
send-timestamp Send a timestamp to unlock a transfer
|
||||
set Set a wallet config setting
|
||||
show-account Show the contents of an account
|
||||
show-stake-account Show the contents of a stake account
|
||||
show-storage-account Show the contents of a storage account
|
||||
show-vote-account Show the contents of a vote account
|
||||
validator-info Publish/get Validator info on Solana
|
||||
withdraw-stake Withdraw the unstaked lamports from the stake account
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-address
|
||||
Get your public key
|
||||
|
||||
USAGE:
|
||||
solana address [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-airdrop
|
||||
Request a batch of lamports
|
||||
|
||||
USAGE:
|
||||
solana airdrop [OPTIONS] <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
--drone-host <HOST> Drone host to use [default: the --url host]
|
||||
--drone-port <PORT> Drone port to use [default: 9900]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<AMOUNT> The airdrop amount to request (default unit SOL)
|
||||
<unit> Specify unit to use for request and balance display [possible values: SOL, lamports]
|
||||
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-authorize-voter
|
||||
Authorize a new vote signing keypair for the given vote account
|
||||
|
||||
USAGE:
|
||||
solana authorize-voter [OPTIONS] <VOTE ACCOUNT PUBKEY> <CURRENT VOTER KEYPAIR FILE> <NEW VOTER PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
|
||||
<CURRENT VOTER KEYPAIR FILE> Keypair file for the currently authorized vote signer
|
||||
<NEW VOTER PUBKEY> New vote signer to authorize
|
||||
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-balance
|
||||
Get your balance
|
||||
|
||||
USAGE:
|
||||
solana balance [FLAGS] [OPTIONS] [PUBKEY]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--lamports Display balance in lamports instead of SOL
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of the balance to check
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-cancel
|
||||
Cancel a transfer
|
||||
|
||||
USAGE:
|
||||
solana cancel [OPTIONS] <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PROCESS ID> The process id of the transfer to cancel
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-claim-storage-reward
|
||||
Redeem storage reward credits
|
||||
|
||||
USAGE:
|
||||
solana claim-storage-reward [OPTIONS] <NODE PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<NODE PUBKEY> The node account to credit the rewards to
|
||||
<STORAGE ACCOUNT PUBKEY> Storage account address to redeem credits for
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-cluster-version
|
||||
Get the version of the cluster entrypoint
|
||||
|
||||
USAGE:
|
||||
solana cluster-version [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-confirm
|
||||
Confirm transaction by signature
|
||||
|
||||
USAGE:
|
||||
solana confirm [OPTIONS] <SIGNATURE>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<SIGNATURE> The transaction signature to confirm
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-replicator-storage-account
|
||||
Create a replicator storage account
|
||||
|
||||
USAGE:
|
||||
solana create-replicator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT OWNER PUBKEY>
|
||||
<STORAGE ACCOUNT PUBKEY>
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-storage-mining-pool-account
|
||||
Create mining pool account
|
||||
|
||||
USAGE:
|
||||
solana create-storage-mining-pool-account [OPTIONS] <STORAGE ACCOUNT PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT PUBKEY> Storage mining pool account address to fund
|
||||
<AMOUNT> The amount to assign to the storage mining pool account (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-validator-storage-account
|
||||
Create a validator storage account
|
||||
|
||||
USAGE:
|
||||
solana create-validator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT OWNER PUBKEY>
|
||||
<STORAGE ACCOUNT PUBKEY>
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-vote-account
|
||||
Create a vote account
|
||||
|
||||
USAGE:
|
||||
solana create-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY> <VALIDATOR PUBKEY> <LAMPORTS>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account address to fund
|
||||
<VALIDATOR PUBKEY> Validator that will vote with this account
|
||||
<LAMPORTS> The amount of lamports to send to the vote account
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-deactivate-stake
|
||||
Deactivate the delegated stake from the stake account
|
||||
|
||||
USAGE:
|
||||
solana deactivate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the delegate transaction.
|
||||
<PUBKEY> The vote account to which the stake is currently delegated
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-delegate-stake
|
||||
Delegate stake to a vote account
|
||||
|
||||
USAGE:
|
||||
solana delegate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <VOTE ACCOUNT PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the new stake account
|
||||
<VOTE ACCOUNT PUBKEY> The vote account to which the stake will be delegated
|
||||
<AMOUNT> The amount to delegate (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-deploy
|
||||
Deploy a program
|
||||
|
||||
USAGE:
|
||||
solana deploy [OPTIONS] <PATH TO PROGRAM>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PATH TO PROGRAM> /path/to/program.o
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-fees
|
||||
Display current cluster fees
|
||||
|
||||
USAGE:
|
||||
solana fees [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get
|
||||
Get wallet config settings
|
||||
|
||||
USAGE:
|
||||
solana get [OPTIONS] [CONFIG_FIELD]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<CONFIG_FIELD> Return a specific config setting [possible values: url, keypair]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get-slot
|
||||
Get current slot
|
||||
|
||||
USAGE:
|
||||
solana get-slot [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get-transaction-count
|
||||
Get current transaction count
|
||||
|
||||
USAGE:
|
||||
solana get-transaction-count [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-pay
|
||||
Send a payment
|
||||
|
||||
USAGE:
|
||||
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [unit]
|
||||
|
||||
FLAGS:
|
||||
--cancelable
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default:
|
||||
/Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
--after <DATETIME> A timestamp after which transaction will execute
|
||||
--require-timestamp-from <PUBKEY> Require timestamp from this third party
|
||||
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<AMOUNT> The amount to send (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-ping
|
||||
Submit transactions sequentially
|
||||
|
||||
USAGE:
|
||||
solana ping [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default:
|
||||
~/.config/solana/wallet/config.yml]
|
||||
-c, --count <NUMBER> Stop after submitting count transactions
|
||||
-i, --interval <SECONDS> Wait interval seconds between submitting the next transaction [default: 2]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
-t, --timeout <SECONDS> Wait up to timeout seconds for transaction confirmation [default: 10]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-redeem-vote-credits
|
||||
Redeem credits in the stake account
|
||||
|
||||
USAGE:
|
||||
solana redeem-vote-credits [OPTIONS] <STAKING ACCOUNT PUBKEY> <VOTE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKING ACCOUNT PUBKEY> Staking account address to redeem credits for
|
||||
<VOTE ACCOUNT PUBKEY> The vote account to which the stake was previously delegated.
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-send-signature
|
||||
Send a signature to authorize a transfer
|
||||
|
||||
USAGE:
|
||||
solana send-signature [OPTIONS] <PUBKEY> <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<PROCESS ID> The process id of the transfer to authorize
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-send-timestamp
|
||||
Send a timestamp to unlock a transfer
|
||||
|
||||
USAGE:
|
||||
solana send-timestamp [OPTIONS] <PUBKEY> <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
--date <DATETIME> Optional arbitrary timestamp to apply
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<PROCESS ID> The process id of the transfer to unlock
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-set
|
||||
Set a wallet config setting
|
||||
|
||||
USAGE:
|
||||
solana set [OPTIONS] <--url <URL>|--keypair <PATH>>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-account
|
||||
Show the contents of an account
|
||||
|
||||
USAGE:
|
||||
solana show-account [FLAGS] [OPTIONS] <ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--lamports Display balance in lamports instead of SOL
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
-o, --output <FILE> Write the account data to this file
|
||||
|
||||
ARGS:
|
||||
<ACCOUNT PUBKEY> Account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-stake-account
|
||||
Show the contents of a stake account
|
||||
|
||||
USAGE:
|
||||
solana show-stake-account [OPTIONS] <STAKE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT PUBKEY> Stake account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-storage-account
|
||||
Show the contents of a storage account
|
||||
|
||||
USAGE:
|
||||
solana show-storage-account [OPTIONS] <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT PUBKEY> Storage account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-vote-account
|
||||
Show the contents of a vote account
|
||||
|
||||
USAGE:
|
||||
solana show-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-validator-info
|
||||
Publish/get Validator info on Solana
|
||||
|
||||
USAGE:
|
||||
solana validator-info [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
SUBCOMMANDS:
|
||||
get Get and parse Solana Validator info
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
publish Publish Validator info on Solana
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-withdraw-stake
|
||||
Withdraw the unstaked lamports from the stake account
|
||||
|
||||
USAGE:
|
||||
solana withdraw-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <DESTINATION PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the withdraw transaction.
|
||||
<DESTINATION PUBKEY> The account where the lamports should be transfered
|
||||
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@ buggy and malicious nodes.
|
||||
|
||||
Before starting any fullnodes, one first needs to create a *genesis block*.
|
||||
The block contains entries referencing two public keys, a *mint* and a
|
||||
*bootstrap leader*. The fullnode holding the bootstrap leader's private key is
|
||||
*bootstrap leader*. The fullnode holding the bootstrap leader's secret key is
|
||||
responsible for appending the first entries to the ledger. It initializes its
|
||||
internal state with the mint's account. That account will hold the number of
|
||||
native tokens defined by the genesis block. The second fullnode then contacts
|
||||
@@ -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,41 +0,0 @@
|
||||
# A Solana Cluster
|
||||
|
||||
A Solana cluster is a set of fullnodes working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this chapter, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
|
||||
|
||||
## Creating a Cluster
|
||||
|
||||
Before starting any fullnodes, one first needs to create a _genesis block_. The block contains entries referencing two public keys, a _mint_ and a _bootstrap leader_. The fullnode holding the bootstrap leader's secret key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis block. The second fullnode then contacts the bootstrap leader to register as a _validator_ or _replicator_. Additional fullnodes then register with any registered member of the cluster.
|
||||
|
||||
A validator receives all entries from the leader and submits votes confirming those entries are valid. After voting, the validator is expected to store those entries until replicator nodes submit proofs that they have stored copies of it. Once the validator observes a sufficient number of copies exist, it deletes its copy.
|
||||
|
||||
## Joining a Cluster
|
||||
|
||||
Validators and 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 for all nodes to synchronize is proportional to the square of the number of nodes participating in the cluster. Algorithmically, that's considered very slow, but in exchange for that time, a node is assured that it eventually has all the same information as every other node, and that that information cannot be censored by any one node.
|
||||
|
||||
## Sending Transactions to a Cluster
|
||||
|
||||
Clients send transactions to any fullnode's Transaction Processing Unit \(TPU\) port. If the node is in the validator role, it forwards the transaction to the designated leader. If in the leader role, the node bundles incoming transactions, timestamps them creating an _entry_, and pushes them onto the cluster's _data plane_. Once on the data plane, the transactions are validated by validator nodes and replicated by replicator nodes, effectively appending them to the ledger.
|
||||
|
||||
## Confirming Transactions
|
||||
|
||||
A Solana cluster is capable of subsecond _confirmation_ for up to 150 nodes with plans to scale up to hundreds of thousands of nodes. Once fully implemented, confirmation times are expected to increase only with the logarithm of the number of validators, where the logarithm's base is very high. If the base is one thousand, for example, it means that for the first thousand nodes, confirmation will be the duration of three network hops plus the time it takes the slowest validator of a supermajority to vote. For the next million nodes, confirmation increases by only one network hop.
|
||||
|
||||
Solana defines confirmation as the duration of time from when the leader timestamps a new entry to the moment when it recognizes a supermajority of ledger votes.
|
||||
|
||||
A gossip network is much too slow to achieve subsecond confirmation once the network grows beyond a certain size. The time it takes to send messages to all nodes is proportional to the square of the number of nodes. If a blockchain wants to achieve low confirmation and attempts to do it using a gossip network, it will be forced to centralize to just a handful of nodes.
|
||||
|
||||
Scalable confirmation can be achieved using the follow combination of techniques:
|
||||
|
||||
1. Timestamp transactions with a VDF sample and sign the timestamp.
|
||||
2. Split the transactions into batches, send each to separate nodes and have
|
||||
|
||||
each node share its batch with its peers.
|
||||
|
||||
3. Repeat the previous step recursively until all nodes have all batches.
|
||||
|
||||
Solana rotates leaders at fixed intervals, called _slots_. Each leader may only produce entries during its allotted slot. The leader therefore timestamps transactions so that validators may lookup the public key of the designated leader. The leader then signs the timestamp so that a validator may verify the signature, proving the signer is owner of the designated leader's public key.
|
||||
|
||||
Next, transactions are broken into batches so that a node can send transactions to multiple parties without making multiple copies. If, for example, the leader needed to send 60 transactions to 6 nodes, it would break that collection of 60 into batches of 10 transactions and send one to each node. This allows the leader to put 60 transactions on the wire, not 60 transactions for each node. Each node then shares its batch with its peers. Once the node has collected all 6 batches, it reconstructs the original set of 60 transactions.
|
||||
|
||||
A batch of transactions can only be split so many times before it is so small that header information becomes the primary consumer of network bandwidth. At the time of this writing, the approach is scaling well up to about 150 validators. To scale up to hundreds of thousands of validators, each node can apply the same technique as the leader node to another set of nodes of equal size. We call the technique _data plane fanout_; learn more in the [data plan fanout](https://github.com/solana-labs/solana/tree/6b18db969dd1616eff07de35e7b823c75339fea8/book/src/data-plane-fanout.md) section.
|
||||
|
@@ -1,81 +0,0 @@
|
||||
# Fork Generation
|
||||
|
||||
The chapter describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
|
||||
|
||||
## Overview
|
||||
|
||||
Nodes take turns being leader and generating the PoH that encodes state changes. The cluster can tolerate loss of connection to any leader by synthesizing what the leader _**would**_ have generated had it been connected but not ingesting any state changes. The possible number of forks is thereby limited to a "there/not-there" skip list of forks that may arise on leader rotation slot boundaries. At any given slot, only a single leader's transactions will be accepted.
|
||||
|
||||
## Message Flow
|
||||
|
||||
1. Transactions are ingested by the current leader.
|
||||
2. Leader filters valid transactions.
|
||||
3. Leader executes valid transactions updating its state.
|
||||
4. Leader packages transactions into entries based off its current PoH slot.
|
||||
5. Leader transmits the entries to validator nodes \(in signed blobs\)
|
||||
1. The PoH stream includes ticks; empty entries that indicate liveness of
|
||||
|
||||
the leader and the passage of time on the cluster.
|
||||
|
||||
2. A leader's stream begins with the tick entries necessary complete the PoH
|
||||
|
||||
back to the leaders most recently observed prior leader slot.
|
||||
6. Validators retransmit entries to peers in their set and to further
|
||||
|
||||
downstream nodes.
|
||||
|
||||
7. Validators validate the transactions and execute them on their state.
|
||||
8. Validators compute the hash of the state.
|
||||
9. At specific times, i.e. specific PoH tick counts, validators transmit votes
|
||||
|
||||
to the leader.
|
||||
|
||||
1. Votes are signatures of the hash of the computed state at that PoH tick
|
||||
|
||||
count
|
||||
|
||||
2. Votes are also propagated via gossip
|
||||
|
||||
10. Leader executes the votes as any other transaction and broadcasts them to
|
||||
|
||||
the cluster.
|
||||
|
||||
11. Validators observe their votes and all the votes from the cluster.
|
||||
|
||||
## Partitions, Forks
|
||||
|
||||
Forks can arise at PoH tick counts that correspond to a vote. The next leader may not have observed the last vote slot and may start their slot with generated virtual PoH entries. These empty ticks are generated by all nodes in the cluster at a cluster-configured rate for hashes/per/tick `Z`.
|
||||
|
||||
There are only two possible versions of the PoH during a voting slot: PoH with `T` ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the cluster can derive from the last tick in the previous slot.
|
||||
|
||||
Validators can ignore forks at other points \(e.g. from the wrong leader\), or slash the leader responsible for the fork.
|
||||
|
||||
Validators vote based on a greedy choice to maximize their reward described in [Tower BFT](../implemented-proposals/tower-bft.md).
|
||||
|
||||
### Validator's View
|
||||
|
||||
#### Time Progression
|
||||
|
||||
The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and `E`s represent entries from that leader during that leader's slot. The `x`s represent ticks only, and time flows downwards in the diagram.
|
||||
|
||||

|
||||
|
||||
Note that an `E` appearing on 2 forks at the same slot is a slashable condition, so a validator observing `E3` and `E3'` can slash L3 and safely choose `x` for that slot. Once a validator commits to a forks, other forks can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain to be proposed by a leader. But multiple virtual entries may overlap as they link back to the a previous slot.
|
||||
|
||||
#### Time Division
|
||||
|
||||
It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the cluster. The following table presents the above tree of forks as a time-divided ledger.
|
||||
|
||||
| leader slot | L1 | L2 | L3 | L4 | L5 |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| data | E1 | E2 | E3 | E4 | E5 |
|
||||
| ticks since prev | | | | x | xx |
|
||||
|
||||
Note that only data from leader L3 will be accepted during leader slot L3. Data from L3 may include "catchup" ticks back to a slot other than L2 if L3 did not observe L2's data. L4 and L5's transmissions include the "ticks to prev" PoH entries.
|
||||
|
||||
This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints.
|
||||
|
||||
### Leader's View
|
||||
|
||||
When a new leader begins a slot, it must first transmit any PoH \(ticks\) required to link the new slot with the most recently observed and voted slot. The fork the leader proposes would link the current slot to a previous fork that the leader has voted on with virtual ticks.
|
||||
|
@@ -1,98 +0,0 @@
|
||||
# Leader Rotation
|
||||
|
||||
At any given moment, a cluster expects only one fullnode to produce ledger entries. By having only one leader at a time, all validators are able to replay identical copies of the ledger. The drawback of only one leader at a time, however, is that a malicious leader is capable of censoring votes and transactions. Since censoring cannot be distinguished from the network dropping packets, the cluster cannot simply elect a single node to hold the leader role indefinitely. Instead, the cluster minimizes the influence of a malicious leader by rotating which node takes the lead.
|
||||
|
||||
Each validator selects the expected leader using the same algorithm, described below. When the validator receives a new signed ledger entry, it can be certain that entry was produced by the expected leader. The order of slots which each leader is assigned a slot is called a _leader schedule_.
|
||||
|
||||
## Leader Schedule Rotation
|
||||
|
||||
A validator rejects blocks that are not signed by the _slot leader_. The list of identities of all slot leaders is called a _leader schedule_. The leader schedule is recomputed locally and periodically. It assigns slot leaders for a duration of time called an _epoch_. The schedule must be computed far in advance of the slots it assigns, such that the ledger state it uses to compute the schedule is finalized. That duration is called the _leader schedule offset_. Solana sets the offset to the duration of slots until the next epoch. That is, the leader schedule for an epoch is calculated from the ledger state at the start of the previous epoch. The offset of one epoch is fairly arbitrary and assumed to be sufficiently long such that all validators will have finalized their ledger state before the next schedule is generated. A cluster may choose to shorten the offset to reduce the time between stake changes and leader schedule updates.
|
||||
|
||||
While operating without partitions lasting longer than an epoch, the schedule only needs to be generated when the root fork crosses the epoch boundary. Since the schedule is for the next epoch, any new stakes committed to the root fork will not be active until the next epoch. The block used for generating the leader schedule is the first block to cross the epoch boundary.
|
||||
|
||||
Without a partition lasting longer than an epoch, the cluster will work as follows:
|
||||
|
||||
1. A validator continuously updates its own root fork as it votes.
|
||||
2. The validator updates its leader schedule each time the slot height crosses an epoch boundary.
|
||||
|
||||
For example:
|
||||
|
||||
The epoch duration is 100 slots. The root fork is updated from fork computed at slot height 99 to a fork computed at slot height 102. Forks with slots at height 100,101 were skipped because of failures. The new leader schedule is computed using fork at slot height 102. It is active from slot 200 until it is updated again.
|
||||
|
||||
No inconsistency can exist because every validator that is voting with the cluster has skipped 100 and 101 when its root passes 102. All validators, regardless of voting pattern, would be committing to a root that is either 102, or a descendant of 102.
|
||||
|
||||
### Leader Schedule Rotation with Epoch Sized Partitions.
|
||||
|
||||
The duration of the leader schedule offset has a direct relationship to the likelihood of a cluster having an inconsistent view of the correct leader schedule.
|
||||
|
||||
Consider the following scenario:
|
||||
|
||||
Two partitions that are generating half of the blocks each. Neither is coming to a definitive supermajority fork. Both will cross epoch 100 and 200 without actually committing to a root and therefore a cluster wide commitment to a new leader schedule.
|
||||
|
||||
In this unstable scenario, multiple valid leader schedules exist.
|
||||
|
||||
* A leader schedule is generated for every fork whose direct parent is in the previous epoch.
|
||||
* The leader schedule is valid after the start of the next epoch for descendant forks until it is updated.
|
||||
|
||||
Each partition's schedule will diverge after the partition lasts more than an epoch. For this reason, the epoch duration should be selected to be much much larger then slot time and the expected length for a fork to be committed to root.
|
||||
|
||||
After observing the cluster for a sufficient amount of time, the leader schedule offset can be selected based on the median partition duration and its standard deviation. For example, an offset longer then the median partition duration plus six standard deviations would reduce the likelihood of an inconsistent ledger schedule in the cluster to 1 in 1 million.
|
||||
|
||||
## Leader Schedule Generation at Genesis
|
||||
|
||||
The genesis block declares the first leader for the first epoch. This leader ends up scheduled for the first two epochs because the leader schedule is also generated at slot 0 for the next epoch. The length of the first two epochs can be specified in the genesis 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](../implemented-proposals/tower-bft.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
Leader schedule is generated using a predefined seed. The process is as follows:
|
||||
|
||||
1. Periodically use the PoH tick height \(a monotonically increasing counter\) to
|
||||
|
||||
seed a stable pseudo-random algorithm.
|
||||
|
||||
2. At that height, sample the bank for all the staked accounts with leader
|
||||
|
||||
identities that have voted within a cluster-configured number of ticks. The
|
||||
|
||||
sample is called the _active set_.
|
||||
|
||||
3. Sort the active set by stake weight.
|
||||
4. Use the random seed to select nodes weighted by stake to create a
|
||||
|
||||
stake-weighted ordering.
|
||||
|
||||
5. This ordering becomes valid after a cluster-configured number of ticks.
|
||||
|
||||
## Schedule Attack Vectors
|
||||
|
||||
### Seed
|
||||
|
||||
The seed that is selected is predictable but unbiasable. There is no grinding attack to influence its outcome.
|
||||
|
||||
### Active Set
|
||||
|
||||
A leader can bias the active set by censoring validator votes. Two possible ways exist for leaders to censor the active set:
|
||||
|
||||
* Ignore votes from validators
|
||||
* Refuse to vote for blocks with votes from validators
|
||||
|
||||
To reduce the likelihood of censorship, the active set is calculated at the leader schedule offset boundary over an _active set sampling duration_. The active set sampling duration is long enough such that votes will have been collected by multiple leaders.
|
||||
|
||||
### Staking
|
||||
|
||||
Leaders can censor new staking transactions or refuse to validate blocks with new stakes. This attack is similar to censorship of validator votes.
|
||||
|
||||
### Validator operational key loss
|
||||
|
||||
Leaders and validators are expected to use ephemeral keys for operation, and stake owners authorize the validators to do work with their stake via delegation.
|
||||
|
||||
The cluster should be able to recover from the loss of all the ephemeral keys used by leaders and validators, which could occur through a common software vulnerability shared by all the nodes. Stake owners should be able to vote directly co-sign a validator vote even though the stake is currently delegated to a validator.
|
||||
|
||||
## Appending Entries
|
||||
|
||||
The lifetime of a leader schedule is called an _epoch_. The epoch is split into _slots_, where each slot has a duration of `T` PoH ticks.
|
||||
|
||||
A leader transmits entries during its slot. After `T` ticks, all the validators switch to the next scheduled leader. Validators must ignore entries sent outside a leader's assigned slot.
|
||||
|
||||
All `T` ticks must be observed by the next leader for it to build its own entries on. If entries are not observed \(leader is down\) or entries are invalid \(leader is buggy or malicious\), the next leader must produce ticks to fill the previous leader's slot. Note that the next leader should do repair requests in parallel, and postpone sending ticks until it is confident other validators also failed to observe the previous leader's entries. If a leader incorrectly builds on its own ticks, the leader following it must replace all its ticks.
|
||||
|