Compare commits

..

36 Commits

Author SHA1 Message Date
40300c2042 Add validator registration link (#4229) 2019-05-10 15:13:31 -07:00
bd1e989b11 The fullnode identity keypair can now be provided via --identity (#4228)
automerge
2019-05-09 07:51:45 -07:00
5dc14658e6 Disable solana-upload-perf until performance can be debugged (#4210) 2019-05-08 17:42:29 -07:00
0e370c38fe Lock blockexplorer version 2019-05-08 17:05:19 -07:00
19bcb350b2 Update testnet-participation.md 2019-05-04 08:36:03 -07:00
9d236ddfa4 Update book to point at 0.14.1 release for testnet participation (#4152) 2019-05-03 17:01:04 -06:00
2f8c424d1e Advance cargo.toml version to 0.14.2 (#4150) 2019-05-03 15:38:06 -06:00
b708ede0a7 Display release date in the local timezone 2019-05-03 13:33:56 -07:00
c55e39166f Add a node-specific ip echo service to remove dependency on ifconfig.co (#4137) (#4140) 2019-05-03 12:00:33 -07:00
6e6fe5ba4e fix accounts_db storage.reset() (#4094) (#4119)
* fix accounts_db storage.reset()

* fix compilation errors, remove unused, fix test_accounts_grow() failure
2019-05-02 11:17:55 -07:00
7fec1f38be earlyoom: Stop using unsupported -k option 2019-05-01 10:52:02 -07:00
5e9638aae0 Stop nodes in parallel 2019-04-30 10:44:00 -07:00
378dbeaeb3 Use more -w 2019-04-30 09:57:03 -07:00
04d0fd8626 Add flag to skip slow extras when deploying a large testnet 2019-04-30 09:21:08 -07:00
18a41ce836 Flip if/else 2019-04-30 08:58:39 -07:00
6971b7914a v0.14: various net/ fixes for large clusters (#4080)
* net.sh: Add -F to discard validator nodes that didn't bootup successfully

* Relax sanity node count when validator bootup failure is permitted

* Less sanity for testnet-demo

* net.sh: Add -F to discard validator nodes that didn't bootup successfully
2019-04-29 21:38:03 -07:00
a02fe1a831 Cherry-pick account set root fixes (#4076)
automerge
2019-04-29 19:39:24 -07:00
120664649a Update release doc to include testnet update instuctions (#4066) (#4077)
* Update release doc to include testnet update instuctions

* Fixup headers and pick nits

* Remove outdated testnet behavior
2019-04-29 20:33:16 -06:00
044fa48fe1 Increment cargo.toml version to 0.14.1 (#4074) 2019-04-29 17:27:53 -06:00
b5342f7485 Cleanup metrics dashboard (#4072) (#4073)
automerge
2019-04-29 16:24:58 -07:00
ff9bd2f512 Fix the output from Gossip Discovery (#4070) 2019-04-29 14:59:01 -07:00
25810ce729 Remove Bench Exchange Contract Execution graph 2019-04-29 14:28:09 -07:00
82c7f0e366 testnet-demo: use more low quota nodes 2019-04-29 12:08:39 -07:00
012d05f10b Increase testnet-demo node count a little 2019-04-29 09:10:18 -07:00
f853595efb testnet-demo now runs across more GCE zones (#4053)
* testnet-demo now runs across more GCE zones

* Save zone info to config file

* Add geoip whitelist for common data centers

* Skip more of start

* Include -x for config

* Fetch private key from first validator node if necessary

* Correct -r propagation
2019-04-28 19:50:02 -07:00
09e4f7e49c Correctly terminate instances across multiple zones 2019-04-28 09:09:34 -07:00
cb37072ed7 Switch testnet-demo to influxcloud 2019-04-27 22:12:30 -07:00
0b109d3340 Correct us-central1-b zone name 2019-04-27 21:43:31 -07:00
dcdc5b8cf7 testnet-demo: skip over validator nodes that fail to boot 2019-04-27 21:34:02 -07:00
1a7c30bb86 Use GPU nodes for blockstreamer as well if rest of testnet has GPUs (#4046) (#4048)
automerge
2019-04-27 21:31:01 -07:00
3ebc14f965 Blockstreamer annotation fix for non buildkite deployments (#4045) (#4047)
automerge
2019-04-27 21:01:26 -07:00
cf589efbbf Performance metrics computation methodology (#4041) (#4044)
automerge
2019-04-27 16:59:45 -07:00
94d5c64281 testnet-demo: add more GCE zones, remove client 2019-04-27 16:53:05 -07:00
566de1fd0e Add DNS resolution for network/drone arguments (#4037)
automerge
2019-04-27 10:00:41 -07:00
cb0f367084 Avoid inaccurate PATH nagging 2019-04-27 15:32:23 +00:00
e08e1fe6ac Add " 2019-04-27 07:41:55 -07:00
523 changed files with 14018 additions and 32232 deletions

View File

@ -1,41 +0,0 @@
os: Visual Studio 2017
version: '{build}'
branches:
only:
- master
- /^v[0-9.]+/
cache:
- '%USERPROFILE%\.cargo'
- '%APPVEYOR_BUILD_FOLDER%\target'
build_script:
- bash ci/publish-tarball.sh
notifications:
- provider: Slack
incoming_webhook:
secure: 6HTXVh+FBz29LGJb+taFOo9dqoADfo9xyAszeyXZF5Ub9t5NERytKAR35B2wb+uIOOCBF8+JhmH4437Cgf/ti4IqvURzW1QReXK7eQhn1EI=
channel: ci-status
on_build_success: false
on_build_failure: true
on_build_status_changed: true
deploy:
- provider: S3
access_key_id:
secure: ptvqM/yvgeTeA12XOzybH1KYNh95AdfEvqoH9mvP2ic=
secret_access_key:
secure: IkrgBlz5hdxvwcJdMXyyHUrpWhKa6fXLOD/8rm/rjKqYCdrba9B8V1nLZVrzXGGy
bucket: release.solana.com
region: us-west-1
set_public: true
- provider: GitHub
auth_token:
secure: vQ3jMl5LQrit6+TQONA3ZgQjZ/Ej62BN2ReVb2NSOwjITHMu1131hjc3dOrMEZL6
draft: false
prerelease: false
on:
appveyor_repo_tag: true

View File

@ -1 +0,0 @@
/secrets_unencrypted.ejson

View File

@ -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:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:jA0WqO70coUtF0iokRdgtCR/lF/lETAI:d/Wl8Tdl6xVh/B39cTf1DaQkomR7I/2vMhvxd1msJ++BjI2l3p2dFoGsXqWT+/os8VgiPg==]",
"CRATES_IO_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:2FaZ6k4RGH8luyNRaN6yeZUQDNAu2KwC:XeYe0tCAivYE0F9HEWM79mAI6kNbfYaqP7k7yY+SBDvs0341U9BdGZp7SErbHleS]",
"GITHUB_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:9kh4DGPiGDcUU7ejSFWg3gTW8nrOM09Q:b+GE07Wu6/bEnkDZcUtf48vTKAFphrCSt3tNNER9h6A+wZ80k499edw4pbDdl9kEvxB30fFwrLQ=]",
"INFLUX_DATABASE": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:rCHsYi0rc7dmvr1V3wEgNoaNIyr+9ClM:omjVcOqM7vwt44kJ+As4BjJL]",
"INFLUX_PASSWORD": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:bP5Gw1Vy66viKFKO41o2Gho998XajH/5:khkCYz2LFvkJkk7R4xY1Hfz1yU3/NENjauiUkPhXA+dmg1qOIToxEagCgIkRwyeCiYaoCR6CZyw=]",
"INFLUX_USERNAME": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:ZamCvza2W9/bZRGSkqDu55xNN04XKKhp:5jlmCOdFbpL7EFez41zCbLfk3ZZlfmhI]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:Oi2nsRxnvWnnBYsB6KwEDzLPcYgpYojU:ELbvjXkXKlgFCMES45R+fxG7Ex43WHWErjMbxZoqasxyr7GSH66hQzUWqiQSJyT4ukYrRhRC9YrsKKGkjACLU57X4EGIy9TuLgTnyBYhPnxLYStC3y/7o/MB5FCTt5wHJw3/A9p+me5+T4UmyZ7OeP21NhDUCGQcb0040VwYWS78klW2aQESJJ6wTI1xboE8/zC0vtnB/u50+LydbKEyb21r6y3OH9FYNEpSwIspWKcgpruJdQSCnDoKxP9YR1yzvk2rabss13LJNdV1Y6mQNIdP4OIFQhCs6dXT253RTl5qdZ0MruHwlp8wX4btOuYDcCoM5exr]"
}
}

View File

@ -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

View File

@ -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"
)
#

View File

@ -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

7
.gitignore vendored
View File

@ -1,12 +1,13 @@
/book/html/
/book/src/img/
/book/src/tests.ok
/core/target/
/farf/
/ledger-tool/target/
/solana-release/
/solana-release.tar.bz2
/solana-metrics/
/solana-metrics.tar.bz2
solana-release.tar.bz2
/target/
/wallet/target/
**/*.rs.bk
.cargo

View File

@ -1,44 +0,0 @@
os:
- osx
language: rust
cache: cargo
rust:
- 1.35.0
install:
- source ci/rust-version.sh
- test $rust_stable = $TRAVIS_RUST_VERSION # Update .travis.yml rust version above when this fails
script:
- source ci/env.sh
- ci/publish-tarball.sh
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
notifications:
slack:
on_success: change
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
bucket: release.solana.com
region: us-west-1
skip_cleanup: true
acl: public_read
local_dir: travis-s3-upload
on:
all_branches: true
- provider: releases
api_key: $GITHUB_TOKEN
skip_cleanup: true
file_glob: true
file: travis-release-upload/*
on:
tags: true

1576
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,11 @@
[workspace]
members = [
".",
"bench-exchange",
"bench-streamer",
"bench-tps",
"chacha-sys",
"client",
"core",
"drone",
"validator",
"fullnode",
"genesis",
"gossip",
"install",
@ -15,30 +13,26 @@ members = [
"kvstore",
"ledger-tool",
"logger",
"merkle-tree",
"metrics",
"netutil",
"programs/bpf",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
"programs/bpf_loader",
"programs/budget_api",
"programs/budget_program",
"programs/config_api",
"programs/config_program",
"programs/exchange_api",
"programs/exchange_program",
"programs/token_api",
"programs/token_program",
"programs/failure_program",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/storage_api",
"programs/storage_program",
"programs/token_api",
"programs/token_program",
"programs/vote_api",
"programs/vote_program",
"replicator",
"runtime",
"sdk",
"upload-perf",
"vote-signer",

View File

@ -30,40 +30,6 @@ Before you jump into the code, review the online book [Solana: Blockchain Rebuil
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
Release Binaries
===
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
Additionally we provide pre-release binaries for the latest code on the edge and
beta channels. Note that these pre-release binaries may be less stable than an
official release.
### Edge channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
### Beta channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
Developing
===
@ -75,7 +41,7 @@ Install rustc, cargo and rustfmt:
```bash
$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
$ rustup component add rustfmt
$ rustup component add rustfmt-preview
```
If your rustc version is lower than 1.34.0, please update it:
@ -100,7 +66,7 @@ $ cd solana
Build
```bash
$ cargo build
$ cargo build --all
```
Then to run a minimal local cluster
@ -114,7 +80,7 @@ Testing
Run the test suite:
```bash
$ cargo test
$ cargo test --all
```
Local Testnet

View File

@ -2,41 +2,38 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.16.2"
version = "0.14.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.0"
clap = "2.32.0"
bincode = "1.1.2"
env_logger = "0.6.0"
itertools = "0.8.0"
log = "0.4.6"
num-derive = "0.2"
num-traits = "0.2"
rand = "0.6.5"
rayon = "1.1.0"
serde = "1.0.92"
serde_derive = "1.0.92"
serde_json = "1.0.39"
serde_yaml = "0.8.9"
num-derive = "0.2"
rayon = "1.0.3"
serde = "1.0.87"
serde_derive = "1.0.87"
serde_json = "1.0.38"
# solana-runtime = { path = "../solana/runtime"}
solana = { path = "../core", version = "0.16.2" }
solana-client = { path = "../client", version = "0.16.2" }
solana-drone = { path = "../drone", version = "0.16.2" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.16.2" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.16.2" }
solana-logger = { path = "../logger", version = "0.16.2" }
solana-metrics = { path = "../metrics", version = "0.16.2" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana-runtime = { path = "../runtime", version = "0.16.2" }
solana-sdk = { path = "../sdk", version = "0.16.2" }
solana = { path = "../core", version = "0.14.2" }
solana-client = { path = "../client", version = "0.14.2" }
solana-drone = { path = "../drone", version = "0.14.2" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.14.2" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.14.2" }
solana-logger = { path = "../logger", version = "0.14.2" }
solana-metrics = { path = "../metrics", version = "0.14.2" }
solana-netutil = { path = "../netutil", version = "0.14.2" }
solana-runtime = { path = "../runtime", version = "0.14.2" }
solana-sdk = { path = "../sdk", version = "0.14.2" }
ws = "0.8.0"
untrusted = "0.6.2"
ws = "0.8.1"
[features]
cuda = ["solana/cuda"]
erasure = []

View File

@ -23,7 +23,7 @@ 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 trade requests to
the exchange. A Swapper monitors the exchange and posts swap requests for
the exchange. A broker monitors the exchange and posts swap requests for
matching trade orders. All the transactions can execute concurrently.
## Premise
@ -75,7 +75,7 @@ matching trade orders. All the transactions can execute concurrently.
contain the same information as the trade request.
- Price spread
- The difference between the two matching trade orders. The spread is the
profit of the Swapper initiating the swap request.
profit of the broker initiating the swap request.
- Swap requirements
- Policies that result in a successful trade swap.
- Swap request
@ -85,7 +85,7 @@ matching trade orders. All the transactions can execute concurrently.
swap requirements. A trade swap may not wholly satisfy one or both of the
trade orders in which case the trade orders are adjusted appropriately. As
long as the swap requirements are met there will be an exchange of tokens
between accounts. Any price spread is deposited into the Swapper's profit
between accounts. Any price spread is deposited into the broker's profit
account. All trade swaps are recorded in a new account for posterity.
- Investor
- Individual investors who hold a number of tokens and wish to trade them on
@ -93,41 +93,41 @@ matching trade orders. All the transactions can execute concurrently.
accounts containing tokens and/or trade requests. Investors post
transactions to the exchange in order to request tokens and post or cancel
trade requests.
- Swapper
- An agent who facilitates trading between investors. Swappers operate as
- Broker
- An agent who facilitates trading between investors. Brokers operate as
Solana thin clients who monitor all the trade orders looking for a trade
match. Once found, the Swapper issues a swap request to the exchange.
Swappers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Swappers also
match. Once found, the broker issues a swap request to the exchange.
Brokers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Brokers also
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
information on demand via a public network port.
- Transaction fees
- Solana transaction fees are paid for by the transaction submitters who are
the Investors and Swappers.
the Investors and Brokers.
## Exchange startup
The exchange is up and running when it reaches a state where it can take
investor's trades and Swapper's swap requests. To achieve this state the
investor's trades and broker's swap requests. To achieve this state the
following must occur in order:
- Start the Solana blockchain
- Start the Swapper thin-client
- The Swapper subscribes to change notifications for all the accounts owned by
- Start the broker thin-client
- The broker subscribes to change notifications for all the accounts owned by
the exchange program id. The subscription is managed via Solana's JSON RPC
interface.
- The Swapper starts responding to queries for bid/ask price and OHLCV
- The broker starts responding to queries for bid/ask price and OHLCV
The Swapper responding successfully to price and OHLCV requests is the signal to
The broker responding successfully to price and OHLCV requests is the signal to
the investors that trades submitted after that point will be analyzed. <!--This
is not ideal, and instead investors should be able to submit trades at any time,
and the Swapper could come and go without missing a trade. One way to achieve
this is for the Swapper to read the current state of all accounts looking for all
and the broker could come and go without missing a trade. One way to achieve
this is for the broker to read the current state of all accounts looking for all
open trade orders.-->
Investors will initially query the exchange to discover their current balance
for each type of token. If the investor does not already have an account for
each type of token, they will submit account requests. Swappers as well will
each type of token, they will submit account requests. Brokers as well will
request accounts to hold the tokens they earn by initiating trade swaps.
```rust
@ -165,7 +165,7 @@ pub struct TokenAccountInfo {
}
```
For this demo investors or Swappers can request more tokens from the exchange at
For this demo investors or brokers 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.
@ -269,10 +269,10 @@ pub enum ExchangeInstruction {
## Trade swaps
The Swapper is monitoring the accounts assigned to the exchange program and
The broker is monitoring the accounts assigned to the exchange program and
building a trade-order table. The trade order table is used to identify
matching trade orders which could be fulfilled. When a match is found the
Swapper should issue a swap request. Swap requests may not satisfy the entirety
broker 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 trade order valid for further swap requests in
the future.
@ -310,14 +310,14 @@ whole for clarity.
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
As part of a successful swap request, the exchange will credit tokens to the
Swapper's account equal to the difference in the price ratios or the two orders.
These tokens are considered the Swapper's profit for initiating the trade.
broker's account equal to the difference in the price ratios or the two orders.
These tokens are considered the broker's profit for initiating the trade.
The Swapper would initiate the following swap on the order table above:
The broker would initiate the following swap on the order table above:
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
- Swapper takes 8 B tokens as profit
- Broker takes 8 B tokens as profit
Both row 1 trades are fully realized, table becomes:
@ -328,11 +328,11 @@ Both row 1 trades are fully realized, table becomes:
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
The Swapper would initiate the following swap:
The broker would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
- Swapper takes 4 B tokens as profit
- Broker takes 4 B tokens as profit
Row 1 From is not fully realized, table becomes:
@ -343,11 +343,11 @@ Row 1 From is not fully realized, table becomes:
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
| 4 | | 2 F AB 1 5 |
The Swapper would initiate the following swap:
The broker would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
- Swapper takes 2 B tokens as profit
- Broker takes 2 B tokens as profit
Row 1 To is now fully realized, table becomes:
@ -357,11 +357,11 @@ Row 1 To is now fully realized, table becomes:
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
The Swapper would initiate the following last swap:
The broker would initiate the following last swap:
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
- Swapper takes 4 B tokens as profit
- Broker takes 4 B tokens as profit
Table becomes:
@ -383,7 +383,7 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
/// key 6 - Token account in which to deposit the brokers profit from the swap.
SwapRequest,
}
@ -442,14 +442,14 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
/// key 6 - Token account in which to deposit the brokers profit from the swap.
SwapRequest,
}
```
## Quotes and OHLCV
The Swapper will provide current bid/ask price quotes based on trade actively and
The broker 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.

View File

@ -3,29 +3,28 @@
use crate::order_book::*;
use itertools::izip;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_client::thin_client::create_client;
use solana_client::thin_client::ThinClient;
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_metrics::datapoint_info;
use solana_metrics::influxdb;
use solana_sdk::client::Client;
use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_s};
use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::prelude::*;
use std::collections::VecDeque;
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};
@ -51,8 +50,6 @@ pub struct Config {
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 {
@ -66,36 +63,18 @@ impl Default for Config {
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(),
fund_amount,
);
});
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();
#[derive(Default)]
pub struct SampleStats {
/// Maximum TPS reported by this node
pub tps: f32,
/// Total time taken for those txs
pub elapsed: Duration,
/// Total transactions reported by this node
pub txs: u64,
}
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
@ -111,71 +90,37 @@ where
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, u64> = 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 exit_signal = Arc::new(AtomicBool::new(false));
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);
}
const NUM_KEYPAIR_GROUPS: u64 = 4;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
info!("Generating {:?} keys", total_keys);
let mut keypairs = generate_keypairs(total_keys);
let trader_signers: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let swapper_signers: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let src_pubkeys: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
let profit_pubkeys: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
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!("Fund trader accounts");
fund_keys(client, &identity, &trader_signers, fund_amount);
info!("Fund swapper accounts");
fund_keys(client, &identity, &swapper_signers, fund_amount);
info!("Create {:?} source token accounts", src_pubkeys.len());
create_token_accounts(client, &trader_signers, &src_pubkeys);
@ -191,7 +136,6 @@ where
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)
@ -224,7 +168,6 @@ where
&shared_txs,
&swapper_signers,
&profit_pubkeys,
transfer_delay,
batch_size,
chunk_size,
account_groups,
@ -294,6 +237,62 @@ where
);
}
fn sample_txs<T>(
exit_signal: &Arc<AtomicBool>,
sample_stats: &Arc<RwLock<Vec<SampleStats>>>,
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
{
let mut max_tps = 0.0;
let mut total_elapsed;
let mut total_txs;
let mut now = Instant::now();
let start_time = now;
let initial_txs = client.get_transaction_count().expect("transaction count");
let mut last_txs = initial_txs;
loop {
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
if txs < last_txs {
error!("expected txs({}) >= last_txs({})", txs, last_txs);
txs = last_txs;
}
total_txs = txs - initial_txs;
let sample_txs = txs - last_txs;
last_txs = txs;
let tps = sample_txs as f32 / duration_as_s(&elapsed);
if tps > max_tps {
max_tps = tps;
}
info!(
"Sampler {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
sample_txs,
total_txs,
total_elapsed.as_secs(),
);
if exit_signal.load(Ordering::Relaxed) {
let stats = SampleStats {
tps: max_tps,
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats.write().unwrap().push(stats);
return;
}
sleep(Duration::from_secs(sample_period));
}
}
fn do_tx_transfers<T>(
exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions,
@ -302,6 +301,7 @@ fn do_tx_transfers<T>(
) where
T: Client,
{
let mut stats = Stats::default();
loop {
let txs;
{
@ -318,18 +318,48 @@ fn do_tx_transfers<T>(
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)
stats.total += n as u64;
stats.sent_ns += duration_as_ns(&duration);
let rate = n as f32 / duration_as_s(&duration);
if rate > stats.sent_peak_rate {
stats.sent_peak_rate = rate;
}
trace!(" tx {:?} sent {:.2}/s", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.add_field("count", influxdb::Value::Integer(n as i64))
.to_owned(),
);
}
if exit_signal.load(Ordering::Relaxed) {
info!(
" Thread Transferred {} Txs, avg {:.2}/s peak {:.2}/s",
stats.total,
(stats.total as f64 / stats.sent_ns as f64) * 1_000_000_000_f64,
stats.sent_peak_rate,
);
return;
}
}
}
#[derive(Default)]
struct Stats {
total: u64,
keygen_ns: u64,
keygen_peak_rate: f32,
sign_ns: u64,
sign_peak_rate: f32,
sent_ns: u64,
sent_peak_rate: f32,
}
struct TradeInfo {
trade_account: Pubkey,
order_info: TradeOrderInfo,
@ -341,7 +371,6 @@ fn swapper<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
profit_pubkeys: &[Pubkey],
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -349,57 +378,28 @@ fn swapper<T>(
) where
T: Client,
{
let mut stats = Stats::default();
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)
.get_balance(&trade_infos[0].trade_account)
.unwrap_or(0)
== 0
{
tries += 1;
if tries >= max_tries {
if tries > 300 {
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;
}
error!("Give up waiting, dump batch");
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());
sleep(Duration::from_millis(100));
}
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
trade_infos.iter().for_each(|info| {
order_book
@ -414,6 +414,9 @@ fn swapper<T>(
}
}
let swaps_size = swaps.len();
stats.total += swaps_size as u64;
let now = Instant::now();
let mut to_swap = vec![];
let start = account_group * swaps_size as usize;
@ -426,8 +429,17 @@ fn swapper<T>(
to_swap.push((signer, swap, profit));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = swaps_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", swaps_size, rate);
let (blockhash, _fee_calculator) = client
let now = Instant::now();
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
@ -447,25 +459,21 @@ fn swapper<T>(
)
})
.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;
let n = to_swap_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
datapoint_info!("bench-exchange-swaps", ("count", to_swap_txs.len(), i64));
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("swaps".to_string()))
.add_field("count", influxdb::Value::Integer(to_swap_txs.len() as i64))
.to_owned(),
);
let chunks: Vec<_> = to_swap_txs.chunks(chunk_size).collect();
{
@ -474,8 +482,6 @@ fn swapper<T>(
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) {
@ -483,9 +489,18 @@ fn swapper<T>(
}
}
info!(
"Swapper sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
"{} Swaps, batch size {}, chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
);
assert_eq!(
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
@ -500,7 +515,7 @@ fn trader<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
srcs: &[Pubkey],
transfer_delay: u64,
delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -508,19 +523,16 @@ fn trader<T>(
) where
T: Client,
{
let mut stats = Stats::default();
// TODO Hard coded for now
let pair = TokenPair::AB;
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 now = Instant::now();
let trade_keys = generate_keypairs(batch_size as u64);
let mut trades = vec![];
@ -554,12 +566,20 @@ fn trader<T>(
trades.push((signer, trade.pubkey(), direction, src));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = batch_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", batch_size, rate);
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| {
let now = Instant::now();
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, trade, direction, src)| {
@ -578,52 +598,55 @@ fn trader<T>(
)
})
.collect();
let duration = now.elapsed();
let n = trades_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("trades".to_string()))
.add_field("count", influxdb::Value::Integer(trades_txs.len() as i64))
.to_owned(),
);
{
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);
}
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
stats.total += chunk_size as u64;
shared_txs_wl.push_back(trades_txs);
}
if transfer_delay > 0 {
sleep(Duration::from_millis(transfer_delay));
if delay > 0 {
sleep(Duration::from_millis(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");
if exit_signal.load(Ordering::Relaxed) {
info!(
"{} Trades with batch size {} chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
);
break;
}
}
}
@ -719,8 +742,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) =
client.get_recent_blockhash().expect("blockhash");
let blockhash = client.get_recent_blockhash().expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
@ -771,11 +793,11 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut to_create_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, new)| {
let owner_pubkey = &signer.pubkey();
let owner_id = &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);
system_instruction::create_account(owner_id, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_id, new);
(
signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
@ -795,7 +817,7 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut retries = 0;
while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
@ -846,13 +868,13 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
}
}
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, total_txs_sent: u64) {
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(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() {
for stats in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
0 => "!!!!!",
_ => "",
@ -911,7 +933,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
let mut tries = 0;
loop {
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
@ -945,17 +967,35 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
}
}
pub fn get_clients(nodes: &[ContactInfo]) -> Vec<ThinClient> {
nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node;
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana::fullnode::FullnodeConfig;
use solana::gossip_service::discover_nodes;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone;
use solana_exchange_api::exchange_processor::process_instruction;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::create_genesis_block;
use solana_sdk::genesis_block::GenesisBlock;
use std::sync::mpsc::channel;
#[test]
@ -963,15 +1003,16 @@ mod tests {
solana_logger::setup();
const NUM_NODES: usize = 1;
let fullnode_config = FullnodeConfig::default();
let mut config = Config::default();
config.identity = Keypair::new();
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 15
config.transfer_delay = 20;
config.batch_size = 100; // 1000;
config.chunk_size = 10; // 200;
config.chunk_size = 10; // 250;
config.account_groups = 1; // 10;
let Config {
fund_amount,
@ -984,8 +1025,8 @@ mod tests {
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000,
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
native_instruction_processors: [solana_exchange_program!()].to_vec(),
fullnode_config,
native_instruction_processors: [("solana_exchange_program".to_string(), id())].to_vec(),
..ClusterConfig::default()
});
@ -1001,41 +1042,46 @@ mod tests {
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
info!("Connecting to the cluster");
let (nodes, _) = discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES)
.unwrap_or_else(|err| {
let nodes =
discover_nodes(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
exit(1);
});
let (client, num_clients) = get_multi_client(&nodes);
let clients = get_clients(&nodes);
info!("clients: {}", num_clients);
assert!(num_clients >= NUM_NODES);
if clients.len() < NUM_NODES {
error!(
"Error: Insufficient nodes discovered. Expecting {} or more",
NUM_NODES
);
exit(1);
}
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&client,
&clients[0],
&drone_addr,
&config.identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
do_bench_exchange(vec![client], config);
do_bench_exchange(clients, config);
}
#[test]
fn test_exchange_bank_client() {
solana_logger::setup();
let (genesis_block, identity) = create_genesis_block(100_000_000_000_000);
let (genesis_block, identity) = GenesisBlock::new(100_000_000_000_000);
let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction);
let clients = vec![BankClient::new(bank)];
let mut config = Config::default();
config.identity = identity;
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 0;
config.batch_size = 100; // 1500;
config.chunk_size = 10; // 1500;

View File

@ -7,7 +7,7 @@ use std::process::exit;
use std::time::Duration;
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub network_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub identity: Keypair,
pub threads: usize,
@ -18,15 +18,12 @@ pub struct Config {
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)),
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
identity: Keypair::new(),
num_nodes: 1,
@ -37,9 +34,6 @@ impl Default for Config {
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,
}
}
}
@ -49,14 +43,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.about(crate_description!())
.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)
.required(false)
.default_value("127.0.0.1:8001")
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
.help("Network's gossip entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@ -147,28 +141,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.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())
args.network_addr = solana_netutil::parse_host_port(matches.value_of("network").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
eprintln!("failed to parse network address: {}", e);
exit(1)
});
@ -204,15 +184,5 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
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
}

View File

@ -2,13 +2,9 @@ pub mod bench;
mod cli;
pub mod order_book;
#[cfg(test)]
#[macro_use]
extern crate solana_exchange_program;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use crate::bench::{airdrop_lamports, do_bench_exchange, get_clients, Config};
use log::*;
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana::gossip_service::discover_nodes;
use solana_sdk::signature::KeypairUtil;
fn main() {
@ -19,7 +15,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
network_addr,
drone_addr,
identity,
threads,
@ -30,12 +26,32 @@ fn main() {
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
..
} = cli_config;
info!("Connecting to the cluster");
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let clients = get_clients(&nodes);
info!("{} nodes found", clients.len());
if clients.len() < num_nodes {
panic!("Error: Insufficient nodes discovered");
}
info!("Funding keypair: {}", identity.pubkey());
let accounts_in_groups = batch_size * account_groups;
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&clients[0],
&drone_addr,
&identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
let config = Config {
identity,
threads,
@ -45,43 +61,7 @@ fn main() {
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);
}
do_bench_exchange(clients, config);
}

View File

@ -1 +0,0 @@
/target/

View File

@ -2,17 +2,17 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "0.16.2"
version = "0.14.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
solana = { path = "../core", version = "0.16.2" }
solana-logger = { path = "../logger", version = "0.16.2" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana = { path = "../core", version = "0.14.2" }
solana-logger = { path = "../logger", version = "0.14.2" }
solana-netutil = { path = "../netutil", version = "0.14.2" }
[features]
cuda = ["solana/cuda"]
erasure = []

View File

@ -20,13 +20,13 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
w.meta.size = PACKET_DATA_SIZE;
w.meta.set_addr(&addr);
}
let msgs = Arc::new(msgs);
let msgs_ = msgs.clone();
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let mut num = 0;
for p in &msgs.packets {
for p in &msgs_.packets {
let a = p.meta.addr();
assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();

View File

@ -2,28 +2,24 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.16.2"
version = "0.14.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
log = "0.4.6"
rayon = "1.1.0"
serde = "1.0.92"
serde_derive = "1.0.92"
rayon = "1.0.3"
serde_json = "1.0.39"
serde_yaml = "0.8.9"
solana = { path = "../core", version = "0.16.2" }
solana-client = { path = "../client", version = "0.16.2" }
solana-drone = { path = "../drone", version = "0.16.2" }
solana-logger = { path = "../logger", version = "0.16.2" }
solana-metrics = { path = "../metrics", version = "0.16.2" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana-runtime = { path = "../runtime", version = "0.16.2" }
solana-sdk = { path = "../sdk", version = "0.16.2" }
solana = { path = "../core", version = "0.14.2" }
solana-client = { path = "../client", version = "0.14.2" }
solana-drone = { path = "../drone", version = "0.14.2" }
solana-logger = { path = "../logger", version = "0.14.2" }
solana-metrics = { path = "../metrics", version = "0.14.2" }
solana-netutil = { path = "../netutil", version = "0.14.2" }
solana-runtime = { path = "../runtime", version = "0.14.2" }
solana-sdk = { path = "../sdk", version = "0.14.2" }
[features]
cuda = ["solana/cuda"]
erasure = []

View File

@ -1,13 +1,10 @@
use solana_metrics;
use log::*;
use rayon::prelude::*;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_drone::drone::request_airdrop_transaction;
use solana_metrics::datapoint_info;
use solana_metrics::influxdb;
use solana_sdk::client::Client;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::system_transaction;
@ -17,6 +14,7 @@ use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::sleep;
@ -24,15 +22,15 @@ use std::thread::Builder;
use std::time::Duration;
use std::time::Instant;
pub const MAX_SPENDS_PER_TX: u64 = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 128;
#[derive(Debug)]
pub enum BenchTpsError {
AirdropFailure,
pub struct NodeStats {
/// Maximum TPS reported by this node
pub tps: f64,
/// Total transactions reported by this node
pub tx: u64,
}
pub type Result<T> = std::result::Result<T, BenchTpsError>;
pub const MAX_SPENDS_PER_TX: usize = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
@ -63,8 +61,7 @@ pub fn do_bench_tps<T>(
config: Config,
gen_keypairs: Vec<Keypair>,
keypair0_balance: u64,
) -> u64
where
) where
T: 'static + Client + Send + Sync,
{
let Config {
@ -101,7 +98,7 @@ where
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_txs(&exit_signal, &maxes, sample_period, &client);
sample_tx_count(&exit_signal, &maxes, first_tx_count, sample_period, &client);
})
.unwrap()
})
@ -139,39 +136,28 @@ where
let start = Instant::now();
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
let mut blockhash = Hash::default();
let mut blockhash_time = Instant::now();
while start.elapsed() < duration {
let balance = client.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;
if let Ok((new_blockhash, _fee_calculator)) = client.get_new_blockhash(&blockhash) {
blockhash = new_blockhash;
} else {
if blockhash_time.elapsed().as_secs() > 30 {
panic!("Blockhash is not updating");
}
sleep(Duration::from_millis(100));
continue;
}
blockhash_time = Instant::now();
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
generate_txs(
&shared_txs,
&blockhash,
&keypairs[..len],
&keypairs[len..],
threads,
reclaim_lamports_back_to_source_account,
&client,
);
// 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(1));
sleep(Duration::from_millis(100));
}
}
@ -208,27 +194,86 @@ where
&start.elapsed(),
total_tx_sent_count.load(Ordering::Relaxed),
);
let r_maxes = maxes.read().unwrap();
r_maxes.first().unwrap().1.txs
}
fn metrics_submit_lamport_balance(lamport_balance: u64) {
println!("Token balance: {}", lamport_balance);
datapoint_info!(
"bench-tps-lamport_balance",
("balance", lamport_balance, i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("lamport_balance".to_string()))
.add_field("balance", influxdb::Value::Integer(lamport_balance as i64))
.to_owned(),
);
}
fn generate_txs(
fn sample_tx_count<T: Client>(
exit_signal: &Arc<AtomicBool>,
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
first_tx_count: u64,
sample_period: u64,
client: &Arc<T>,
) {
let mut now = Instant::now();
let mut initial_tx_count = client.get_transaction_count().expect("transaction count");
let mut max_tps = 0.0;
let mut total;
let log_prefix = format!("{:21}:", client.transactions_addr());
loop {
let mut tx_count = client.get_transaction_count().expect("transaction count");
if tx_count < initial_tx_count {
println!(
"expected tx_count({}) >= initial_tx_count({})",
tx_count, initial_tx_count
);
tx_count = initial_tx_count;
}
let duration = now.elapsed();
now = Instant::now();
let sample = tx_count - initial_tx_count;
initial_tx_count = tx_count;
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
let tps = (sample * 1_000_000_000) as f64 / ns as f64;
if tps > max_tps {
max_tps = tps;
}
if tx_count > first_tx_count {
total = tx_count - first_tx_count;
} else {
total = 0;
}
println!(
"{} {:9.2} TPS, Transactions: {:6}, Total transactions: {}",
log_prefix, tps, sample, total
);
sleep(Duration::new(sample_period, 0));
if exit_signal.load(Ordering::Relaxed) {
println!("{} Exiting validator thread", log_prefix);
let stats = NodeStats {
tps: max_tps,
tx: total,
};
maxes
.write()
.unwrap()
.push((client.transactions_addr(), stats));
break;
}
}
}
fn generate_txs<T: Client>(
shared_txs: &SharedTransactions,
blockhash: &Hash,
source: &[Keypair],
dest: &[Keypair],
threads: usize,
reclaim: bool,
client: &Arc<T>,
) {
let blockhash = client.get_recent_blockhash().unwrap();
let tx_count = source.len();
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
let signing_start = Instant::now();
@ -242,7 +287,7 @@ fn generate_txs(
.par_iter()
.map(|(id, keypair)| {
(
system_transaction::create_user_account(id, &keypair.pubkey(), 1, *blockhash),
system_transaction::create_user_account(id, &keypair.pubkey(), 1, blockhash, 0),
timestamp(),
)
})
@ -259,9 +304,14 @@ fn generate_txs(
duration_as_ms(&duration),
blockhash,
);
datapoint_info!(
"bench-tps-generate_txs",
("duration", duration_as_ms(&duration), i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("generate_txs".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.to_owned(),
);
let sz = transactions.len() / threads;
@ -288,7 +338,7 @@ fn do_tx_transfers<T: Client>(
}
let txs;
{
let mut shared_txs_wl = shared_txs.write().expect("write lock in do_tx_transfers");
let mut shared_txs_wl = shared_txs.write().unwrap();
txs = shared_txs_wl.pop_front();
}
if let Some(txs0) = txs {
@ -305,9 +355,7 @@ fn do_tx_transfers<T: Client>(
if now > tx.1 && now - tx.1 > 1000 * 30 {
continue;
}
client
.async_send_transaction(tx.0)
.expect("async_send_transaction in do_tx_transfers");
client.async_send_transaction(tx.0).unwrap();
}
shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed);
total_tx_sent_count.fetch_add(tx_len, Ordering::Relaxed);
@ -316,10 +364,15 @@ fn do_tx_transfers<T: Client>(
duration_as_ms(&transfer_start.elapsed()),
tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
);
datapoint_info!(
"bench-tps-do_tx_transfers",
("duration", duration_as_ms(&transfer_start.elapsed()), i64),
("count", tx_len, i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64),
)
.add_field("count", influxdb::Value::Integer(tx_len as i64))
.to_owned(),
);
}
if exit_signal.load(Ordering::Relaxed) {
@ -341,13 +394,8 @@ fn verify_funding_transfer<T: Client>(client: &T, tx: &Transaction, amount: u64)
/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX
/// on every iteration. This allows us to replay the transfers because the source is either empty,
/// or full
pub fn fund_keys<T: Client>(
client: &T,
source: &Keypair,
dests: &[Keypair],
total: u64,
lamports_per_signature: u64,
) {
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lamports: u64) {
let total = lamports * dests.len() as u64;
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
@ -357,12 +405,12 @@ pub fn fund_keys<T: Client>(
let mut to_fund = vec![];
println!("creating from... {}", funded.len());
for f in &mut funded {
let max_units = cmp::min(notfunded.len() as u64, MAX_SPENDS_PER_TX);
let max_units = cmp::min(notfunded.len(), MAX_SPENDS_PER_TX);
if max_units == 0 {
break;
}
let start = notfunded.len() - max_units as usize;
let per_unit = (f.1 - max_units * lamports_per_signature) / max_units;
let start = notfunded.len() - max_units;
let per_unit = f.1 / (max_units as u64);
let moves: Vec<_> = notfunded[start..]
.iter()
.map(|k| (k.pubkey(), per_unit))
@ -417,7 +465,7 @@ pub fn fund_keys<T: Client>(
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let blockhash = client.get_recent_blockhash().unwrap();
// re-sign retained to_fund_txes with updated blockhash
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
@ -453,7 +501,7 @@ pub fn airdrop_lamports<T: Client>(
drone_addr: &SocketAddr,
id: &Keypair,
tx_count: u64,
) -> Result<()> {
) {
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(starting_balance);
println!("starting balance {}", starting_balance);
@ -467,7 +515,7 @@ pub fn airdrop_lamports<T: Client>(
id.pubkey(),
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let blockhash = client.get_recent_blockhash().unwrap();
match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap();
@ -502,14 +550,13 @@ pub fn airdrop_lamports<T: Client>(
current_balance,
starting_balance
);
return Err(BenchTpsError::AirdropFailure);
exit(1);
}
}
Ok(())
}
fn compute_and_report_stats(
maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
sample_period: u64,
tx_send_elapsed: &Duration,
total_tx_send_count: usize,
@ -523,14 +570,14 @@ fn compute_and_report_stats(
println!("---------------------+---------------+--------------------");
for (sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
let maybe_flag = match stats.tx {
0 => "!!!!!",
_ => "",
};
println!(
"{:20} | {:13.2} | {} {}",
sock, stats.tps, stats.txs, maybe_flag
sock, stats.tps, stats.tx, maybe_flag
);
if stats.tps == 0.0 {
@ -541,33 +588,27 @@ fn compute_and_report_stats(
if stats.tps > max_of_maxes {
max_of_maxes = stats.tps;
}
if stats.txs > max_tx_count {
max_tx_count = stats.txs;
if stats.tx > max_tx_count {
max_tx_count = stats.tx;
}
}
if total_maxes > 0.0 {
let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps;
let average_max = total_maxes / num_nodes_with_tps as f32;
let average_max = total_maxes / num_nodes_with_tps as f64;
println!(
"\nAverage max TPS: {:.2}, {} nodes had 0 TPS",
average_max, nodes_with_zero_tps
);
}
let total_tx_send_count = total_tx_send_count as u64;
let drop_rate = if total_tx_send_count > max_tx_count {
(total_tx_send_count - max_tx_count) as f64 / total_tx_send_count as f64
} else {
0.0
};
println!(
"\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}",
max_of_maxes,
sample_period,
max_tx_count,
maxes.read().unwrap().len(),
drop_rate,
(total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64,
);
println!(
"\tAverage TPS: {}",
@ -582,72 +623,31 @@ fn should_switch_directions(num_lamports_per_account: u64, i: u64) -> bool {
i % (num_lamports_per_account / 4) == 0 && (i >= (3 * num_lamports_per_account) / 4)
}
pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> Vec<Keypair> {
pub fn generate_keypairs(id: &Keypair, tx_count: usize) -> Vec<Keypair> {
let mut seed = [0u8; 32];
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
seed.copy_from_slice(&id.to_bytes()[..32]);
let mut rnd = GenKeys::new(seed);
let mut total_keys = 1;
while total_keys < count {
total_keys *= MAX_SPENDS_PER_TX;
let mut total_keys = 0;
let mut target = tx_count * 2;
while target > 0 {
total_keys += target;
target /= MAX_SPENDS_PER_TX;
}
rnd.gen_n_keypairs(total_keys)
}
pub fn generate_and_fund_keypairs<T: Client>(
client: &T,
drone_addr: Option<SocketAddr>,
funding_pubkey: &Keypair,
tx_count: usize,
lamports_per_account: u64,
) -> Result<(Vec<Keypair>, u64)> {
info!("Creating {} keypairs...", tx_count * 2);
let mut keypairs = generate_keypairs(funding_pubkey, tx_count as u64 * 2);
info!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let last_keypair_balance = client
.get_balance(&keypairs[tx_count * 2 - 1].pubkey())
.unwrap_or(0);
if lamports_per_account > last_keypair_balance {
let (_, fee_calculator) = client.get_recent_blockhash().unwrap();
let extra =
lamports_per_account - last_keypair_balance + fee_calculator.max_lamports_per_signature;
let total = extra * (keypairs.len() as u64);
if client.get_balance(&funding_pubkey.pubkey()).unwrap_or(0) < total {
airdrop_lamports(client, &drone_addr.unwrap(), funding_pubkey, total)?;
}
info!("adding more lamports {}", extra);
fund_keys(
client,
funding_pubkey,
&keypairs,
total,
fee_calculator.max_lamports_per_signature,
);
}
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(2 * tx_count);
Ok((keypairs, last_keypair_balance))
rnd.gen_n_keypairs(total_keys as u64)
}
#[cfg(test)]
mod tests {
use super::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::fullnode::FullnodeConfig;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_client::thin_client::create_client;
use solana_drone::drone::run_local_drone;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::client::SyncClient;
use solana_sdk::genesis_block::create_genesis_block;
use solana_sdk::genesis_block::GenesisBlock;
use std::sync::mpsc::channel;
#[test]
@ -666,13 +666,14 @@ mod tests {
}
#[test]
fn test_bench_tps_local_cluster() {
solana_logger::setup();
#[ignore]
fn test_bench_tps() {
let fullnode_config = FullnodeConfig::default();
const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 2_000_000,
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
fullnode_config,
..ClusterConfig::default()
});
@ -687,28 +688,18 @@ mod tests {
config.tx_count = 100;
config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count);
let client = create_client(
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
(cluster.entry_point_info.gossip, drone_addr),
FULLNODE_PORT_RANGE,
);
let lamports_per_account = 100;
let (keypairs, _keypair_balance) = generate_and_fund_keypairs(
&client,
Some(drone_addr),
&config.id,
config.tx_count,
lamports_per_account,
)
.unwrap();
let total = do_bench_tps(vec![client], config, keypairs, 0);
assert!(total > 100);
do_bench_tps(vec![client], config, keypairs, 0);
}
#[test]
fn test_bench_tps_bank_client() {
let (genesis_block, id) = create_genesis_block(10_000);
let (genesis_block, id) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let clients = vec![BankClient::new(bank)];
@ -717,25 +708,9 @@ mod tests {
config.tx_count = 10;
config.duration = Duration::from_secs(5);
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20).unwrap();
let keypairs = generate_keypairs(&config.id, config.tx_count);
fund_keys(&clients[0], &config.id, &keypairs, 20);
do_bench_tps(clients, config, keypairs, 0);
}
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let client = BankClient::new(bank);
let tx_count = 10;
let lamports = 20;
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports).unwrap();
for kp in &keypairs {
assert!(client.get_balance(&kp.pubkey()).unwrap() >= lamports);
}
}
}

View File

@ -4,12 +4,11 @@ use std::time::Duration;
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
use solana_drone::drone::DRONE_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
/// Holds the configuration for a single run of the benchmark
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub network_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub id: Keypair,
pub threads: usize,
@ -18,16 +17,12 @@ 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,
}
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,
@ -36,10 +31,6 @@ 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,
}
}
}
@ -49,12 +40,12 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
.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")
@ -62,7 +53,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")
@ -115,30 +106,6 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.help("Per-thread-per-iteration sleep in ms"),
)
.arg(
Arg::with_name("write-client-keys")
.long("write-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Generate client keys and stakes and write the list to YAML file"),
)
.arg(
Arg::with_name("read-client-keys")
.long("read-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Read client keys and stakes from the YAML file"),
)
.arg(
Arg::with_name("target_lamports_per_signature")
.long("target-lamports-per-signature")
.value_name("LAMPORTS")
.takes_value(true)
.help(
"The cost in lamports that the cluster will charge for signature \
verification when the cluster is operating at target-signatures-per-slot",
),
)
}
/// Parses a clap `ArgMatches` structure into a `Config`
@ -149,9 +116,9 @@ 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 = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse network address: {}", e);
exit(1)
});
}
@ -196,20 +163,5 @@ 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
}

View File

@ -2,20 +2,16 @@ mod bench;
mod cli;
use crate::bench::{
do_bench_tps, generate_and_fund_keypairs, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
airdrop_lamports, do_bench_tps, fund_keys, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::Keypair;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gossip_service::discover_nodes;
use solana_client::thin_client::create_client;
use solana_sdk::client::SyncClient;
use solana_sdk::signature::KeypairUtil;
use std::process::exit;
/// Number of signatures for all transactions in ~1 week at ~100K TPS
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
fn main() {
solana_logger::setup();
solana_metrics::set_panic_hook("bench-tps");
@ -24,7 +20,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
network_addr,
drone_addr,
id,
threads,
@ -33,78 +29,54 @@ fn main() {
tx_count,
thread_batch_sleep_ms,
sustained,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
target_lamports_per_signature,
} = cli_config;
if write_to_client_file {
let keypairs = generate_keypairs(&id, tx_count as u64 * 2);
let num_accounts = keypairs.len() as u64;
let max_fee = FeeCalculator::new(target_lamports_per_signature).max_lamports_per_signature;
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
/ num_accounts
+ NUM_LAMPORTS_PER_ACCOUNT;
let mut accounts = HashMap::new();
keypairs.iter().for_each(|keypair| {
accounts.insert(
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
num_lamports_per_account,
);
});
let serialized = serde_yaml::to_string(&accounts).unwrap();
let path = Path::new(&client_ids_and_stake_file);
let mut file = File::create(path).unwrap();
file.write_all(&serialized.into_bytes()).unwrap();
return;
}
println!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let (client, num_clients) = get_multi_client(&nodes);
if nodes.len() < num_clients {
let nodes = discover_nodes(&network_addr, 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);
}
let (keypairs, keypair_balance) = if read_from_client_file {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
let mut keypairs = vec![];
let mut last_balance = 0;
accounts.into_iter().for_each(|(keypair, balance)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
last_balance = balance;
});
(keypairs, last_balance)
} else {
generate_and_fund_keypairs(
&client,
Some(drone_addr),
&id,
tx_count,
NUM_LAMPORTS_PER_ACCOUNT,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
exit(1);
let clients: Vec<_> = nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node.clone();
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
};
.collect();
println!("Creating {} keypairs...", tx_count * 2);
let keypairs = generate_keypairs(&id, tx_count);
println!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let keypair0_balance = clients[0]
.get_balance(&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 * (keypairs.len() as u64);
airdrop_lamports(&clients[0], &drone_addr, &id, total);
println!("adding more lamports {}", extra);
fund_keys(&clients[0], &id, &keypairs, extra);
}
let config = Config {
id,
@ -115,5 +87,5 @@ fn main() {
sustained,
};
do_bench_tps(vec![client], config, keypairs, keypair_balance);
do_bench_tps(clients, config, keypairs, keypair0_balance);
}

View File

@ -1,19 +0,0 @@
+------------------------------------------------------------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +--------+-+------+ +------+-+--------+ |
| | | | | |
| | +-----------------------------+ | | |
| | +------------------------+------+ | |
| | | | | |
+------------------------------------------------------------------+
| | | |
v v v v
+---------+------+---+ +-+--------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+--------------------+ +--------------------+

View File

@ -1,15 +0,0 @@
+--------------+
| |
+------------+ Leader +------------+
| | | |
| +--------------+ |
v v
+------------+----------------------------------------+------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +-----------------+ +-----------------+ |
| |
+------------------------------------------------------------------+

View File

@ -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 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+

View File

@ -1,5 +1,5 @@
.--------------------------------------.
| Validator |
| Fullnode |
| |
.--------. | .-------------------. |
| |---->| | |

View File

@ -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 |
| |
`------------`

View File

@ -3,4 +3,16 @@ set -e
cd "$(dirname "$0")"
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)"

View File

@ -1,8 +1,7 @@
BOB_SRCS=$(wildcard art/*.bob)
MSC_SRCS=$(wildcard art/*.msc)
MD_SRCS=$(wildcard src/*.md)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg)
all: html/index.html
@ -18,10 +17,6 @@ src/img/%.svg: art/%.bob
@mkdir -p $(@D)
svgbob < $< > $@
src/img/%.svg: art/%.msc
@mkdir -p $(@D)
mscgen -T svg -i $< -o $@
src/%.md: %.md
@mkdir -p $(@D)
@cp $< $@

View File

@ -5,8 +5,6 @@
- [Terminology](terminology.md)
- [Getting Started](getting-started.md)
- [Testnet Participation](testnet-participation.md)
- [Testnet Replicator](testnet-replicator.md)
- [Example: Web Wallet](webwallet.md)
- [Programming Model](programs.md)
@ -18,13 +16,13 @@
- [Leader Rotation](leader-rotation.md)
- [Fork Generation](fork-generation.md)
- [Managing Forks](managing-forks.md)
- [Turbine Block Propagation](turbine-block-propagation.md)
- [Data Plane Fanout](data-plane-fanout.md)
- [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.md)
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
- [Performance Metrics](performance-metrics.md)
- [Anatomy of a Validator](validator.md)
- [Anatomy of a Fullnode](fullnode.md)
- [TPU](tpu.md)
- [TVU](tvu.md)
- [Blocktree](blocktree.md)
@ -41,6 +39,9 @@
- [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.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)
@ -55,18 +56,13 @@
- [Economic Design MVP](ed_mvp.md)
- [References](ed_references.md)
- [Cluster Test Framework](cluster-test-framework.md)
- [Testing Programs](testing-programs.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Validator](validator-proposal.md)
- [Implemented Design Proposals](implemented-proposals.md)
- [Blocktree](blocktree.md)
- [Cluster Software Installation and Updates](installer.md)
- [Deterministic Transaction Fees](transaction-fees.md)
- [Implemented Design Proposals](implemented-proposals.md)
- [Fork Selection](fork-selection.md)
- [Leader-to-Leader Transition](leader-leader-transition.md)
- [Leader-to-Validator Transition](leader-validator-transition.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Repair Service](repair-service.md)
- [Testing Programs](testing-programs.md)
- [Testnet Participation](testnet-participation.md)

View File

@ -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:

View File

@ -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.
@ -61,18 +61,18 @@ let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
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:

View File

@ -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

View File

@ -0,0 +1,84 @@
# Data Plane Fanout
A Solana cluster uses a multi-layer mechanism called *data plane fanout* to
broadcast transaction blobs to all nodes in a very quick and efficient manner.
In order to establish the fanout, the cluster divides itself into small
collections of nodes, called *neighborhoods*. Each node is responsible for
sharing any data it receives with the other nodes in its neighborhood, as well
as propagating the data on to a small set of nodes in other neighborhoods.
During its slot, the leader node distributes blobs between the validator nodes
in one neighborhood (layer 1). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in each of multiple
neighborhoods in the next layer (layer 2). The layer-2 nodes each share their
data with their neighborhood peers, and retransmit to nodes in the next layer,
etc, until all nodes in the cluster have received all the blobs.
<img alt="Two layer cluster" src="img/data-plane.svg" class="center"/>
## Neighborhood Assignment - Weighted Selection
In order for data plane fanout to work, the entire cluster must agree on how the
cluster is divided into neighborhoods. To achieve this, all the recognized
validator nodes (the TVU peers) are sorted by stake and stored in a list. This
list is then indexed in different ways to figure out neighborhood boundaries and
retransmit peers. For example, the leader will simply select the first nodes to
make up layer 1. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-1 and lower-layer
nodes use the same logic to find their neighbors and lower layer peers.
## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
nodes. If this layer 1 is smaller than the number of nodes in the cluster, then
the data plane fanout mechanism adds layers below. Subsequent layers follow
these constraints to determine layer-capacity: Each neighborhood contains
`NEIGHBORHOOD_SIZE` nodes and each layer may have up to `DATA_PLANE_FANOUT/2`
neighborhoods.
As mentioned above, each node in a layer only has to broadcast its blobs to its
neighbors and to exactly 1 node in each next-layer neighborhood, instead of to
every TVU peer in the cluster. In the default mode, each layer contains
`DATA_PLANE_FANOUT/2` neighborhoods. The retransmit mechanism also supports a
second, `grow`, mode of operation that squares the number of neighborhoods
allowed each layer. This dramatically reduces the number of layers needed to
support a large cluster, but can also have a negative impact on the network
pressure on each node in the lower layers. A good way to think of the default
mode (when `grow` is disabled) is to imagine it as chain of layers, where the
leader sends blobs to layer-1 and then layer-1 to layer-2 and so on, the `layer
capacities` remain constant, so all layers past layer-2 will have the same
number of nodes until the whole cluster is covered. When `grow` is enabled, this
becomes a traditional fanout where layer-3 will have the square of the number of
nodes in layer-2 and so on.
#### Configuration Values
`DATA_PLANE_FANOUT` - Determines the size of layer 1. Subsequent
layers have `DATA_PLANE_FANOUT/2` neighborhoods when `grow` is inactive.
`NEIGHBORHOOD_SIZE` - The number of nodes allowed in a neighborhood.
Neighborhoods will fill to capacity before new ones are added, i.e if a
neighborhood isn't full, it _must_ be the last one.
`GROW_LAYER_CAPACITY` - Whether or not retransmit should be behave like a
_traditional fanout_, i.e if each additional layer should have growing
capacities. When this mode is disabled (default), all layers after layer 1 have
the same capacity, keeping the network pressure on all nodes equal.
Currently, configuration is set when the cluster is launched. In the future,
these parameters may be hosted on-chain, allowing modification on the fly as the
cluster sizes change.
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact.
What this diagram doesn't capture is that each neighbor actually receives
blobs from one validator per neighborhood above it. This means that, to
cripple a neighborhood, enough nodes (erasure codes +1 per neighborhood) from
the layer above need to fail. Since multiple neighborhoods exist in the upper
layer and a node will receive blobs from a node in each of those neighborhoods,
we'd need a big network failure in the upper layers to end up with incomplete
data.
<img alt="Inner workings of a neighborhood"
src="img/data-plane-neighborhood.svg" class="center"/>

View File

@ -1,10 +1,10 @@
# Anatomy of a Validator
# Anatomy of a Fullnode
<img alt="Validator block diagrams" src="img/validator.svg" class="center"/>
<img alt="Fullnode block diagrams" src="img/fullnode.svg" class="center"/>
## Pipelining
The validators make extensive use of an optimization common in CPU design,
The fullnodes make extensive use of an optimization common in CPU design,
called *pipelining*. Pipelining is the right tool for the job when there's a
stream of input data that needs to be processed by a sequence of steps, and
there's different hardware responsible for each. The quintessential example is
@ -19,9 +19,9 @@ dryer and the first is being folded. In this way, one can make progress on
three loads of laundry simultaneously. Given infinite loads, the pipeline will
consistently complete a load at the rate of the slowest stage in the pipeline.
## Pipelining in the Validator
## Pipelining in the Fullnode
The validator contains two pipelined processes, one used in leader mode called
The fullnode contains two pipelined processes, one used in leader mode called
the TPU and one used in validator mode called the TVU. In both cases, the
hardware being pipelined is the same, the network input, the GPU cards, the CPU
cores, writes to disk, and the network output. What it does with that hardware

View File

@ -47,8 +47,8 @@ nodes are started
$ cargo build --all
```
The network is initialized with a genesis ledger generated by running the
following script.
The network is initialized with a genesis ledger and fullnode configuration files.
These files can be generated by running the following script.
```bash
$ ./multinode-demo/setup.sh
@ -69,7 +69,7 @@ $ ./multinode-demo/drone.sh
### Singlenode Testnet
Before you start a validator, make sure you know the IP address of the machine you
Before you start a fullnode, make sure you know the IP address of the machine you
want to be the bootstrap leader for the demo, and make sure that udp ports 8000-10000 are
open on all the machines you want to test with.
@ -86,10 +86,10 @@ The drone does not need to be running for subsequent leader starts.
### Multinode Testnet
To run a multinode testnet, after starting a leader node, spin up some
additional validators in separate shells:
additional full nodes in separate shells:
```bash
$ ./multinode-demo/validator-x.sh
$ ./multinode-demo/fullnode-x.sh
```
To run a performance-enhanced full node on Linux,
@ -99,7 +99,7 @@ your system:
```bash
$ ./fetch-perf-libs.sh
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
$ SOLANA_CUDA=1 ./multinode-demo/fullnode-x.sh
```
### Testnet Client Demo
@ -145,7 +145,7 @@ Generally we are using `debug` for infrequent debug messages, `trace` for potent
messages and `info` for performance-related logging.
You can also attach to a running process with GDB. The leader's process is named
_solana-validator_:
_solana-fullnode_:
```bash
$ sudo gdb
@ -161,7 +161,7 @@ This will dump all the threads stack traces into gdb.txt
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
```bash
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
$ ./multinode-demo/client.sh --network testnet.solana.com:8001 --duration 60
```
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)

View File

@ -1,6 +1,6 @@
# Gossip Service
The Gossip Service acts as a gateway to nodes in the control plane. Validators
The Gossip Service acts as a gateway to nodes in the control plane. Fullnodes
use the service to ensure information is available to all other nodes in a cluster.
The service broadcasts information using a gossip protocol.
@ -116,8 +116,8 @@ Just like *pull message*, nodes are selected into the active set based on weight
## Notable differences from PlumTree
The active push protocol described here is based on [Plum
Tree](https://haslab.uminho.pt/jop/files/lpr07a.pdf). The main differences are:
The active push protocol described here is based on (Plum
Tree)[https://haslab.uminho.pt/jop/files/lpr07a.pdf]. The main differences are:
* Push messages have a wallclock that is signed by the originator. Once the
wallclock expires the message is dropped. A hop limit is difficult to implement

View File

@ -12,18 +12,18 @@ updates is managed using an on-chain update manifest program.
#### Fetch and run a pre-built installer using a bootstrap curl/shell script
The easiest install method for supported platforms:
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh
```
This script will check github for the latest tagged release and download and run the
`solana-install-init` binary from there.
`solana-install` binary from there.
If additional arguments need to be specified during the installation, the
following shell syntax is used:
```bash
$ init_args=.... # arguments for `solana-install-init ...`
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s - ${init_args}
$ init_args=.... # arguments for `solana-installer init ...`
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh -s - ${init_args}
```
#### Fetch and run a pre-built installer from a Github release
@ -31,9 +31,9 @@ With a well-known release URL, a pre-built binary can be obtained for supported
platforms:
```bash
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.16.0/solana-install-init-x86_64-apple-darwin
$ chmod +x ./solana-install-init
$ ./solana-install-init --help
$ curl -o solana-install https://github.com/solana-labs/solana/releases/download/v0.13.0/solana-install-x86_64-apple-darwin
$ chmod +x ./solana-install
$ ./solana-install --help
```
#### Build and run the installer from source
@ -49,7 +49,7 @@ $ cargo run -- --help
Given a solana release tarball (as created by `ci/publish-tarball.sh`) that has already been uploaded to a publicly accessible URL,
the following commands will deploy the update:
```bash
$ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users
$ solana-keygen -o update-manifest.json # <-- only generated once, the public key is shared with users
$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json
```
@ -58,7 +58,7 @@ $ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update
$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
$ export PATH=~/.local/share/solana-install/bin:$PATH
$ solana-keygen ... # <-- runs the latest solana-keygen
$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necesary when an update is applied
$ solana-install run solana-fullnode ... # <-- runs a fullnode, restarting it as necesary when an update is applied
```
### On-chain Update Manifest
@ -119,7 +119,7 @@ It manages the following files and directories in the user's home directory:
#### Command-line Interface
```manpage
solana-install 0.16.0
solana-install 0.13.0
The solana cluster software installer
USAGE:

View File

@ -25,14 +25,11 @@ Methods
* [getAccountInfo](#getaccountinfo)
* [getBalance](#getbalance)
* [getClusterNodes](#getclusternodes)
* [getProgramAccounts](#getprogramaccounts)
* [getRecentBlockhash](#getrecentblockhash)
* [getSignatureStatus](#getsignaturestatus)
* [getSlotLeader](#getslotleader)
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount)
* [getTotalSupply](#gettotalsupply)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction)
* [startSubscriptionChannel](#startsubscriptionchannel)
@ -97,32 +94,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
{"jsonrpc":"2.0","result":true,"id":1}
```
---
### getAccountInfo
Returns all information associated with the account of provided Pubkey
##### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
##### Results:
The result field will be a JSON object with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
```
---
### getBalance
@ -153,7 +124,7 @@ 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
* `id` - Node identifier, 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
@ -164,49 +135,45 @@ The result field will be an array of JSON objects, each with the following sub f
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}
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","id":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
---
### getProgramAccounts
Returns all accounts owned by the provided program Pubkey
### getAccountInfo
Returns all information associated with the account of provided Pubkey
##### Parameters:
* `string` - Pubkey of program, as base-58 encoded string
* `string` - Pubkey of account to query, as base-58 encoded string
##### Results:
The result field will be an array of arrays. Each sub array will contain:
* `string` - a the account Pubkey as base-58 encoded string
and a JSON object, with the following sub fields:
The result field will be a JSON object with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
* `loader`, array of 32 bytes representing the loader for this program (if `executable`), otherwise all
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
```
---
### getRecentBlockhash
Returns a recent block hash from the ledger, and a fee schedule that can be used
to compute the cost of submitting a transaction using it.
Returns a recent block hash from the ledger
##### Parameters:
None
##### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
##### Example:
```bash
@ -214,7 +181,7 @@ An array consisting of
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
---
@ -304,51 +271,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
---
### getTotalSupply
Returns the current total supply in Lamports
##### Parameters:
None
##### Results:
* `integer` - Total supply, as unsigned 64-bit integer
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":10126,"id":1}
```
---
### getEpochVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current epoch.
##### Parameters:
None
##### Results:
The result field will be an array of JSON objects, each with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `stake` - the stake, in lamports, delegated to this vote account
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"commission":0,"nodePubkey":"Et2RaZJdJRTzTkodUwiHr4H6sLkVmijBFv8tkd7oSSFY","stake":42,"votePubkey":"B4CdWq3NBSoH2wYsVE1CaZSWPo2ZtopE4SJipQhZ3srF"}],"id":1}
```
---
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
@ -394,14 +316,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
- Submit subscription requests to the websocket using the methods below
- Multiple subscriptions may be active at once
- All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
---
@ -411,8 +325,6 @@ for a given account public key changes
##### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -422,15 +334,13 @@ for a given account public key changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
##### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
---
@ -461,8 +371,6 @@ for a given account owned by the program changes
##### Parameters:
* `string` - program_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -472,8 +380,6 @@ for a given account owned by the program changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
@ -513,8 +419,6 @@ On `signatureNotification`, the subscription is automatically cancelled
##### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - subscription id (needed to unsubscribe)
@ -524,8 +428,6 @@ On `signatureNotification`, the subscription is automatically cancelled
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```

View File

@ -45,7 +45,7 @@ The upsides compared to guards:
* The timeout is not fixed.
* The timeout is local to the leader, and therefore can be clever. The leader's
heuristic can take into account turbine performance.
heuristic can take into account avalanche performance.
* This design doesn't require a ledger hard fork to update.

View File

@ -57,7 +57,7 @@ Forwarding is preferred, as it would minimize network congestion, allowing the
cluster to advertise higher TPS capacity.
## Validator Loop
## Fullnode Loop
The PoH Recorder manages the transition between modes. Once a ledger is
replayed, the validator can run until the recorder indicates it should be

View File

@ -2,12 +2,6 @@
Replication behavior yet to be implemented.
### Storage epoch
The storage epoch should be the number of slots which results in around 100GB-1TB of
ledger to be generated for replicators to store. Replicators will start storing ledger
when a given fork has a high probability of not being rolled back.
### Validator behavior
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
@ -43,100 +37,3 @@ transacation proves the validator incorrectly validated a fake storage proof.
The replicator is rewarded and the validator's staking balance is slashed or
frozen.
### Storage proof contract logic
Each replicator and validator will have their own storage account. The validator's
account would be separate from their gossip id similiar to their vote account.
These should be implemented as two programs one which handles the validator as the keysigner
and one for the replicator. In that way when the programs reference other accounts, they
can check the program id to ensure it is a validator or replicator account they are
referencing.
#### SubmitMiningProof
```rust,ignore
SubmitMiningProof {
slot: u64,
sha_state: Hash,
signature: Signature,
};
keys = [replicator_keypair]
```
Replicators create these after mining their stored ledger data for a certain hash value.
The slot is the end slot of the segment of ledger they are storing, the sha\_state
the result of the replicator using the hash function to sample their encrypted ledger segment.
The signature is the signature that was created when they signed a PoH value for the
current storage epoch. The list of proofs from the current storage epoch should be saved
in the account state, and then transfered to a list of proofs for the previous epoch when
the epoch passes. In a given storage epoch a given replicator should only submit proofs
for one segment.
The program should have a list of slots which are valid storage mining slots.
This list should be maintained by keeping track of slots which are rooted slots in which a significant
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
number of slots would be added to this set. The program should check that the slot is in this set. The set can
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/locktower state.
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
the previous storage epoch PoH value.
#### ProofValidation
```rust,ignore
ProofValidation {
proof_mask: Vec<ProofStatus>,
}
keys = [validator_keypair, replicator_keypair(s) (unsigned)]
```
A validator will submit this transaction to indicate that a set of proofs for a given
segment are valid/not-valid or skipped where the validator did not look at it. The
keypairs for the replicators that it looked at should be referenced in the keys so the program
logic can go to those accounts and see that the proofs are generated in the previous epoch. The
sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by
the validator according to the logic outlined in the validator behavior of sampling.
The included replicator keys will indicate the the storage samples which are being referenced; the
length of the proof\_mask should be verified against the set of storage proofs in the referenced
replicator account(s), and should match with the number of proofs submitted in the previous storage
epoch in the state of said replicator account.
#### ClaimStorageReward
```rust,ignore
ClaimStorageReward {
}
keys = [validator_keypair or replicator_keypair, validator/replicator_keypairs (unsigned)]
```
Replicators and validators will use this transaction to get paid tokens from a program state
where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where
proofs have been submitted and validated and there are no ChallengeProofValidations referencing
those proofs. For a validator, it should reference the replicator keypairs to which it has validated
proofs in the relevant epoch. And for a replicator it should reference validator keypairs for which it
has validated and wants to be rewarded.
#### ChallengeProofValidation
```rust,ignore
ChallengeProofValidation {
proof_index: u64,
hash_seed_value: Vec<u8>,
}
keys = [replicator_keypair, validator_keypair]
```
This transaction is for catching lazy validators who are not doing the work to validate proofs.
A replicator will submit this transaction when it sees a validator has approved a fake SubmitMiningProof
transaction. Since the replicator is a light client not looking at the full chain, it will have to ask
a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for
a certain segment in the previous storage epoch. The program will look in the validator account
state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and
see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so,
then it will save the challenge to the list of challenges that it has in its state.
#### AdvertiseStorageRecentBlockhash
```rust,ignore
AdvertiseStorageRecentBlockhash {
hash: Hash,
slot: u64,
}
```
Validators and replicators will submit this to indicate that a new storage epoch has passed and that the
storage proofs which are current proofs should now be for the previous epoch. Other transactions should
check to see that the epoch that they are referencing is accurate according to current chain state.

View File

@ -1,18 +1,19 @@
# Ledger Replication
At full capacity on a 1gbps network solana will generate 4 petabytes of data
per year. To prevent the network from centralizing around validators that have
per year. To prevent the network from centralizing around full nodes that have
to store the full data set this protocol proposes a way for mining nodes to
provide storage capacity for pieces of the data.
provide storage capacity for pieces of the network.
The basic idea to Proof of Replication is encrypting a dataset with a public
symmetric key using CBC encryption, then hash the encrypted dataset. The main
problem with the naive approach is that a dishonest storage node can stream the
encryption and delete the data as it's hashed. The simple solution is to periodically
regenerate the hash based on a signed PoH value. This ensures that all the data is present
during the generation of the proof and it also requires validators to have the
entirety of the encrypted data present for verification of every proof of every identity.
So the space required to validate is `number_of_proofs * data_size`
encryption and delete the data as its hashed. The simple solution is to force
the hash to be done on the reverse of the encryption, or perhaps with a random
order. This ensures that all the data is present during the generation of the
proof and it also requires the validator to have the entirety of the encrypted
data present for verification of every proof of every identity. So the space
required to validate is `number_of_proofs * data_size`
## Optimization with PoH
@ -28,12 +29,13 @@ core. The total space required for verification is `1_ledger_segment +
## Network
Validators for PoRep are the same validators that are verifying transactions.
If a replicator can prove that a validator verified a fake PoRep, then the
validator will not receive a reward for that storage epoch.
They have some stake that they have put up as collateral that ensures that
their work is honest. If you can prove that a validator verified a fake PoRep,
then the validators stake can be slashed.
Replicators are specialized *light clients*. They download a part of the
ledger (a.k.a Segment) and store it, and provide PoReps of storing the ledger.
For each verified PoRep replicators earn a reward of sol from the mining pool.
Replicators are specialized *light clients*. They download a part of the ledger
and store it, and provide PoReps of storing the ledger. For each verified PoRep
replicators earn a reward of sol from the mining pool.
## Constraints
@ -51,10 +53,11 @@ changes to determine what rate it can validate storage proofs.
### Constants
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
1. NUM\_STORAGE\_ENTRIES: Number of entries in a segment of ledger data. The
unit of storage for a replicator.
2. NUM\_KEY\_ROTATION\_SEGMENTS: Number of segments after which replicators
regenerate their encryption keys and select a new dataset to store.
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
key generation for the section of ledger just generated and the rotation of
another key in the set.
3. NUM\_STORAGE\_PROOFS: Number of storage proofs required for a storage proof
claim to be successfully rewarded.
4. RATIO\_OF\_FAKE\_PROOFS: Ratio of fake proofs to real proofs that a storage
@ -63,108 +66,75 @@ mining proof claim has to contain to be valid for a reward.
proof.
6. NUM\_CHACHA\_ROUNDS: Number of encryption rounds performed to generate
encrypted state.
7. NUM\_SLOTS\_PER\_TURN: Number of slots that define a single storage epoch or
a "turn" of the PoRep game.
### Validator behavior
1. Validators join the network and begin looking for replicator accounts at each
storage epoch/turn boundary.
2. Every turn, Validators sign the PoH value at the boundary and use that signature
to randomly pick proofs to verify from each storage account found in the turn boundary.
This signed value is also submitted to the validator's storage account and will be used by
replicators at a later stage to cross-verify.
3. Every `NUM_SLOTS_PER_TURN` slots the validator advertises the PoH value. This is value
is also served to Replicators via RPC interfaces.
4. For a given turn N, all validations get locked out until turn N+3 (a gap of 2 turn/epoch).
At which point all validations during that turn are available for reward collection.
5. Any incorrect validations will be marked during the turn in between.
1. Validator joins the network and submits a storage validation capacity
transaction which tells the network how many proofs it can process in a given
period defined by NUM\_KEY\_ROTATION\_TICKS.
2. Every NUM\_KEY\_ROTATION\_TICKS the validator stores the PoH value at that
height.
3. Validator generates a storage proof confirmation transaction.
4. The storage proof confirmation transaction is integrated into the ledger.
6. Validator responds to RPC interfaces for what the last storage epoch PoH
value is and its entry\_height.
### Replicator behavior
1. Since a replicator is somewhat of a light client and not downloading all the
ledger data, they have to rely on other validators and replicators for information.
Any given validator may or may not be malicious and give incorrect information, although
there are not any obvious attack vectors that this could accomplish besides having the
replicator do extra wasted work. For many of the operations there are a number of options
depending on how paranoid a replicator is:
ledger data, they have to rely on other full nodes (validators) for
information. Any given validator may or may not be malicious and give incorrect
information, although there are not any obvious attack vectors that this could
accomplish besides having the replicator do extra wasted work. For many of the
operations there are a number of options depending on how paranoid a replicator
is:
- (a) replicator can ask a validator
- (b) replicator can ask multiple validators
- (c) replicator can ask other replicators
- (d) replicator can subscribe to the full transaction stream and generate
the information itself (assuming the slot is recent enough)
- (e) replicator can subscribe to an abbreviated transaction stream to
generate the information itself (assuming the slot is recent enough)
2. A replicator obtains the PoH hash corresponding to the last turn with its slot.
- (c) replicator can subscribe to the full transaction stream and generate
the information itself
- (d) replicator can subscribe to an abbreviated transaction stream to
generate the information itself
2. A replicator obtains the PoH hash corresponding to the last key rotation
along with its entry\_height.
3. The replicator signs the PoH hash with its keypair. That signature is the
seed used to pick the segment to replicate and also the encryption key. The
replicator mods the signature with the slot to get which segment to
replicator mods the signature with the entry\_height to get which segment to
replicate.
4. The replicator retrives the ledger by asking peer validators and
replicators. See 6.5.
5. The replicator then encrypts that segment with the key with chacha algorithm
in CBC mode with `NUM_CHACHA_ROUNDS` of encryption.
6. The replicator initializes a chacha rng with the a signed recent PoH value as
in CBC mode with NUM\_CHACHA\_ROUNDS of encryption.
6. The replicator initializes a chacha rng with the signature from step 2 as
the seed.
7. The replicator generates `NUM_STORAGE_SAMPLES` samples in the range of the
7. The replicator generates NUM\_STORAGE\_SAMPLES samples in the range of the
entry size and samples the encrypted segment with sha256 for 32-bytes at each
offset value. Sampling the state should be faster than generating the encrypted
segment.
8. The replicator sends a PoRep proof transaction which contains its sha state
at the end of the sampling operation, its seed and the samples it used to the
current leader and it is put onto the ledger.
9. During a given turn the replicator should submit many proofs for the same segment
and based on the `RATIO_OF_FAKE_PROOFS` some of those proofs must be fake.
10. As the PoRep game enters the next turn, the replicator must submit a
transaction with the mask of which proofs were fake during the last turn. This
transaction will define the rewards for both replicators and validators.
11. Finally for a turn N, as the PoRep game enters turn N + 3, replicator's proofs for
turn N will be counted towards their rewards.
### The PoRep Game
The Proof of Replication game has 4 primary stages. For each "turn" multiple PoRep
games can be in progress but each in a different stage.
The 4 stages of the PoRep Game are as follows:
1. Proof submission stage
- Replicators: submit as many proofs as possible during this stage
- Validators: No-op
2. Proof verification stage
- Replicators: No-op
- Validators: Select replicators and verify their proofs from the previous turn
3. Proof challenge stage
- Replicators: Submit the proof mask with justifications (for fake proofs submitted 2 turns ago)
- Validators: No-op
4. Reward collection stage
- Replicators: Collect rewards for 3 turns ago
- Validators: Collect rewards for 3 turns ago
For each turn of the PoRep game, both Validators and Replicators evaluate each
stage. The stages are run as separate transactions on the storage program.
### Finding who has a given block of ledger
1. Validators monitor the turns in the PoRep game and look at the rooted bank
at turn boundaries for any proofs.
2. Validators maintain a map of ledger segments and corresponding replicator public keys.
The map is updated when a Validator processes a replicator's proofs for a segment.
The validator provides an RPC interface to access the this map. Using this API, clients
can map a segment to a replicator's network address (correlating it via cluster_info table).
The clients can then send repair requests to the replicator to retrieve segments.
3. Validators would need to invalidate this list every N turns.
1. Validators monitor the transaction stream for storage mining proofs, and
keep a mapping of ledger segments by entry\_height to public keys. When it sees
a storage mining proof it updates this mapping and provides an RPC interface
which takes an entry\_height and hands back a list of public keys. The client
then looks up in their cluster\_info table to see which network address that
corresponds to and sends a repair request to retrieve the necessary blocks of
ledger.
2. Validators would need to prune this list which it could do by periodically
looking at the oldest entries in its mappings and doing a network query to see
if the storage host is still serving the first entry.
## Sybil attacks
For any random seed, we force everyone to use a signature that is derived from
a PoH hash at the turn boundary. Everyone uses the same count, so the same PoH
hash is signed by every participant. The signatures are then each cryptographically
tied to the keypair, which prevents a leader from grinding on the resulting
value for more than 1 identity.
a PoH hash. Everyone must use the same count, so the same PoH hash is signed by
every participant. The signatures are then each cryptographically tied to the
keypair, which prevents a leader from grinding on the resulting value for more
than 1 identity.
Since there are many more client identities then encryption identities, we need
to split the reward for multiple clients, and prevent Sybil attacks from
@ -185,7 +155,8 @@ the network can reward long lived client identities more than new ones.
showing the initial state for the hash.
- If a validator marks real proofs as fake, no on-chain computation can be done
to distinguish who is correct. Rewards would have to rely on the results from
multiple validators to catch bad actors and replicators from being denied rewards.
multiple validators in a stake-weighted fashion to catch bad actors and
replicators from being locked out of the network.
- Validator stealing mining proof results for itself. The proofs are derived
from a signature from a replicator, since the validator does not know the
private key used to generate the encryption key, it cannot be the generator of

View File

@ -76,24 +76,21 @@ this field can only modified by this entity
### StakeState
A StakeState takes one of two forms, StakeState::Stake and StakeState::MiningPool.
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
### StakeState::Stake
### StakeState::Delegate
Stake is the current delegation preference of the **staker**. Stake
StakeState is the current delegation preference of the **staker**. StakeState
contains the following state information:
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
* Account::lamports - The staked lamports.
* `voter_id` - The pubkey of the VoteState instance the lamports are
delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the
program.
* `stake` - The actual activated stake.
* Account::lamports - Lamports available for staking, including any earned as rewards.
### StakeState::MiningPool
There are two approaches to the mining pool. The bank could allow the
@ -108,12 +105,11 @@ tokens stored as `Account::lamports`.
The stakes and the MiningPool are accounts that are owned by the same `Stake`
program.
### StakeInstruction::DelegateStake(stake)
### StakeInstruction::Initialize
* `account[0]` - RW - The StakeState::Stake instance.
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`
`StakeState::Stake::stake` is initialized to `stake`, as long as it's less than account[0].lamports
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_id` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
@ -128,15 +124,15 @@ deposited into the StakeState and as validator commission is proportional to
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
reward.
* `account[1]` - RW - The StakeState::Stake instance that is redeeming votes
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
`StakeState::voter_id`
Reward is payed out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
`VoteState::credits`. The commission is deposited into the `VoteState` token
balance, and the reward is deposited to the `StakeState::Stake` token balance. The
balance, and the reward is deposited to the `StakeState::Delegate` token balance. The
reward and the commission is weighted by the `StakeState::lamports` divided by total lamports staked.
The Staker or the owner of the Stake program sends a transaction with this
@ -150,7 +146,7 @@ stake_state.credits_observed = vote_state.credits;
```
`credits_to_claim` is used to compute the reward and commission, and
`StakeState::Stake::credits_observed` is updated to the latest
`StakeState::Delegate::credits_observed` is updated to the latest
`VoteState::credits` value.
### Collecting network fees into the MiningPool
@ -179,13 +175,13 @@ many rewards to be claimed concurrently.
## Passive Delegation
Any number of instances of StakeState::Stake programs can delegate to a single
Any number of instances of StakeState::Delegate programs can delegate to a single
VoteState program without an interactive action from the identity controlling
the VoteState program or submitting votes to the program.
The total stake allocated to a VoteState program can be calculated by the sum of
all the StakeState programs that have the VoteState pubkey as the
`StakeState::Stake::voter_pubkey`.
`StakeState::Delegate::voter_id`.
## Example Callflow
@ -198,12 +194,12 @@ nodes since stake is used as weight in the network control and data planes. One
way to implement this would be for the StakeState to delegate to a pool of
validators instead of a single one.
Instead of a single `vote_pubkey` and `credits_observed` entry in the StakeState
Instead of a single `vote_id` and `credits_observed` entry in the StakeState
program, the program can be initialized with a vector of tuples.
```rust,ignore
Voter {
voter_pubkey: Pubkey,
voter_id: Pubkey,
credits_observed: u64,
weight: u8,
}

View File

@ -11,7 +11,7 @@ it on the dashboard.
## TPS
The leader node's banking stage maintains a count of transactions that it recorded.
The leader node's banking stage maintains a count of transactions that it processed.
The dashboard displays the count averaged over 2 second period in the TPS time series
graph. The dashboard also shows per second mean, maximum and total TPS as a running
counter.
@ -26,4 +26,4 @@ super majority vote, and when one of its children forks is frozen.
The node assigns a timestamp to every new fork, and computes the time it took to confirm
the fork. This time is reflected as validator confirmation time in performance metrics.
The performance dashboard displays the average of each validator node's confirmation time
as a time series graph.
as a time series graph.

View File

@ -1,51 +0,0 @@
# Repair Service
The RepairService is in charge of retrieving missing blobs that failed to be delivered by primary communication protocols like Avalanche. It is in charge of managing the protocols described below in the `Repair Protocols` section below.
# Challenges:
1) Validators can fail to receive particular blobs due to network failures
2) Consider a scenario where blocktree contains the set of slots {1, 3, 5}. Then Blocktree receives blobs for some slot 7, where for each of the blobs b, b.parent == 6, so then the parent-child relation 6 -> 7 is stored in blocktree. However, there is no way to chain these slots to any of the existing banks in Blocktree, and thus the `Blob Repair` protocol will not repair these slots. If these slots happen to be part of the main chain, this will halt replay progress on this node.
3) Validators that find themselves behind the cluster by an entire epoch struggle/fail to catch up because they do not have a leader schedule for future epochs. If nodes were to blindly accept repair blobs in these future epochs, this exposes nodes to spam.
# Repair Protocols
The repair protocol makes best attempts to progress the forking structure of Blocktree.
The different protocol strategies to address the above challenges:
1. Blob Repair (Addresses Challenge #1):
This is the most basic repair protocol, with the purpose of detecting and filling "holes" in the ledger. Blocktree tracks the latest root slot. RepairService will then periodically iterate every fork in blocktree starting from the root slot, sending repair requests to validators for any missing blobs. It will send at most some `N` repair reqeusts per iteration.
Note: Validators will only accept blobs within the current verifiable epoch (epoch the validator has a leader schedule for).
2. Preemptive Slot Repair (Addresses Challenge #2):
The goal of this protocol is to discover the chaining relationship of "orphan" slots that do not currently chain to any known fork.
* Blocktree will track the set of "orphan" slots in a separate column family.
* RepairService will periodically make `RequestOrphan` requests for each of the orphans in blocktree.
`RequestOrphan(orphan)` request - `orphan` is the orphan slot that the requestor wants to know the parents of
`RequestOrphan(orphan)` response - The highest blobs for each of the first `N` parents of the requested `orphan`
On receiving the responses `p`, where `p` is some blob in a parent slot, validators will:
* Insert an empty `SlotMeta` in blocktree for `p.slot` if it doesn't already exist.
* If `p.slot` does exist, update the parent of `p` based on `parents`
Note: that once these empty slots are added to blocktree, the `Blob Repair` protocol should attempt to fill those slots.
Note: Validators will only accept responses containing blobs within the current verifiable epoch (epoch the validator has a leader schedule for).
3. Repairmen (Addresses Challenge #3):
This part of the repair protocol is the primary mechanism by which new nodes joining the cluster catch up after loading a snapshot. This protocol works in a "forward" fashion, so validators can verify every blob that they receive against a known leader schedule.
Each validator advertises in gossip:
* Current root
* The set of all completed slots in the confirmed epochs (an epoch that was calculated based on a bank <= current root) past the current root
Observers of this gossip message with higher epochs (repairmen) send blobs to catch the lagging node up with the rest of the cluster. The repairmen are responsible for sending the slots within the epochs that are confrimed by the advertised `root` in gossip. The repairmen divide the responsibility of sending each of the missing slots in these epochs based on a random seed (simple blob.index iteration by N, seeded with the repairman's node_pubkey). Ideally, each repairman in an N node cluster (N nodes whose epochs are higher than that of the repairee) sends 1/N of the missing blobs. Both data and coding blobs for missing slots are sent. Repairmen do not send blobs again to the same validator until they see the message in gossip updated, at which point they perform another iteration of this protocol.
Gossip messages are updated every time a validator receives a complete slot within the epoch. Completed slots are detected by blocktree and sent over a channel to RepairService. It is important to note that we know that by the time a slot X is complete, the epoch schedule must exist for the epoch that contains slot X because WindowService will reject blobs for unconfirmed epochs. When a newly completed slot is detected, we also update the current root if it has changed since the last update. The root is made available to RepairService through Blocktree, which holds the latest root.

View File

@ -1,195 +1,68 @@
# Stake Delegation and Rewards
Stakers are rewarded for helping to validate the ledger. They do this by
delegating their stake to validator nodes. Those validators do the legwork of
replaying the ledger and send votes to a per-node vote account to which stakers
can delegate their stakes. The rest of the cluster uses those stake-weighted
votes to select a block when forks arise. Both the validator and staker need
some economic incentive to play their part. The validator needs to be
compensated for its hardware and the staker needs to be compensated for the risk
of getting its stake slashed. The economics are covered in [staking
Stakers are rewarded for helping validate the ledger. They do it by delegating
their stake to fullnodes. Those fullnodes do the legwork and send votes to the
stakers' staking accounts. The rest of the cluster uses those stake-weighted
votes to select a block when forks arise. Both the fullnode and staker need
some economic incentive to play their part. The fullnode needs to be
compensated for its hardware and the staker needs to be compensated for risking
getting its stake slashed. The economics are covered in [staking
rewards](staking-rewards.md). This chapter, on the other hand, describes the
underlying mechanics of its implementation.
## Basic Besign
## Vote and Rewards accounts
The general idea is that the validator owns a Vote account. The Vote account
tracks validator votes, counts validator generated credits, and provides any
additional validator specific state. The Vote account is not aware of any
stakes delegated to it and has no staking weight.
The rewards process is split into two on-chain programs. The Vote program
solves the problem of making stakes slashable. The Rewards account acts as
custodian of the rewards pool. It is responsible for paying out each staker
once the staker proves to the Rewards program that it participated in
validating the ledger.
A separate Stake account (created by a staker) names a Vote account to which the
stake is delegated. Rewards generated are proportional to the amount of
lamports staked. The Stake account is owned by the staker only. Lamports
stored in this account are the stake.
The Vote account contains the following state information:
## Passive Delegation
* votes - The submitted votes.
Any number of Stake accounts can delegate to a single
Vote account without an interactive action from the identity controlling
the Vote account or submitting votes to the account.
* `delegate_id` - An identity that may operate with the weight of this
account's stake. It is typically the identity of a fullnode, but may be any
identity involved in stake-weighted computations.
The total stake allocated to a Vote account can be calculated by the sum of
all the Stake accounts that have the Vote account pubkey as the
`StakeState::Delegate::voter_pubkey`.
* `authorized_voter_id` - Only this identity is authorized to submit votes.
## Vote and Stake accounts
* `credits` - The amount of unclaimed rewards.
The rewards process is split into two on-chain programs. The Vote program solves
the problem of making stakes slashable. The Stake account acts as custodian of
the rewards pool, and provides passive delegation. The Stake program is
responsible for paying out each staker once the staker proves to the Stake
program that its delegate has participated in validating the ledger.
* `root_slot` - The last slot to reach the full lockout commitment necessary
for rewards.
### VoteState
The Rewards program is stateless and pays out reward when a staker submits its
Vote account to the program. Claiming a reward requires a transaction that
includes the following instructions:
VoteState is the current state of all the votes the validator has submitted to
the network. VoteState contains the following state information:
1. `RewardsInstruction::RedeemVoteCredits`
2. `VoteInstruction::ClearCredits`
* votes - The submitted votes data structure.
* credits - The total number of rewards this vote program has generated over its
lifetime.
* root\_slot - The last slot to reach the full lockout commitment necessary for
rewards.
* commission - The commission taken by this VoteState for any rewards claimed by
staker's Stake accounts. This is the percentage ceiling of the reward.
* Account::lamports - The accumulated lamports from the commission. These do not
count as stakes.
* `authorized_vote_signer` - Only this identity is authorized to submit votes. This field can only modified by this identity.
### VoteInstruction::Initialize
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is initialized to `account[0]`
other VoteState members defaulted
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
signed by Pubkey
### VoteInstruction::Vote(Vec<Vote>)
* `account[0]` - RW - The VoteState
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Fork Selection](fork-selection.md)
The Rewards program transfers lamports from the Rewards account to the Vote
account's public key. The Rewards program also ensures that the `ClearCredits`
instruction follows the `RedeemVoteCredits` instruction, such that a staker may
not claim rewards for the same work more than once.
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
### Delegating Stake
`VoteInstruction::DelegateStake` allows the staker to choose a fullnode to
validate the ledger on its behalf. By being a delegate, the fullnode is
entitled to collect transaction fees when its is leader. The larger the stake,
the more often the fullnode will be able to collect those fees.
### StakeState
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
### StakeState::Delegate
StakeState is the current delegation preference of the **staker**. StakeState
contains the following state information:
* Account::lamports - The staked lamports.
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the
program.
### StakeState::MiningPool
There are two approaches to the mining pool. The bank could allow the
StakeState program to bypass the token balance check, or a program representing
the mining pool could run on the network. To avoid a single network wide lock,
the pool can be split into several mining pools. This design focuses on using
StakeState::MiningPool instances as the cluster wide mining pools.
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
tokens stored as `Account::lamports`.
The stakes and the MiningPool are accounts that are owned by the same `Stake`
program.
### StakeInstruction::Initialize
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
### StakeInstruction::RedeemVoteCredits
The Staker or the owner of the Stake account sends a transaction with this
instruction to claim rewards.
The Vote account and the Stake account pair maintain a lifetime counter
of total rewards generated and claimed. When claiming rewards, the total lamports
deposited into the Stake account and as validator commission is proportional to
`VoteState::credits - StakeState::credits_observed`.
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
Reward is paid out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
`VoteState::credits`. The commission is deposited into the Vote account token
balance, and the reward is deposited to the Stake account token balance.
The total lamports paid is a percentage-rate of the lamports staked muiltplied by
the ratio of rewards being redeemed to rewards that could have been generated
during the rate period.
Any random MiningPool can be used to redeem the credits.
```rust,ignore
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
stake_state.credits_observed = vote_state.credits;
```
`credits_to_claim` is used to compute the reward and commission, and
`StakeState::Delegate::credits_observed` is updated to the latest
`VoteState::credits` value.
## Collecting network fees into the MiningPool
At the end of the block, before the bank is frozen, but after it processed all
the transactions for the block, a virtual instruction is executed to collect
the transaction fees.
* A portion of the fees are deposited into the leader's account.
* A portion of the fees are deposited into the smallest StakeState::MiningPool
account.
## Authorizing a Vote Signer
### Authorizing a Vote Signer
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
for its votes. That service is responsible for ensuring the vote won't cause
the staker to be slashed.
## Benefits of the design
## Limitations
* Single vote for all the stakers.
* Clearing of the credit variable is not necessary for claiming rewards.
* Each delegated stake can claim its rewards independently.
* Commission for the work is deposited when a reward is claimed by the delegated
stake.
This proposal would benefit from the `read-only` accounts proposal to allow for
many rewards to be claimed concurrently.
## Example Callflow
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
Many stakers may delegate their stakes to the same fullnode. The fullnode must
send a separate vote to each staking account. If there are far more stakers
than fullnodes, that's a lot of network traffic. An alternative design might
have fullnodes submit each vote to just one account and then have each staker
submit that account along with their own to collect its reward.

View File

@ -1,8 +1,8 @@
# Staking Rewards
A Proof of Stake (PoS), (i.e. using in-protocol asset, SOL, to provide
secure consensus) design is outlined here. Solana implements a proof of
stake reward/security scheme for validator nodes in the cluster. The purpose is
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide
secure consensus) design ideas outlined here. Solana will implement a proof of
stake reward/security scheme for node validators in the cluster. The purpose is
threefold:
- Align validator incentives with that of the greater cluster through
@ -48,7 +48,7 @@ specific parameters will be necessary:
Solana's trustless sense of time and ordering provided by its PoH data
structure, along with its
[turbine](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
[avalanche](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
and transmission design, should provide sub-second transaction confirmation times that scale
with the log of the number of nodes in the cluster. This means we shouldn't
have to restrict the number of validating nodes with a prohibitive 'minimum
@ -64,7 +64,7 @@ capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
We intend to implement slashing rules which, if broken, result some amount of
the offending validator's deposited stake to be removed from circulation. Given
the ordering properties of the PoH data structure, we believe we can simplify
our slashing rules to the level of a voting lockout time assigned per vote.
our slashing rules to the level of a voting lockout time assigned per vote.
I.e. Each vote has an associated lockout time (PoH duration) that represents a
duration by any additional vote from that validator must be in a PoH that
@ -110,7 +110,7 @@ in a slashable amount as a function of either:
1. the fraction of validators, out of the total validator pool, that were also
slashed during the same time period (ala Casper)
2. the amount of time since the vote was cast (e.g. a linearly increasing % of
total deposited as slashable amount over time), or both.
total deposited as slashable amount over time), or both.
This is an area currently under exploration

View File

@ -15,43 +15,39 @@ reasons:
* The cluster rolled back the ledger
* A validator responded to queries maliciously
### The AsyncClient and SyncClient Traits
### The Transact Trait
To troubleshoot, the application should retarget a lower-level component, where
fewer errors are possible. Retargeting can be done with different
implementations of the AsyncClient and SyncClient traits.
implementations of the Transact trait.
Components implement the following primary methods:
When Futures 0.3.0 is released, the Transact trait may look like this:
```rust,ignore
trait AsyncClient {
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>;
}
trait SyncClient {
fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>;
trait Transact {
async fn send_transactions(txs: &[Transaction]) -> Vec<Result<(), TransactionError>>;
}
```
Users send transactions and asynchrounously and synchrounously await results.
Users send transactions and asynchrounously await their results.
#### ThinClient for Clusters
#### Transact with Clusters
The highest level implementation, ThinClient, targets a Solana cluster, which
may be a deployed testnet or a local cluster running on a development machine.
The highest level implementation targets a Solana cluster, which may be a
deployed testnet or a local cluster running on a development machine.
#### TpuClient for the TPU
#### Transact with the TPU
The next level is the TPU implementation, which is not yet implemented. At the
TPU level, the application sends transactions over Rust channels, where there
can be no surprises from network queues or dropped packets. The TPU implements
all "normal" transaction errors. It does signature verification, may report
The next level is the TPU implementation of Transact. At the TPU level, the
application sends transactions over Rust channels, where there can be no
surprises from network queues or dropped packets. The TPU implements all
"normal" transaction errors. It does signature verification, may report
account-in-use errors, and otherwise results in the ledger, complete with proof
of history hashes.
### Low-level testing
#### BankClient for the Bank
### Testing with the Bank
Below the TPU level is the Bank. The Bank doesn't do signature verification or
generate a ledger. The Bank is a convenient layer at which to test new on-chain

View File

@ -16,10 +16,10 @@ The testnet is configured to reset the ledger daily, or sooner
should the hourly automated cluster sanity test fail.
There is a **#validator-support** Discord channel available to reach other
testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
testnet participants, https://discord.gg/pquxPsq.
Also we'd love it if you choose to register your validator node with us at
[https://forms.gle/LfFscZqJELbuUP139](https://forms.gle/LfFscZqJELbuUP139).
https://forms.gle/LfFscZqJELbuUP139.
### Machine Requirements
Since the testnet is not intended for stress testing of max transaction
@ -32,23 +32,6 @@ traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source.
For a performance testnet with many transactions we have some preliminary recommended setups:
| | Low end | Medium end | High end | Notes |
| --- | ---------|------------|----------| -- |
| CPU | AMD Threadripper 1900x | AMD Threadripper 2920x | AMD Threadripper 2950x | Consider a 10Gb-capable motherboard with as many PCIe lanes and m.2 slots as possible. |
| RAM | 16GB | 32GB | 64GB | |
| OS Drive | Samsung 860 Evo 2TB | Samsung 860 Evo 4TB | Samsung 860 Evo 4TB | Or equivalent SSD |
| Accounts Drive(s) | None | Samsung 970 Pro 1TB | 2x Samsung 970 Pro 1TB | |
| GPU | 4x Nvidia 1070 or 2x Nvidia 1080 Ti or 2x Nvidia 2070 | 2x Nvidia 2080 Ti | 4x Nvidia 2080 Ti | Any number of cuda-capable GPUs are supported on Linux platforms. |
#### GPU Requirements
CUDA is required to make use of the GPU on your system. The provided Solana
release binaries are built on Ubuntu 18.04 with <a
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
update 1"</a>. If your machine is using a different CUDA version then you will
need to rebuild from source.
#### Confirm The Testnet Is Reachable
Before attaching a validator node, sanity check that the cluster is accessible
to your machine by running some simple commands. If any of the commands fail,
@ -71,11 +54,11 @@ for more detail on cluster activity.
##### Bootstrap with `solana-install`
The `solana-install` tool can be used to easily install and upgrade the cluster
software on Linux x86_64 and mac OS systems.
software on Linux x86_64 systems.
```bash
$ export SOLANA_RELEASE=v0.16.0 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s
$ export SOLANA_RELEASE=v0.14.1 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.14.1/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
@ -85,12 +68,11 @@ $ solana-install init
```
After a successful install, `solana-install update` may be used to easily update the cluster
software to a newer version at any time.
software to a newer version.
##### Download Prebuilt Binaries
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
Binaries are available for Linux x86_64 systems.
###### Linux
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
@ -100,17 +82,6 @@ $ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
###### mac OS
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
##### Build From Source
If you are unable to use the prebuilt binaries or prefer to build it yourself
from source, navigate to
@ -122,29 +93,23 @@ $ ./scripts/cargo-install-all.sh .
$ export PATH=$PWD/bin:$PATH
```
If building for CUDA, include the `cuda` feature flag as well:
```bash
$ ./scripts/cargo-install-all.sh . cuda
$ export PATH=$PWD/bin:$PATH
```
### Starting The Validator
Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone:
```bash
$ solana-wallet airdrop 123
$ solana-wallet balance
$ solana-wallet -n testnet.solana.com airdrop 123
$ solana-wallet -n testnet.solana.com balance
```
Also try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --network testnet.solana.com:8001 spy
# Press ^C to exit
```
Now configure a key pair for your validator by running:
```bash
$ solana-keygen new -o ~/validator-keypair.json
$ solana-keygen -o fullnode-keypair.json
```
Then use one of the following commands, depending on your installation
@ -152,91 +117,73 @@ choice, to start the node:
If this is a `solana-install`-installation:
```bash
$ clear-config.sh
$ validator.sh --identity ~/validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ clear-fullnode-config.sh
$ fullnode.sh --identity fullnode-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
Alternatively, the `solana-install run` command can be used to run the validator
node while periodically checking for and applying software updates:
```bash
$ clear-config.sh
$ solana-install run validator.sh -- --identity ~/validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ clear-fullnode-config.sh
$ solana-install run fullnode.sh -- --identity fullnode-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
If you built from source:
```bash
$ USE_INSTALL=1 ./multinode-demo/clear-config.sh
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ USE_INSTALL=1 ./multinode-demo/clear-fullnode-config.sh
$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --identity fullnode-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
#### Enabling CUDA
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
define the SOLANA_CUDA flag in your environment *before* running any of the
previusly mentioned commands
```bash
$ export SOLANA_CUDA=1
```
When your validator is started look for the following log message to indicate that CUDA is enabled:
`"[<timestamp> solana::validator] CUDA is enabled"`
#### Controlling local network port allocation
By default the validator will dynamically select available network ports in the
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
example, `validator.sh --dynamic-port-range 11000-11010 ...` will restrict the
example, `fullnode.sh --dynamic-port-range 11000-11010 ...` will restrict the
validator to ports 11000-11011.
### Validator Monitoring
When `validator.sh` starts, it will output a validator configuration that looks
When `fullnode.sh` starts, it will output a fullnode configuration that looks
similar to:
```bash
======================[ validator configuration ]======================
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
======================[ Fullnode configuration ]======================
node pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
ledger: ...
accounts: ...
======================================================================
```
The **identity pubkey** for your validator can also be found by running:
The **node pubkey** for your validator can also be found by running:
```bash
$ solana-keygen pubkey ~/validator-keypair.json
$ solana-keygen pubkey fullnode-keypair.json
```
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
From another console, confirm the IP address of your validator is visible in the
gossip network by running:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --network testnet.solana.com:8001 spy
```
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
the recent voting activity from your validator:
```bash
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
The vote pubkey for the validator can also be found by running:
```bash
# If this is a `solana-install`-installation run:
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/validator-vote-keypair.json
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/fullnode-vote-keypair.json
# Otherwise run:
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
$ solana-keygen pubkey ./config-local/fullnode-vote-keypair.json
```
#### Validator Metrics
Metrics are available for local monitoring of your validator.
Docker must be installed and the current user added to the docker group. Then
download `solana-metrics.tar.bz2` from the Github Release and run
### Sharing Metrics From Your Validator
If you have obtained a metrics username/password from the Solana maintainers to
help us monitor the health of the testnet, please perform the following steps
before starting the validator node to activate metrics reporting:
```bash
$ tar jxf solana-metrics.tar.bz2
$ cd solana-metrics/
$ ./start.sh
export u="username obtained from the Solana maintainers"
export p="password obtained from the Solana maintainers"
export SOLANA_METRICS_CONFIG="db=testnet,u=${u:?},p=${p:?}"
source scripts/configure-metrics.sh
```
A local InfluxDB and Grafana instance is now running on your machine. Define
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
`start.sh` output and restart your validator.
Metrics should now be streaming and visible from your local Grafana dashboard.

View File

@ -1,154 +0,0 @@
## Testnet Replicator
This document describes how to setup a replicator in the testnet
Please note some of the information and instructions described here may change
in future releases.
### Overview
Replicators are specialized light clients. They download a part of the
ledger (a.k.a Segment) and store it. They earn rewards for storing segments.
The testnet features a validator running at testnet.solana.com, which
serves as the entrypoint to the cluster for your replicator node.
Additionally there is a blockexplorer available at
[http://testnet.solana.com/](http://testnet.solana.com/).
The testnet is configured to reset the ledger daily, or sooner
should the hourly automated cluster sanity test fail.
### Machine Requirements
Replicators don't need specialized hardware. Anything with more than
128GB of disk space will be able to participate in the cluster as a replicator node.
Currently the disk space requirements are very low but we expect them to change
in the future.
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended),
macOS, and Windows.
#### Confirm The Testnet Is Reachable
Before starting a replicator node, sanity check that the cluster is accessible
to your machine by running some simple commands. If any of the commands fail,
please retry 5-10 minutes later to confirm the testnet is not just restarting
itself before debugging further.
Fetch the current transaction count over JSON RPC:
```bash
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
```
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
View the [metrics dashboard](
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
for more detail on cluster activity.
### Replicator Setup
##### Obtaining The Software
##### Bootstrap with `solana-install`
The `solana-install` tool can be used to easily install and upgrade the cluster
software.
##### Linux and mac OS
```bash
$ export SOLANA_RELEASE=v0.16.0 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
following command to obtain the same result:
```bash
$ solana-install init
```
##### Windows
Download and install **solana-install-init** from
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest)
After a successful install, `solana-install update` may be used to
easily update the software to a newer version at any time.
##### Download Prebuilt Binaries
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
##### Linux
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
##### mac OS
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
##### Windows
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-pc-windows-msvc.tar.bz2**, then extract it into a folder.
It is a good idea to add this extracted folder to your windows PATH.
### Starting The Replicator
Try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
# Press ^C to exit
```
Now configure the keypairs for your replicator by running:
Navigate to the solana install location and open a cmd prompt
```bash
$ solana-keygen new -o replicator-keypair.json
$ solana-keygen new -o storage-keypair.json
```
Use solana-keygen to show the public keys for each of the keypairs,
they will be needed in the next step:
- Windows
```bash
# The replicator's identity
$ solana-keygen pubkey replicator-keypair.json
$ solana-keygen pubkey storage-keypair.json
```
- Linux and mac OS
```bash
$ export REPLICATOR_IDENTITY=$(solana-keygen pubkey replicator-keypair.json)
$ export STORAGE_IDENTITY=$(solana-keygen pubkey storage-keypair.json)
```
Then set up the storage accounts for your replicator by running:
```bash
$ solana-wallet --keypair replicator-keypair.json airdrop 100000
$ solana-wallet --keypair replicator-keypair.json create-replicator-storage-account $REPLICATOR_IDENTITY $STORAGE_IDENTITY
```
Note: Every time the testnet restarts, run the wallet steps to setup the replicator accounts again.
To start the replicator:
```bash
$ solana-replicator --entrypoint testnet.solana.com:8001 --identity replicator-keypair.json --storage-keypair storage-keypair.json --ledger replicator-ledger
```
### Verify Replicator Setup
From another console, confirm the IP address and **identity pubkey** of your replicator is visible in the
gossip network by running:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
Provide the **storage account pubkey** to the `solana-wallet show-storage-account` command to view
the recent mining activity from your replicator:
```bash
$ solana-wallet --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
```

View File

@ -8,14 +8,17 @@ client won't know how much was collected until the transaction is confirmed by
the cluster and the remaining balance is checked. It smells of exactly what we
dislike about Ethereum's "gas", non-determinism.
## Implementation Status
This design is not yet implemented, but is written as though it has been. Once
implemented, delete this comment.
### Congestion-driven fees
Each validator uses *signatures per slot* (SPS) to estimate network congestion
and *SPS target* to estimate the desired processing capacity of the cluster.
The validator learns the SPS target from the genesis block, whereas it
calculates SPS from recently processed transactions. The genesis block also
defines a target `lamports_per_signature`, which is the fee to charge per
signature when the cluster is operating at *SPS target*.
calculates SPS from the ledger data in the previous epoch.
### Calculating fees
@ -34,11 +37,8 @@ lamports as returned by the fee calculator.
In the first implementation of this design, the only fee parameter is
`lamports_per_signature`. The more signatures the cluster needs to verify, the
higher the fee. The exact number of lamports is determined by the ratio of SPS
to the SPS target. At the end of each slot, the cluster lowers
`lamports_per_signature` when SPS is below the target and raises it when above
the target. The minimum value for `lamports_per_signature` is 50% of the target
`lamports_per_signature` and the maximum value is 10x the target
`lamports_per_signature'
to the SPS target. The cluster lowers `lamports_per_signature` when SPS is
below the target and raises it when at or above the target.
Future parameters might include:

View File

@ -1,90 +0,0 @@
# Turbine Block Propagation
A Solana cluster uses a multi-layer block propagation mechanism called *Turbine*
to broadcast transaction blobs to all nodes with minimal amount of duplicate
messages. The cluster divides itself into small collections of nodes, called
*neighborhoods*. Each node is responsible for sharing any data it receives with
the other nodes in its neighborhood, as well as propagating the data on to a
small set of nodes in other neighborhoods. This way each node only has to
communicate with a small number of nodes.
During its slot, the leader node distributes blobs between the validator nodes
in the first neighborhood (layer 0). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in some neighborhoods
in the next layer (layer 1). The layer-1 nodes each share their data with their
neighborhood peers, and retransmit to nodes in the next layer, etc, until all
nodes in the cluster have received all the blobs.
## Neighborhood Assignment - Weighted Selection
In order for data plane fanout to work, the entire cluster must agree on how the
cluster is divided into neighborhoods. To achieve this, all the recognized
validator nodes (the TVU peers) are sorted by stake and stored in a list. This
list is then indexed in different ways to figure out neighborhood boundaries and
retransmit peers. For example, the leader will simply select the first nodes to
make up layer 0. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-0 and lower-layer
nodes use the same logic to find their neighbors and next layer peers.
To reduce the possibility of attack vectors, each blob is transmitted over a
random tree of neighborhoods. Each node uses the same set of nodes representing
the cluster. A random tree is generated from the set for each blob using
randomness derived from the blob itself. Since the random seed is not known in
advance, attacks that try to eclipse neighborhoods from certain leaders or
blocks become very difficult, and should require almost complete control of the
stake in the cluster.
## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
nodes. If this layer 0 is smaller than the number of nodes in the cluster, then
the data plane fanout mechanism adds layers below. Subsequent layers follow
these constraints to determine layer-capacity: Each neighborhood contains
`DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes.
The number of nodes in each additional layer grows by a factor of fanout.
As mentioned above, each node in a layer only has to broadcast its blobs to its
neighbors and to exactly 1 node in some next-layer neighborhoods,
instead of to every TVU peer in the cluster. A good way to think about this is,
layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout"
neighborhoods, each with fanout nodes and layer-2 will have
`fanout * number of nodes in layer-1` and so on.
This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
The following diagram shows how the Leader sends blobs with a Fanout of 2 to
Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data
with each other.
<img alt="Leader sends blobs to Neighborhood 0 in Layer 0" src="img/data-plane-seeding.svg" class="center"/>
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
<img alt="Neighborhood 0 Fanout to Neighborhood 1 and 2" src="img/data-plane-fanout.svg" class="center"/>
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
<img alt="Two layer cluster with a Fanout of 2" src="img/data-plane.svg" class="center"/>
#### Configuration Values
`DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent
layers grow by a factor of `DATA_PLANE_FANOUT`.
The number of nodes in a neighborhood is equal to the fanout value.
Neighborhoods will fill to capacity before new ones are added, i.e if a
neighborhood isn't full, it _must_ be the last one.
Currently, configuration is set when the cluster is launched. In the future,
these parameters may be hosted on-chain, allowing modification on the fly as the
cluster sizes change.
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact.
To cripple a neighborhood, enough nodes (erasure codes +1) from the neighborhood
above need to fail. Since each neighborhood receives blobs from multiple nodes
in a neighborhood in the upper layer, we'd need a big network failure in the upper
layers to end up with incomplete data.
<img alt="Inner workings of a neighborhood"
src="img/data-plane-neighborhood.svg" class="center"/>

View File

@ -1,56 +0,0 @@
# Anatomy of a Validator
## History
When we first started Solana, the goal was to de-risk our TPS claims. We knew
that between optimistic concurrency control and sufficiently long leader slots,
that PoS consensus was not the biggest risk to TPS. It was GPU-based signature
verification, software pipelining and concurrent banking. Thus, the TPU was
born. After topping 100k TPS, we split the team into one group working toward
710k TPS and another to flesh out the validator pipeline. Hence, the TVU was
born. The current architecture is a consequence of incremental development with
that ordering and project priorities. It is not a reflection of what we ever
believed was the most technically elegant cross-section of those technologies.
In the context of leader rotation, the strong distinction between leading and
validating is blurred.
## Difference between validating and leading
The fundamental difference between the pipelines is when the PoH is present. In
a leader, we process transactions, removing bad ones, and then tag the result
with a PoH hash. In the validator, we verify that hash, peel it off, and
process the transactions in exactly the same way. The only difference is that
if a validator sees a bad transaction, it can't simply remove it like the
leader does, because that would cause the PoH hash to change. Instead, it
rejects the whole block. The other difference between the pipelines is what
happens *after* banking. The leader broadcasts entries to downstream validators
whereas the validator will have already done that in RetransmitStage, which is
a confirmation time optimization. The validation pipeline, on the other hand,
has one last step. Any time it finishes processing a block, it needs to weigh
any forks it's observing, possibly cast a vote, and if so, reset its PoH hash
to the block hash it just voted on.
## Proposed Design
We unwrap the many abstraction layers and build a single pipeline that can
toggle leader mode on whenever the validator's ID shows up in the leader
schedule.
<img alt="Validator block diagram" src="img/validator-proposal.svg" class="center"/>
## Notable changes
* No threads are shut down to switch out of leader mode. Instead, FetchStage
should forward transactions to the next leader.
* Hoist FetchStage and BroadcastStage out of TPU
* Blocktree renamed to Blockstore
* BankForks renamed to Banktree
* TPU moves to new socket-free crate called solana-tpu.
* TPU's BankingStage absorbs ReplayStage
* TVU goes away
* New RepairStage absorbs Blob Fetch Stage and repair requests
* JSON RPC Service is optional - used for debugging. It should instead be part
of a separate `solana-blockstreamer` executable.
* New MulticastStage absorbs retransmit part of RetransmitStage
* MulticastStage downstream of Blockstore

View File

@ -284,18 +284,6 @@ ARGS:
<PATH> /path/to/program.o
```
```manpage
solana-wallet-fees
Display current cluster fees
USAGE:
solana-wallet fees
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
```
```manpage
solana-wallet-get-transaction-count
Get current transaction count

View File

@ -521,4 +521,4 @@ ul#searchresults span.teaser em {
}
.content pre {
padding: 0 28px;
}
}

View File

@ -152,4 +152,4 @@ blockquote {
*:active,
*:hover {
outline: none;
}
}

File diff suppressed because one or more lines are too long

19
build-perf-libs.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
#
# Builds perf-libs from the upstream source and installs them into the correct
# location in the tree
#
set -e
cd "$(dirname "$0")"
if [[ -d target/perf-libs ]]; then
echo "target/perf-libs/ already exists, to continue run:"
echo "$ rm -rf target/perf-libs"
exit 1
fi
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install

View File

@ -1 +0,0 @@
/target/

View File

@ -1,12 +0,0 @@
[package]
name = "solana-chacha-sys"
version = "0.16.2"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
license = "Apache-2.0"
edition = "2018"
[build-dependencies]
cc = "1.0.37"

View File

@ -1,8 +0,0 @@
extern crate cc;
fn main() {
cc::Build::new()
.file("cpu-crypt/chacha20_core.c")
.file("cpu-crypt/chacha_cbc.c")
.compile("libcpu-crypt");
}

View File

@ -1 +0,0 @@
release/

View File

@ -1,25 +0,0 @@
V:=debug
LIB:=cpu-crypt
CFLAGS_common:=-Wall -Werror -pedantic -fPIC
CFLAGS_release:=-march=native -O3 $(CFLAGS_common)
CFLAGS_debug:=-g $(CFLAGS_common)
CFLAGS:=$(CFLAGS_$V)
all: $V/lib$(LIB).a
$V/chacha20_core.o: chacha20_core.c chacha.h
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$V/chacha_cbc.o: chacha_cbc.c chacha.h
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$V/lib$(LIB).a: $V/chacha20_core.o $V/chacha_cbc.o
$(AR) rcs $@ $^
.PHONY:clean
clean:
rm -rf $V

View File

@ -1,35 +0,0 @@
#ifndef HEADER_CHACHA_H
# define HEADER_CHACHA_H
#include <string.h>
#include <inttypes.h>
# include <stddef.h>
# ifdef __cplusplus
extern "C" {
# endif
typedef unsigned int u32;
#define CHACHA_KEY_SIZE 32
#define CHACHA_NONCE_SIZE 12
#define CHACHA_BLOCK_SIZE 64
#define CHACHA_ROUNDS 500
void chacha20_encrypt(const u32 input[16],
unsigned char output[64],
int num_rounds);
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE], const uint8_t nonce[CHACHA_NONCE_SIZE],
uint32_t counter);
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
uint32_t len, const uint8_t* key,
unsigned char* ivec);
# ifdef __cplusplus
}
# endif
#endif

View File

@ -1,102 +0,0 @@
#include "chacha.h"
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
#define ROTATE(v, c) ROTL32((v), (c))
#define XOR(v, w) ((v) ^ (w))
#define PLUS(x, y) ((x) + (y))
#define U32TO8_LITTLE(p, v) \
{ (p)[0] = ((v) ) & 0xff; (p)[1] = ((v) >> 8) & 0xff; \
(p)[2] = ((v) >> 16) & 0xff; (p)[3] = ((v) >> 24) & 0xff; }
#define U8TO32_LITTLE(p) \
(((u32)((p)[0]) ) | ((u32)((p)[1]) << 8) | \
((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24) )
#define QUARTERROUND(a,b,c,d) \
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]),16); \
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]),12); \
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]), 8); \
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]), 7);
// sigma contains the ChaCha constants, which happen to be an ASCII string.
static const uint8_t sigma[16] = { 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
'2', '-', 'b', 'y', 't', 'e', ' ', 'k' };
void chacha20_encrypt(const u32 input[16],
unsigned char output[64],
int num_rounds)
{
u32 x[16];
int i;
memcpy(x, input, sizeof(u32) * 16);
for (i = num_rounds; i > 0; i -= 2) {
QUARTERROUND( 0, 4, 8,12)
QUARTERROUND( 1, 5, 9,13)
QUARTERROUND( 2, 6,10,14)
QUARTERROUND( 3, 7,11,15)
QUARTERROUND( 0, 5,10,15)
QUARTERROUND( 1, 6,11,12)
QUARTERROUND( 2, 7, 8,13)
QUARTERROUND( 3, 4, 9,14)
}
for (i = 0; i < 16; ++i) {
x[i] = PLUS(x[i], input[i]);
}
for (i = 0; i < 16; ++i) {
U32TO8_LITTLE(output + 4 * i, x[i]);
}
}
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE],
const uint8_t nonce[CHACHA_NONCE_SIZE],
uint32_t counter)
{
uint32_t input[16];
uint8_t buf[64];
size_t todo, i;
input[0] = U8TO32_LITTLE(sigma + 0);
input[1] = U8TO32_LITTLE(sigma + 4);
input[2] = U8TO32_LITTLE(sigma + 8);
input[3] = U8TO32_LITTLE(sigma + 12);
input[4] = U8TO32_LITTLE(key + 0);
input[5] = U8TO32_LITTLE(key + 4);
input[6] = U8TO32_LITTLE(key + 8);
input[7] = U8TO32_LITTLE(key + 12);
input[8] = U8TO32_LITTLE(key + 16);
input[9] = U8TO32_LITTLE(key + 20);
input[10] = U8TO32_LITTLE(key + 24);
input[11] = U8TO32_LITTLE(key + 28);
input[12] = counter;
input[13] = U8TO32_LITTLE(nonce + 0);
input[14] = U8TO32_LITTLE(nonce + 4);
input[15] = U8TO32_LITTLE(nonce + 8);
while (in_len > 0) {
todo = sizeof(buf);
if (in_len < todo) {
todo = in_len;
}
chacha20_encrypt(input, buf, 20);
for (i = 0; i < todo; i++) {
out[i] = in[i] ^ buf[i];
}
out += todo;
in += todo;
in_len -= todo;
input[12]++;
}
}

View File

@ -1,72 +0,0 @@
#include "chacha.h"
#if !defined(STRICT_ALIGNMENT) && !defined(PEDANTIC)
# define STRICT_ALIGNMENT 0
#endif
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
uint32_t len, const uint8_t* key,
unsigned char* ivec)
{
size_t n;
unsigned char *iv = ivec;
(void)key;
if (len == 0) {
return;
}
#if !defined(OPENSSL_SMALL_FOOTPRINT)
if (STRICT_ALIGNMENT &&
((size_t)in | (size_t)out | (size_t)ivec) % sizeof(size_t) != 0) {
while (len >= CHACHA_BLOCK_SIZE) {
for (n = 0; n < CHACHA_BLOCK_SIZE; ++n) {
out[n] = in[n] ^ iv[n];
//printf("%x ", out[n]);
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
} else {
while (len >= CHACHA_BLOCK_SIZE) {
for (n = 0; n < CHACHA_BLOCK_SIZE; n += sizeof(size_t)) {
*(size_t *)(out + n) =
*(size_t *)(in + n) ^ *(size_t *)(iv + n);
//printf("%zu ", *(size_t *)(iv + n));
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
}
#endif
while (len) {
for (n = 0; n < CHACHA_BLOCK_SIZE && n < len; ++n) {
out[n] = in[n] ^ iv[n];
}
for (; n < CHACHA_BLOCK_SIZE; ++n) {
out[n] = iv[n];
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
if (len <= CHACHA_BLOCK_SIZE) {
break;
}
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
memcpy(ivec, iv, CHACHA_BLOCK_SIZE);
}
void chacha20_cbc_encrypt(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE], uint8_t* ivec)
{
chacha20_cbc128_encrypt(in, out, in_len, key, ivec);
}

View File

@ -1,21 +0,0 @@
extern "C" {
fn chacha20_cbc_encrypt(
input: *const u8,
output: *mut u8,
in_len: usize,
key: *const u8,
ivec: *mut u8,
);
}
pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) {
unsafe {
chacha20_cbc_encrypt(
input.as_ptr(),
output.as_mut_ptr(),
input.len(),
key.as_ptr(),
ivec.as_mut_ptr(),
);
}
}

View File

@ -12,7 +12,7 @@
set -e
cd "$(dirname "$0")"/..
if [[ -n $CI_PULL_REQUEST ]]; then
if ci/is-pr.sh; then
affectedFiles="$(buildkite-agent meta-data get affected_files)"
echo "Affected files in this PR: $affectedFiles"

20
ci/audit.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
#
# Audits project dependencies for security vulnerabilities
#
set -e
cd "$(dirname "$0")/.."
source ci/_
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
_ cargo install "$crate"
}
cargo_install_unless cargo-audit cargo audit --version
_ cargo audit

View File

@ -2,26 +2,26 @@ steps:
- command: "ci/shellcheck.sh"
name: "shellcheck"
timeout_in_minutes: 5
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-checks.sh"
name: "checks"
timeout_in_minutes: 15
- wait
- command: "ci/test-stable-perf.sh"
name: "stable-perf"
timeout_in_minutes: 30
timeout_in_minutes: 20
artifact_paths: "log-*.txt"
agents:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
timeout_in_minutes: 60
timeout_in_minutes: 20
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 40
timeout_in_minutes: 30
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"
timeout_in_minutes: 40
timeout_in_minutes: 20
# TODO: Fix and re-enable test-large-network.sh
# - command: "ci/test-large-network.sh || true"
# name: "large-network [ignored]"

View File

@ -89,11 +89,11 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag}
STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag}
if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
CHANNEL=stable
elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
CHANNEL=edge
elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
CHANNEL=beta
fi

View File

@ -64,14 +64,11 @@ fi
ARGS+=(
--env BUILDKITE
--env BUILDKITE_AGENT_ACCESS_TOKEN
--env BUILDKITE_BRANCH
--env BUILDKITE_COMMIT
--env BUILDKITE_JOB_ID
--env BUILDKITE_TAG
--env CI
--env CI_BRANCH
--env CI_BUILD_ID
--env CI_COMMIT
--env CI_JOB_ID
--env CI_PULL_REQUEST
--env CI_REPO_SLUG
--env CODECOV_TOKEN
--env CRATES_IO_TOKEN
)

View File

@ -3,10 +3,10 @@ ARG date
RUN set -x \
&& rustup install nightly-$date \
&& rustup component add clippy --toolchain=nightly-$date \
&& rustup show \
&& rustup show \
&& rustc --version \
&& cargo --version \
&& cargo install grcov \
&& rustc +nightly-$date --version \
&& cargo +nightly-$date --version

View File

@ -15,12 +15,12 @@ To update the pinned version:
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
specific YYYY-MM-DD that is desired (default is today's build).
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
to confirm the new nightly image builds. Fix any issues as needed
1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized.
1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com.
1. Send a PR with the `ci/rust-version.sh` change and any codebase adjustments needed.
1. Modify the `solanalabs/rust-nightly:YYYY-MM-DD` reference in `ci/rust-version.sh` from the previous to
new *YYYY-MM-DD* value, send a PR with this change and any codebase adjustments needed.
## Troubleshooting

View File

@ -1,6 +1,6 @@
# Note: when the rust version is changed also modify
# ci/rust-version.sh to pick up the new image tag
FROM rust:1.35.0
# ci/buildkite.yml to pick up the new image tag
FROM rust:1.34.0
RUN set -x \
&& apt update \
@ -21,11 +21,9 @@ RUN set -x \
rsync \
sudo \
\
&& rm -rf /var/lib/apt/lists/* \
&& rustup component add rustfmt \
&& rustup component add clippy \
&& cargo install cargo-audit \
&& cargo install svgbob_cli \
&& cargo install mdbook \
&& rm -rf /var/lib/apt/lists/* \
&& rustc --version \
&& cargo --version

View File

@ -1,7 +1,6 @@
Docker image containing rust and some preinstalled packages used in CI.
This image manually maintained:
1. Edit `Dockerfile` to match the desired rust version
2. Run `./build.sh` to publish the new image, if you are a member of the [Solana
Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization.
This image may be manually updated by running `./build.sh` if you are a member
of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub
organization, but it is also automatically updated periodically by
[this automation](https://buildkite.com/solana-labs/solana-ci-docker-rust).

View File

@ -9,4 +9,3 @@ read -r rustc version _ < <(docker run solanalabs/rust rustc --version)
[[ $rustc = rustc ]]
docker tag solanalabs/rust:latest solanalabs/rust:"$version"
docker push solanalabs/rust:"$version"
docker push solanalabs/rust:latest

View File

@ -1,89 +0,0 @@
#
# Normalized CI environment variables
#
# |source| me
#
if [[ -n $CI ]]; then
export CI=1
if [[ -n $TRAVIS ]]; then
export CI_BRANCH=$TRAVIS_BRANCH
export CI_BUILD_ID=$TRAVIS_BUILD_ID
export CI_COMMIT=$TRAVIS_COMMIT
export CI_JOB_ID=$TRAVIS_JOB_ID
if $TRAVIS_PULL_REQUEST; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
export CI_OS_NAME=$TRAVIS_OS_NAME
export CI_REPO_SLUG=$TRAVIS_REPO_SLUG
export CI_TAG=$TRAVIS_TAG
elif [[ -n $BUILDKITE ]]; then
export CI_BRANCH=$BUILDKITE_BRANCH
export CI_BUILD_ID=$BUILDKITE_BUILD_ID
export CI_COMMIT=$BUILDKITE_COMMIT
export CI_JOB_ID=$BUILDKITE_JOB_ID
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
# to how solana-ci-gate is used to trigger PR builds rather than using the
# standard Buildkite PR trigger.
if [[ $CI_BRANCH =~ pull/* ]]; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
export CI_OS_NAME=linux
if [[ -n $BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG ]]; then
# The solana-secondary pipeline should use the slug of the pipeline that
# triggered it
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG
else
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG
fi
# TRIGGERED_BUILDKITE_TAG is a workaround to propagate BUILDKITE_TAG into
# the solana-secondary pipeline
if [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
export CI_TAG=$TRIGGERED_BUILDKITE_TAG
else
export CI_TAG=$BUILDKITE_TAG
fi
elif [[ -n $APPVEYOR ]]; then
export CI_BRANCH=$APPVEYOR_REPO_BRANCH
export CI_BUILD_ID=$APPVEYOR_BUILD_ID
export CI_COMMIT=$APPVEYOR_REPO_COMMIT
export CI_JOB_ID=$APPVEYOR_JOB_ID
if [[ -n $APPVEYOR_PULL_REQUEST_NUMBER ]]; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
if [[ $CI_LINUX = True ]]; then
export CI_OS_NAME=linux
elif [[ $CI_WINDOWS = True ]]; then
export CI_OS_NAME=windows
fi
export CI_REPO_SLUG=$APPVEYOR_REPO_NAME
export CI_TAG=$APPVEYOR_REPO_TAG_NAME
fi
else
export CI=
export CI_BRANCH=
export CI_BUILD_ID=
export CI_COMMIT=
export CI_JOB_ID=
export CI_OS_NAME=
export CI_PULL_REQUEST=
export CI_REPO_SLUG=
export CI_TAG=
fi
cat <<EOF
CI=$CI
CI_BRANCH=$CI_BRANCH
CI_BUILD_ID=$CI_BUILD_ID
CI_COMMIT=$CI_COMMIT
CI_JOB_ID=$CI_JOB_ID
CI_OS_NAME=$CI_OS_NAME
CI_PULL_REQUEST=$CI_PULL_REQUEST
CI_TAG=$CI_TAG
EOF

9
ci/is-pr.sh Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
#
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
# to how solana-ci-gate is used to trigger PR builds rather than using the
# standard Buildkite PR trigger.
#
[[ $BUILDKITE_BRANCH =~ pull/* ]]

View File

@ -25,7 +25,8 @@ fi
build() {
$genPipeline && return
source ci/rust-version.sh stable
source scripts/ulimit-n.sh
_ scripts/ulimit-n.sh
_ cargo +$rust_stable build --all
}
@ -51,11 +52,11 @@ runTest() {
build
runTest "basic" \
runTest "Leader rotation on" \
"ci/localnet-sanity.sh -i 128"
runTest "restart" \
runTest "Leader rotation on, restart" \
"ci/localnet-sanity.sh -i 128 -k 16"
runTest "incremental restart, extra node" \
runTest "Leader rotation on, incremental restart, extra node" \
"ci/localnet-sanity.sh -i 128 -k 16 -R -x"

View File

@ -7,7 +7,7 @@ restartInterval=never
rollingRestart=false
maybeNoLeaderRotation=
extraNodes=0
walletRpcPort=:8899
walletRpcEndpoint=
usage() {
exitcode=0
@ -27,7 +27,7 @@ Start a local cluster and run sanity on it
nodes (at the cadence specified by -k). When disabled all
nodes will be first killed then restarted (default: $rollingRestart)
-b - Disable leader rotation
-x - Add an extra validator (may be supplied multiple times)
-x - Add an extra fullnode (may be supplied multiple times)
-r - Select the RPC endpoint hosted by a node that starts as
a validator node. If unspecified the RPC endpoint hosted by
the bootstrap leader will be used.
@ -61,7 +61,7 @@ while getopts "ch?i:k:brxR" opt; do
extraNodes=$((extraNodes + 1))
;;
r)
walletRpcPort=":18899"
walletRpcEndpoint="--rpc-port 18899"
;;
R)
rollingRestart=true
@ -79,20 +79,17 @@ nodes=(
"multinode-demo/drone.sh"
"multinode-demo/bootstrap-leader.sh \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node1.log"
"multinode-demo/validator.sh \
"multinode-demo/fullnode.sh \
$maybeNoLeaderRotation \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node2.log \
--rpc-port 18899"
)
for i in $(seq 1 $extraNodes); do
nodes+=(
"multinode-demo/validator.sh \
--no-restart \
"multinode-demo/fullnode.sh \
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log \
$maybeNoLeaderRotation"
@ -169,11 +166,13 @@ startNodes() {
killNode() {
declare pid=$1
echo "kill $pid"
set +e
if kill "$pid"; then
echo "Waiting for $pid to exit..."
wait "$pid"
echo "$pid exited with $?"
else
echo "^^^ +++"
echo "Warning: unable to kill $pid"
fi
set -e
}
@ -197,11 +196,10 @@ killNodes() {
# Give the nodes a splash of time to cleanly exit before killing them
sleep 2
echo "--- Killing nodes: ${pids[*]}"
echo "--- Killing nodes"
for pid in "${pids[@]}"; do
killNode "$pid"
done
echo "done killing nodes"
pids=()
}
@ -256,7 +254,7 @@ rollingNodeRestart() {
}
verifyLedger() {
for ledger in bootstrap-leader validator; do
for ledger in bootstrap-leader fullnode; do
echo "--- $ledger ledger verification"
(
source multinode-demo/common.sh
@ -306,10 +304,10 @@ while [[ $iteration -le $iterations ]]; do
(
source multinode-demo/common.sh
set -x
client_keypair=/tmp/client-id.json-$$
$solana_keygen new -f -o $client_keypair || exit $?
client_id=/tmp/client-id.json-$$
$solana_keygen -o $client_id || exit $?
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
rm -rf $client_keypair
rm -rf $client_id
) || flag_error
echo "--- RPC API: bootstrap-leader getTransactionCount ($iteration)"
@ -323,7 +321,7 @@ while [[ $iteration -le $iterations ]]; do
cat log-transactionCount.txt
) || flag_error
echo "--- RPC API: validator getTransactionCount ($iteration)"
echo "--- RPC API: fullnode getTransactionCount ($iteration)"
(
set -x
curl --retry 5 --retry-delay 2 --retry-connrefused \
@ -364,7 +362,8 @@ while [[ $iteration -le $iterations ]]; do
}
(
set -x
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
# shellcheck disable=SC2086 # Don't want to double quote $walletRpcEndpoint
timeout 60s scripts/wallet-sanity.sh $walletRpcEndpoint
) || flag_error_if_no_leader_rotation
iteration=$((iteration + 1))

View File

@ -13,7 +13,6 @@ declare prints=(
'println!'
'eprint!'
'eprintln!'
'dbg!'
)
# Parts of the tree that are expected to be print free
@ -23,16 +22,10 @@ declare print_free_tree=(
'metrics/src'
'netutil/src'
'runtime/src'
'sdk/bpf/rust/rust-utils'
'sdk/src'
'programs/bpf/rust'
'programs/stake_api/src'
'programs/stake_program/src'
'programs/vote_api/src'
'programs/vote_program/src'
)
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
if _ git grep --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
exit 1
fi
@ -41,21 +34,7 @@ fi
# Default::default()
#
# Ref: https://github.com/solana-labs/solana/issues/2630
if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
if _ git grep 'Default::default()' -- '*.rs'; then
exit 1
fi
# Let's keep a .gitignore for every crate, ensure it's got
# /target/ in it
declare gitignores_ok=true
for i in $(git --no-pager ls-files \*/Cargo.toml ); do
dir=$(dirname "$i")
if [[ ! -f $dir/.gitignore ]]; then
echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2
gitignores_ok=false
elif ! grep -q -e '^/target/$' "$dir"/.gitignore; then
echo 'error: nits.sh "/target/" apparently missing from '"$dir"'/.gitignore' >&2
gitignores_ok=false
fi
done
"$gitignores_ok"

View File

@ -1,65 +0,0 @@
#!/usr/bin/env python
#
# This script figures the order in which workspace crates must be published to
# crates.io. Along the way it also ensures there are no circular dependencies
# that would cause a |cargo publish| to fail.
#
# On success an ordered list of Cargo.toml files is written to stdout
#
import os
import json
import subprocess
import sys;
def load_metadata():
return json.loads(subprocess.Popen(
'cargo metadata --no-deps --format-version=1',
shell=True, stdout=subprocess.PIPE).communicate()[0])
def get_packages():
metadata = load_metadata()
manifest_path = dict()
# Build dictionary of packages and their immediate solana-only dependencies
dependency_graph = dict()
for pkg in metadata['packages']:
manifest_path[pkg['name']] = pkg['manifest_path'];
dependency_graph[pkg['name']] = [x['name'] for x in pkg['dependencies'] if x['name'].startswith('solana')];
# Check for direct circular dependencies
circular_dependencies = set()
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph and package in dependency_graph[dependency]:
circular_dependencies.add(' <--> '.join(sorted([package, dependency])))
for dependency in circular_dependencies:
sys.stderr.write('Error: Circular dependency: {}\n'.format(dependency))
if len(circular_dependencies) != 0:
sys.exit(1)
# Order dependencies
sorted_dependency_graph = []
max_iterations = pow(len(dependency_graph),2)
while dependency_graph:
if max_iterations == 0:
# TODO: Be more helpful and find the actual cycle for the user
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format(' '.join(dependency_graph.keys())))
max_iterations -= 1
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph:
break
else:
del dependency_graph[package]
sorted_dependency_graph.append((package, manifest_path[package]))
return sorted_dependency_graph
for package, manifest in get_packages():
print os.path.relpath(manifest)

View File

@ -13,7 +13,7 @@ echo --- create book repo
git config user.email "maintainers@solana.com"
git config user.name "$(basename "$0")"
git add ./* ./.nojekyll
git commit -m "${CI_COMMIT:-local}"
git commit -m "${BUILDKITE_COMMIT:-local}"
)
eval "$(ci/channel-info.sh)"

View File

@ -3,21 +3,43 @@ set -e
cd "$(dirname "$0")/.."
source ci/semver_bash/semver.sh
# shellcheck disable=SC2086
is_crate_version_uploaded() {
name=$1
version=$2
curl https://crates.io/api/v1/crates/${name}/${version} | \
python3 -c "import sys,json; print('version' in json.load(sys.stdin));"
}
# List of internal crates to publish
#
# IMPORTANT: the order of the CRATES *is* significant. Crates must be published
# before the crates that depend on them. Note that this information is already
# expressed in the various Cargo.toml files, and ideally would not be duplicated
# here. (TODO: figure the crate ordering dynamically)
#
CRATES=(
kvstore
logger
netutil
sdk
keygen
metrics
client
drone
programs/{budget_api,config_api,stake_api,storage_api,token_api,vote_api,exchange_api}
programs/{vote_program,budget_program,bpf_loader,config_program,exchange_program,failure_program}
programs/{noop_program,stake_program,storage_program,token_program}
runtime
vote-signer
core
fullnode
genesis
gossip
ledger-tool
wallet
install
)
# Only package/publish if this is a tagged release
[[ -n $CI_TAG ]] || {
echo CI_TAG unset, skipped
[[ -n $TRIGGERED_BUILDKITE_TAG ]] || {
echo TRIGGERED_BUILDKITE_TAG unset, skipped
exit 0
}
semverParseInto "$CI_TAG" MAJOR MINOR PATCH SPECIAL
semverParseInto "$TRIGGERED_BUILDKITE_TAG" MAJOR MINOR PATCH SPECIAL
expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
[[ -n "$CRATES_IO_TOKEN" ]] || {
@ -27,37 +49,25 @@ expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
Cargo_tomls=$(ci/order-crates-for-publishing.py)
for Cargo_toml in $Cargo_tomls; do
echo "-- $Cargo_toml"
grep -q "^version = \"$expectedCrateVersion\"$" "$Cargo_toml" || {
echo "Error: $Cargo_toml version is not $expectedCrateVersion"
for crate in "${CRATES[@]}"; do
if [[ ! -r $crate/Cargo.toml ]]; then
echo "Error: $crate/Cargo.toml does not exist"
exit 1
fi
echo "-- $crate"
grep -q "^version = \"$expectedCrateVersion\"$" "$crate"/Cargo.toml || {
echo "Error: $crate/Cargo.toml version is not $expectedCrateVersion"
exit 1
}
(
set -x
crate=$(dirname "$Cargo_toml")
# TODO: the rocksdb package does not build with the stock rust docker image,
# so use the solana rust docker image until this is resolved upstream
source ci/rust-version.sh
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand"
) || true # <-- Don't fail. We want to be able to retry the job in cases when a publish fails halfway due to network/cloud issues
# shellcheck disable=SC2086
crate_name=$(grep -m 1 '^name = ' $Cargo_toml | cut -f 3 -d ' ' | tr -d \")
numRetries=30
for ((i = 1 ; i <= numRetries ; i++)); do
echo "Attempt ${i} of ${numRetries}"
# shellcheck disable=SC2086
if [[ $(is_crate_version_uploaded $crate_name $expectedCrateVersion) = True ]] ; then
echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io"
break
fi
echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds."
sleep 2
done
#ci/docker-run.sh rust bash -exc "cd $crate; $cargoCommand"
)
done
exit 0

View File

@ -45,9 +45,7 @@ beta)
CHANNEL_BRANCH=$BETA_CHANNEL
;;
stable)
# Set to whatever branch 'testnet' is on.
# TODO: Revert to $STABLE_CHANNEL for TdS
CHANNEL_BRANCH=$BETA_CHANNEL
CHANNEL_BRANCH=$STABLE_CHANNEL
;;
*)
echo "Error: Invalid PUBLISH_CHANNEL=$PUBLISH_CHANNEL"
@ -55,7 +53,7 @@ stable)
;;
esac
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
(
cat <<EOF
steps:

View File

@ -3,20 +3,8 @@ set -e
cd "$(dirname "$0")/.."
if [[ -n $APPVEYOR ]]; then
# Bootstrap rust build environment
source ci/env.sh
source ci/rust-version.sh
appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
./rustup-init -yv --default-toolchain $rust_stable --default-host x86_64-pc-windows-msvc
export PATH="$PATH:$USERPROFILE/.cargo/bin"
rustc -vV
cargo -vV
fi
DRYRUN=
if [[ -z $CI_BRANCH ]]; then
if [[ -z $BUILDKITE_BRANCH ]]; then
DRYRUN="echo"
CHANNEL=unknown
fi
@ -24,9 +12,12 @@ fi
eval "$(ci/channel-info.sh)"
TAG=
if [[ -n "$CI_TAG" ]]; then
CHANNEL_OR_TAG=$CI_TAG
TAG="$CI_TAG"
if [[ -n "$BUILDKITE_TAG" ]]; then
CHANNEL_OR_TAG=$BUILDKITE_TAG
TAG="$BUILDKITE_TAG"
elif [[ -n "$TRIGGERED_BUILDKITE_TAG" ]]; then
CHANNEL_OR_TAG=$TRIGGERED_BUILDKITE_TAG
TAG="$TRIGGERED_BUILDKITE_TAG"
else
CHANNEL_OR_TAG=$CHANNEL
fi
@ -36,17 +27,12 @@ if [[ -z $CHANNEL_OR_TAG ]]; then
exit 1
fi
PERF_LIBS=false
case "$CI_OS_NAME" in
osx)
case "$(uname)" in
Darwin)
TARGET=x86_64-apple-darwin
;;
linux)
Linux)
TARGET=x86_64-unknown-linux-gnu
PERF_LIBS=true
;;
windows)
TARGET=x86_64-pc-windows-msvc
;;
*)
TARGET=unknown-unknown-unknown
@ -62,7 +48,7 @@ echo --- Creating tarball
COMMIT="$(git rev-parse HEAD)"
(
echo "channel: $CHANNEL_OR_TAG"
echo "channel: $CHANNEL"
echo "commit: $COMMIT"
echo "target: $TARGET"
) > solana-release/version.yml
@ -70,99 +56,69 @@ echo --- Creating tarball
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" solana-release
if $PERF_LIBS; then
rm -rf target/perf-libs
./fetch-perf-libs.sh
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd validator
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
fi
./fetch-perf-libs.sh
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd fullnode
cargo install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-fullnode solana-release/bin/solana-fullnode-cuda
cp -a scripts multinode-demo solana-release/
# Add a wrapper script for validator.sh
# Add a wrapper script for fullnode.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/validator.sh <<'EOF'
cat > solana-release/bin/fullnode.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/validator.sh "$@"
exec multinode-demo/fullnode.sh "$@"
EOF
chmod +x solana-release/bin/validator.sh
chmod +x solana-release/bin/fullnode.sh
# Add a wrapper script for clear-config.sh
# Add a wrapper script for clear-fullnode-config.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/clear-config.sh <<'EOF'
cat > solana-release/bin/clear-fullnode-config.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/clear-config.sh "$@"
exec multinode-demo/clear-fullnode-config.sh "$@"
EOF
chmod +x solana-release/bin/clear-config.sh
chmod +x solana-release/bin/clear-fullnode-config.sh
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
cp solana-release/bin/solana-install-init solana-install-init-$TARGET
cp solana-release/bin/solana-install solana-install-$TARGET
)
# Metrics tarball is platform agnostic, only publish it from Linux
MAYBE_METRICS_TARBALL=
if [[ "$CI_OS_NAME" = linux ]]; then
metrics/create-metrics-tarball.sh
MAYBE_METRICS_TARBALL=solana-metrics.tar.bz2
echo --- Saving build artifacts
source ci/upload-ci-artifact.sh
upload-ci-artifact solana-release-$TARGET.tar.bz2
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
echo Skipped due to DO_NOT_PUBLISH_TAR
exit 0
fi
source ci/upload-ci-artifact.sh
for file in solana-release-$TARGET.tar.bz2 solana-install-$TARGET; do
echo --- AWS S3 Store: $file
(
set -x
$DRYRUN docker run \
--rm \
--env AWS_ACCESS_KEY_ID \
--env AWS_SECRET_ACCESS_KEY \
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
for file in solana-release-$TARGET.tar.bz2 solana-install-init-"$TARGET"* $MAYBE_METRICS_TARBALL; do
upload-ci-artifact "$file"
echo Published to:
$DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
)
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
continue
fi
if [[ -n $BUILDKITE ]]; then
echo --- AWS S3 Store: "$file"
(
set -x
$DRYRUN docker run \
--rm \
--env AWS_ACCESS_KEY_ID \
--env AWS_SECRET_ACCESS_KEY \
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
echo Published to:
$DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
)
if [[ -n $TAG ]]; then
ci/upload-github-release-asset.sh "$file"
fi
elif [[ -n $TRAVIS ]]; then
# .travis.yml uploads everything in the travis-s3-upload/ directory to release.solana.com
mkdir -p travis-s3-upload/"$CHANNEL_OR_TAG"
cp -v "$file" travis-s3-upload/"$CHANNEL_OR_TAG"/
if [[ -n $TAG ]]; then
# .travis.yaml uploads everything in the travis-release-upload/ directory to
# the associated Github Release
mkdir -p travis-release-upload/
cp -v "$file" travis-release-upload/
fi
elif [[ -n $APPVEYOR ]]; then
# Add artifacts for .appveyor.yml to upload
appveyor PushArtifact "$file" -FileName "$CHANNEL_OR_TAG"/"$file"
if [[ -n $TAG ]]; then
ci/upload-github-release-asset.sh $file
fi
done

View File

@ -13,14 +13,11 @@
# $ source ci/rust-version.sh
#
stable_version=1.35.0
nightly_version=2019-06-20
export rust_stable=1.34.0
export rust_stable_docker_image=solanalabs/rust:1.34.0
export rust_stable="$stable_version"
export rust_stable_docker_image=solanalabs/rust:"$stable_version"
export rust_nightly=nightly-"$nightly_version"
export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
export rust_nightly=nightly-2019-03-14
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-03-14
[[ -z $1 ]] || (

View File

@ -30,8 +30,8 @@ set -o pipefail
export RUST_BACKTRACE=1
UPLOAD_METRICS=""
TARGET_BRANCH=$CI_BRANCH
if [[ -z $CI_BRANCH ]] || [[ -n $CI_PULL_REQUEST ]]; then
TARGET_BRANCH=$BUILDKITE_BRANCH
if [[ -z $BUILDKITE_BRANCH ]] || ./ci/is-pr.sh; then
TARGET_BRANCH=$EDGE_CHANNEL
else
UPLOAD_METRICS="upload"
@ -40,14 +40,7 @@ fi
BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.log
# Clear the C dependency files, if dependeny moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Ensure all dependencies are built
_ cargo +$rust_nightly build --all --release
# Remove "BENCH_FILE", if it exists so that the following commands can append
# First remove "BENCH_FILE", if it exists so that the following commands can append
rm -f "$BENCH_FILE"
# Run sdk benches
@ -70,7 +63,6 @@ _ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verb
exit 0
_ cargo +$rust_nightly run --release --package solana-upload-perf \
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT"
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" > "$BENCH_ARTIFACT"
upload-ci-artifact "$BENCH_FILE"
upload-ci-artifact "$BENCH_ARTIFACT"

View File

@ -5,37 +5,15 @@ cd "$(dirname "$0")/.."
source ci/_
source ci/rust-version.sh stable
source ci/rust-version.sh nightly
export RUST_BACKTRACE=1
export RUSTFLAGS="-D warnings"
do_bpf_check() {
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_nightly" clippy --all -- --version
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
}
(
(
cd sdk/bpf/rust/rust-utils
do_bpf_check
)
for project in programs/bpf/rust/*/ ; do
(
cd "$project"
do_bpf_check
)
done
)
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_stable" clippy --all -- --version
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
_ ci/audit.sh
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
echo --- ok

View File

@ -25,7 +25,7 @@ source scripts/ulimit-n.sh
scripts/coverage.sh
report=coverage-"${CI_COMMIT:0:9}".tar.gz
report=coverage-"${BUILDKITE_COMMIT:0:9}".tar.gz
mv target/cov/report.tar.gz "$report"
upload-ci-artifact "$report"
annotate --style success --context lcov-report \
@ -39,5 +39,5 @@ else
bash <(curl -s https://codecov.io/bash) -X gcov -f target/cov/lcov.info
annotate --style success --context codecov.io \
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${CI_COMMIT:0:9}"
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${BUILDKITE_COMMIT:0:9}"
fi

View File

@ -8,7 +8,6 @@ source ci/rust-version.sh stable
export RUST_BACKTRACE=1
rm -rf target/perf-libs
./fetch-perf-libs.sh
export LD_LIBRARY_PATH=$PWD/target/perf-libs:$LD_LIBRARY_PATH

View File

@ -19,14 +19,7 @@ source scripts/ulimit-n.sh
# Clear cached json keypair files
rm -rf "$HOME/.config/solana"
# Clear the C dependency files, if dependeny moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Clear the BPF sysroot files, they are not automatically rebuilt
rm -rf target/xargo # Issue #3105
# Run the appropriate test based on entrypoint
# Run tbe appropriate test based on entrypoint
testName=$(basename "$0" .sh)
case $testName in
test-stable)
@ -42,10 +35,8 @@ test-stable-perf)
.rs$ \
Cargo.lock$ \
Cargo.toml$ \
^ci/test-stable-perf.sh \
^ci/test-stable.sh \
^core/build.rs \
^fetch-perf-libs.sh \
ci/test-stable-perf.sh \
ci/test-stable.sh \
^programs/ \
^sdk/ \
|| {
@ -56,23 +47,25 @@ test-stable-perf)
# BPF program tests
_ make -C programs/bpf/c tests
_ programs/bpf/rust/noop/build.sh # Must be built out of band
_ cargo +"$rust_stable" test \
--manifest-path programs/bpf/Cargo.toml \
--no-default-features --features=bpf_c,bpf_rust
# Run root package tests with these features
ROOT_FEATURES=
if [[ $(uname) = Linux ]]; then
ROOT_FEATURES=erasure,chacha
if [[ $(uname) = Darwin ]]; then
./build-perf-libs.sh
else
# Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a
# lengthy and unexpected delay the first time CUDA is involved when the driver
# is not yet loaded.
sudo --non-interactive ./net/scripts/enable-nvidia-persistence-mode.sh
rm -rf target/perf-libs
./fetch-perf-libs.sh
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
ROOT_FEATURES=cuda
ROOT_FEATURES=$ROOT_FEATURES,cuda
fi
# Run root package library tests

View File

@ -52,14 +52,14 @@ launchTestnet() {
declare q_mean_tps='
SELECT round(mean("sum_count")) AS "mean_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'
declare q_max_tps='
SELECT round(max("sum_count")) AS "max_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'

View File

@ -20,10 +20,10 @@ tarChannelOrTag=edge
delete=false
enableGpu=false
bootDiskType=""
leaderRotation=true
blockstreamer=false
deployUpdateManifest=true
fetchLogs=true
maybeHashesPerTick=
usage() {
exitcode=0
@ -50,8 +50,9 @@ Deploys a CD testnet
-c [number] - Number of client bencher nodes (default: $clientNodeCount)
-u - Include a Blockstreamer (default: $blockstreamer)
-P - Use public network IP addresses (default: $publicNetwork)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=4,type=nvidia-tesla-k80)
-g - Enable GPU (default: $enableGpu)
-b - Disable leader rotation
-a [address] - Set the bootstrap fullnode's external IP address to this GCE address
-d [disk-type] - Specify a boot disk type (default None) Use pd-ssd to get ssd on GCE.
-D - Delete the network
@ -65,9 +66,6 @@ Deploys a CD testnet
-w - Skip time-consuming "bells and whistles" that are
unnecessary for a high-node count demo testnet
--hashes-per-tick NUM_HASHES|sleep|auto
- Override the default --hashes-per-tick for the cluster
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
metrics
EOF
@ -76,22 +74,7 @@ EOF
zone=()
shortArgs=()
while [[ -n $1 ]]; do
if [[ ${1:0:2} = -- ]]; then
if [[ $1 = --hashes-per-tick ]]; then
maybeHashesPerTick="$1 $2"
shift 2
else
usage "Unknown long option: $1"
fi
else
shortArgs+=("$1")
shift
fi
done
while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:Sfew" opt; do
case $opt in
h | \?)
usage
@ -124,6 +107,9 @@ while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
;;
esac
;;
b)
leaderRotation=false
;;
g)
enableGpu=true
;;
@ -166,7 +152,7 @@ while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
deployUpdateManifest=false
;;
*)
usage "Unknown option: $opt"
usage "Error: unhandled option: $opt"
;;
esac
done
@ -244,6 +230,10 @@ if ! $skipCreate; then
fi
fi
if ! $leaderRotation; then
create_args+=(-b)
fi
if $publicNetwork; then
create_args+=(-P)
fi
@ -311,13 +301,6 @@ if ! $skipStart; then
if [[ -n $NO_LEDGER_VERIFY ]]; then
args+=(-o noLedgerVerify)
fi
if [[ -n $NO_INSTALL_CHECK ]]; then
args+=(-o noInstallCheck)
fi
if [[ -n $maybeHashesPerTick ]]; then
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
args+=($maybeHashesPerTick)
fi
if $reuseLedger; then
args+=(-r)
@ -327,11 +310,10 @@ if ! $skipStart; then
args+=(-F)
fi
if $deployUpdateManifest; then
rm -f update_manifest_keypair.json
args+=(--deploy-update linux)
args+=(--deploy-update osx)
args+=(--deploy-update windows)
# shellcheck disable=SC2154 # SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu comes from .buildkite/env/
if $deployUpdateManifest && [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
echo "$SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu" > update_manifest_keypair.json
args+=(-i update_manifest_keypair.json)
fi
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables

View File

@ -80,20 +80,7 @@ ci/channel-info.sh
eval "$(ci/channel-info.sh)"
EC2_ZONES=(
us-west-1a
us-west-2a
us-east-1a
us-east-2a
sa-east-1a
eu-west-1a
eu-west-2a
eu-central-1a
ap-northeast-2a
ap-southeast-2a
ap-south-1a
ca-central-1a
)
EC2_ZONES=(us-west-1a sa-east-1a ap-northeast-2a eu-central-1a ca-central-1a)
# GCE zones with _lots_ of quota
GCE_ZONES=(
@ -132,16 +119,19 @@ case $TESTNET in
testnet-edge|testnet-edge-perf)
CHANNEL_OR_TAG=edge
CHANNEL_BRANCH=$EDGE_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-beta|testnet-beta-perf)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
: "${EC2_NODE_COUNT:=10}"
: "${GCE_NODE_COUNT:=}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-perf)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
@ -152,6 +142,7 @@ testnet-demo)
CHANNEL_BRANCH=$BETA_CHANNEL
: "${GCE_NODE_COUNT:=150}"
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@ -184,7 +175,7 @@ if [[ -n $TESTNET_TAG ]]; then
CHANNEL_OR_TAG=$TESTNET_TAG
else
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
(
cat <<EOF
steps:
@ -212,7 +203,6 @@ sanity() {
testnet-edge)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh edge-testnet-solana-com ec2 us-west-1a
)
@ -229,7 +219,6 @@ sanity() {
testnet-beta)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh beta-testnet-solana-com ec2 us-west-1a
)
@ -336,6 +325,7 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p edge-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@ -348,6 +338,7 @@ deploy() {
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p beta-testnet-solana-com -C ec2 -z us-west-1a \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0f286cf8a0771ce35 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@ -362,6 +353,7 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p beta-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@ -378,7 +370,7 @@ deploy() {
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C ec2 ${EC2_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f -a eipalloc-0fa502bf95f6f18b2 \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -a eipalloc-0fa502bf95f6f18b2 \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
@ -387,7 +379,7 @@ deploy() {
if [[ -n $GCE_NODE_COUNT ]]; then
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -f \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@ -405,6 +397,7 @@ deploy() {
ci/testnet-deploy.sh -p perf-testnet-solana-com -C gce -z us-west1-b \
-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100" \
-t "$CHANNEL_OR_TAG" -c 2 \
-b \
-d pd-ssd \
${skipCreate:+-e} \
${skipStart:+-s} \

View File

@ -64,7 +64,6 @@ for zone in "$@"; do
${NO_LEDGER_VERIFY:+-o noLedgerVerify} \
${NO_VALIDATOR_SANITY:+-o noValidatorSanity} \
${REJECT_EXTRA_NODES:+-o rejectExtraNodes} \
${NO_INSTALL_CHECK:+-o noInstallCheck} \
$zone || ok=false
net/net.sh logs

View File

@ -8,6 +8,8 @@
#
set -e
REPO_SLUG=solana-labs/solana
if [[ -z $1 ]]; then
echo No files specified
exit 1
@ -18,30 +20,31 @@ if [[ -z $GITHUB_TOKEN ]]; then
exit 1
fi
if [[ -z $CI_TAG ]]; then
echo Error: CI_TAG not defined
exit 1
if [[ -n $BUILDKITE_TAG ]]; then
TAG=$BUILDKITE_TAG
elif [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
TAG=$TRIGGERED_BUILDKITE_TAG
fi
if [[ -z $CI_REPO_SLUG ]]; then
echo Error: CI_REPO_SLUG not defined
if [[ -z $TAG ]]; then
echo Error: TAG not defined
exit 1
fi
releaseId=$( \
curl -s "https://api.github.com/repos/$CI_REPO_SLUG/releases/tags/$CI_TAG" \
curl -s "https://api.github.com/repos/$REPO_SLUG/releases/tags/$TAG" \
| grep -m 1 \"id\": \
| sed -ne 's/^[^0-9]*\([0-9]*\),$/\1/p' \
)
echo "Github release id for $CI_TAG is $releaseId"
echo "Github release id for $TAG is $releaseId"
for file in "$@"; do
echo "--- Uploading $file to tag $CI_TAG of $CI_REPO_SLUG"
echo "--- Uploading $file to tag $TAG of $REPO_SLUG"
curl \
--data-binary @"$file" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/$CI_REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
"https://uploads.github.com/repos/$REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
echo
done

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