Compare commits

..

122 Commits

Author SHA1 Message Date
Pankaj Garg
cfe91c67f9 Revert "Revert "Disable accounts squash call from bank""
This reverts commit f84593ad5f.
2019-04-03 06:59:21 -07:00
carllin
0f9c516570 Cherry pick #3572 and #3459 (#3608)
* Fix skipping half leader slot

* Get rid of unnecessary frozen banks
2019-04-02 17:57:00 -07:00
Pankaj Garg
e4aba9c900 Remove second block streamer from testnet beta 2019-04-02 15:38:02 -07:00
Pankaj Garg
88aea74ab1 Don't use external node ssh key if one is not configured 2019-04-02 14:52:30 -07:00
carllin
357f28ae4e Implement finalizer so that all locked accounts are dropped (#3585) (#3598)
* Implement finalizer so that all locked accounts are dropped when finalizer goes out of scope

* Add test for tx error with lock conflict

* Fix double unlock from destructor running after a call to unlock
2019-04-02 13:49:09 -07:00
Pankaj Garg
7c9c667c5c Deploy beta testnet with 100 nodes across AWS and GCP 2019-04-02 12:44:42 -07:00
Pankaj Garg
d62d1174d0 Support for configuring testnet nodes across multiple cloud services 2019-04-01 19:56:01 -07:00
Pankaj Garg
5d5ae1c962 Increase node count in beta testnet 2019-04-01 13:02:13 -07:00
carllin
bf11d7ef43 Fix resetting PohRecorder to wrong bank (#3553) (#3574)
* Check whether future slot already has transmission
2019-03-30 02:06:49 -07:00
Pankaj Garg
9568a1da03 Send metrics data to the correct/configured database host 2019-03-29 13:12:01 -07:00
Pankaj Garg
f11a3d9796 Fix EC2 scripts for blockstream startup 2019-03-28 15:56:28 -07:00
Pankaj Garg
8a7e8aacdd enable leader rotation in beta testnet 2019-03-28 14:34:30 -07:00
Pankaj Garg
e3750fb73d fix clippy errors 2019-03-28 14:34:30 -07:00
Pankaj Garg
d78b09492e review comments 2019-03-28 14:34:30 -07:00
Pankaj Garg
d573b55821 fix the ip address that's stored in the config file 2019-03-28 14:34:30 -07:00
Pankaj Garg
18cad565cf fix shell-check errors 2019-03-28 14:34:30 -07:00
Pankaj Garg
9abf90b443 Added support for multi-region cloud testnet 2019-03-28 14:34:30 -07:00
anatoly yakovenko
cd3ff5c335 V0.12.3, cherry pick 3523 and 3529 (#3531)
* validator confirmation

* validator confirmaiton

* remove leader confirmaiton

* hang out on progress until fork is confirmed

* use the right id for delegate id

* fixup! hang out on progress until fork is confirmed

* fixup! use the right id for delegate id

* version bump
2019-03-28 05:59:42 -07:00
Pankaj Garg
e55249e63f propagate TESTNET_DB_HOST env variable to next step in buildkite 2019-03-26 15:00:33 -07:00
Pankaj Garg
10bc0c6ee2 Add provisions to specify a database server in testnet manager buildkite 2019-03-26 15:00:33 -07:00
Anatoly Yakovenko
ed14b78d81 also check the delegate_id 2019-03-26 13:44:53 -07:00
Rob Walker
7f404941bb remove status_cache.freeze (#3509) 2019-03-26 12:10:46 -07:00
anatoly yakovenko
6d45ac1bc7 Record the current nodes locktower votes from the bank (#3502)
* observed_locktower_stats

* fixup! observed_locktower_stats
2019-03-26 11:45:59 -07:00
Rob Walker
fabb6d2092 delay freeze of status_cache until squash (#3503) 2019-03-26 11:37:38 -07:00
Pankaj Garg
93cea4c86c Remove rewards crate from publishing script 2019-03-25 21:34:54 -07:00
Pankaj Garg
5fb35f79c3 Added stats for locktower in testnet dashboard 2019-03-25 21:11:37 -07:00
Pankaj Garg
da11274b63 Add support for influx cloud 2019-03-25 21:11:37 -07:00
Pankaj Garg
5d70e2efa9 0.12.2 2019-03-25 20:38:48 -07:00
Anatoly Yakovenko
8f181b4350 keep track of locktower slots and stakes 2019-03-25 16:36:19 -07:00
Rob Walker
48844924e5 Setup staking (#3480) 2019-03-25 14:19:14 -07:00
Pankaj Garg
f84593ad5f Revert "Disable accounts squash call from bank"
This reverts commit 7685ba2805.
2019-03-25 12:21:32 -07:00
Sathish
0469dc52ac Ensure accounts are unlocked (#3458) 2019-03-25 12:21:32 -07:00
Carl
4cf418f33f Fix wrong keypair 2019-03-23 16:33:50 -07:00
carllin
6c46fcfa4e Restart node test (#3459)
* Add test to local_cluster for restarting a node

* fix so that we don't hit end of epoch - leader not found before trying to transfer
2019-03-23 15:00:23 -07:00
Carl
12ec5304f2 Revert "fix so that we don't hit end of epoch - leader not found before trying to transfer"
Revert "Add test to local_cluster for restarting a node"
2019-03-22 21:46:08 -07:00
Carl
e32f798d5f fix so that we don't hit end of epoch - leader not found before trying to transfer 2019-03-22 20:47:32 -07:00
Carl
68a8b955bc Add test to local_cluster for restarting a node 2019-03-22 19:30:14 -07:00
Pankaj Garg
f479021c0f Update leader slot in poh recorder if we skipped it (#3451)
* reset poh recorder with the original start slot
2019-03-22 17:35:54 -07:00
Michael Vines
b91afb7079 Remove attempt to update the cluster, just restart it (v0.12 is not ready for update) 2019-03-22 16:51:53 -07:00
Michael Vines
e189c429d5 Refrain from trying to configure a staking account that was previously configured 2019-03-22 16:51:53 -07:00
Michael Vines
6a1904664c Demote log level 2019-03-22 16:51:53 -07:00
Michael Vines
3285cf8047 Retry more for a new blockhash 2019-03-22 10:56:59 -07:00
Michael Vines
bdee3a25f2 Add --poll-for-new-genesis-block flag 2019-03-22 00:44:31 -07:00
Michael Vines
8655df0520 Use same gossip port for all testnet nodes 2019-03-21 23:56:23 -07:00
Michael Vines
c43eecb8ca Include multinode-demo scripts in release tarball 2019-03-21 22:12:07 -07:00
Michael Vines
18f45ebc2c Use installed binaries if not within the cargo workspace 2019-03-21 22:12:07 -07:00
Michael Vines
fd28642603 Run a drone on blockstreamer nodes 2019-03-21 22:12:07 -07:00
Michael Vines
038583b466 Kill all node processes (blockexplorer) 2019-03-21 22:12:07 -07:00
Michael Vines
ed138d392d Fixup ledger path 2019-03-21 17:06:05 -07:00
Michael Vines
58f1f0a28b solana-install doesn't exist on v0.12 2019-03-21 16:49:41 -07:00
Michael Vines
330d9330b0 Ensure current crate versions match the tag before publishing to crates.io 2019-03-21 16:27:44 -07:00
Michael Vines
d626a89c88 / 2019-03-21 16:27:06 -07:00
Michael Vines
db5d22e532 Upload tarball as a github release asset 2019-03-21 16:27:06 -07:00
Michael Vines
aa8759744e Add script to upload github release assets 2019-03-21 16:27:06 -07:00
Michael Vines
060db36c34 Add GITHUB_TOKEN 2019-03-21 16:27:06 -07:00
Michael Vines
fa1ea1c458 Switch version file from .txt to .yaml; add target tuple to version.yml 2019-03-21 16:27:06 -07:00
Pankaj Garg
7685ba2805 Disable accounts squash call from bank
- It's asserting and killing testnet
- temporary solution for beacons
2019-03-21 16:01:43 -07:00
Anatoly Yakovenko
a0d940acf0 allow empty ancestors 2019-03-21 16:01:43 -07:00
carllin
f4c914a630 Clear progress map on squash (#3377) 2019-03-21 16:01:43 -07:00
Anatoly Yakovenko
eede274cfe fix is_locked_out logic 2019-03-21 16:01:43 -07:00
Carl
4df79b653b PR comments 2019-03-21 16:01:43 -07:00
Carl
a2c1fa7cb4 Modify bank_forks to support squashing/filtering new root and also don't remove parents from bank_forks when inserting, otherwise we lose potential fork points when querying blocktree for child slots 2019-03-21 16:01:43 -07:00
Stephen Akridge
95cead91a5 Decendent is not a word 2019-03-21 16:01:43 -07:00
anatoly yakovenko
89c42ecd3f Implement locktower voting (#3251)
* locktower components and tests

* integrate locktower into replay stage

* track locktower duration

* make sure threshold is checked after simulating the vote

* check vote lockouts using the VoteState program

* duplicate vote test

* epoch stakes

* disable impossible to verify tests
2019-03-21 16:01:43 -07:00
Michael Vines
f93c9f052f Ensure genesis ledger directory is populated on all validator nodes
This allows all nodes to serve the genesis ledger over rsync instead of
just the bootstrap leader
2019-03-21 15:55:12 -07:00
Michael Vines
e2871053bd Get client-id.json out of the genesis ledger directory 2019-03-21 15:55:08 -07:00
Pankaj Garg
351c9c33d2 change num threads in banking stage bench 2019-03-21 15:00:30 -07:00
Pankaj Garg
59f2a478b7 v0.12 specific stability changes 2019-03-21 15:00:30 -07:00
Pankaj Garg
3f7cd4adc4 Ignore broken tests that are fixed on master
- ignoring, as cherry picking from master will bring in other
  unnecessary dependent changes
2019-03-21 13:45:41 -07:00
Pankaj Garg
4318854a64 ignore broken test 2019-03-21 13:45:41 -07:00
Pankaj Garg
430740b691 use ticks per slot to check if the current tick is in the leader slot 2019-03-21 13:45:41 -07:00
Pankaj Garg
797603a0fe address review comments 2019-03-21 13:45:41 -07:00
Pankaj Garg
f402139991 change pubkey to ref 2019-03-21 13:45:41 -07:00
Pankaj Garg
4db72d85d7 find next leader slot before resetting working bank in Poh recorder 2019-03-21 13:45:41 -07:00
Pankaj Garg
007e17c290 Check if poh recorder has over stepped the leader slot 2019-03-21 13:45:41 -07:00
Pankaj Garg
ad7e727938 Use same VM type for validators as leader, if CUDA is enabled (#3253)
- Since all nodes are created equal
2019-03-21 13:45:41 -07:00
Rob Walker
3d5eeab6d9 stop copying Blooms (#3379)
* stop copying Blooms

* fixup

* clippy
2019-03-21 13:45:41 -07:00
Michael Vines
8278585545 Avoid panic on duplicate account indices 2019-03-19 16:06:50 -07:00
Pankaj Garg
061d6ec8fd fix formatting 2019-03-19 11:21:00 -07:00
Pankaj Garg
000cc27e53 Schedule node for consecutive slots as leader (#3353)
* Also tweak epoch and slot duration

* new test for leader schedule
2019-03-19 11:21:00 -07:00
Pankaj Garg
9b3092b965 Report how many grace ticks were afforded to previous leader (#3350) 2019-03-19 11:21:00 -07:00
Sagar Dhawan
ca819fc4fb Fix leader rotation counter 2019-03-19 11:21:00 -07:00
Tyera Eulberg
5ff8f57c0e Remove dangling thin_client 2019-03-18 22:20:14 -07:00
Pankaj Garg
4798612560 Reduce log level for periodic debug messages 2019-03-15 16:02:52 -07:00
Rob Walker
9760cb2e6a add support for finding the next slot a node will be leader (#3298) 2019-03-15 15:02:20 -07:00
Pankaj Garg
46b3b3a1c6 Give last leader some grace ticks to catch up (#3299)
* Wait for last leader for some ticks

* New tests and fixed existing tests
2019-03-15 15:02:20 -07:00
Pankaj Garg
1e70f85e83 [v0.12] Reduce ticks per second (#3287)
* Reduce ticks per second

- It's improving TPS. Temp fix for beacons timeframe

* Fix confirmation test
2019-03-15 14:15:54 -07:00
Michael Vines
b2d6681762 Bump log level for better CI logs 2019-03-15 07:48:57 -07:00
Michael Vines
1b51cba778 Avoid stray '' when rust version is not specified 2019-03-14 21:32:25 -07:00
Michael Vines
19ab7333aa cloud_DeleteInstances() now waits for the instances to be terminated 2019-03-14 21:17:36 -07:00
Michael Vines
b0e6604b9a Revert "Block until instances are confirmed to be deleted"
This reverts commit 5e40a5bfc1.
2019-03-14 21:17:30 -07:00
Michael Vines
9ce1d5e990 Upgrade nightly rust version 2019-03-14 20:37:44 -07:00
Michael Vines
facc47cb62 Preserve original nightly name 2019-03-14 20:37:44 -07:00
Michael Vines
3dba8b7952 Overhaul cargo/rustc version management 2019-03-14 20:37:44 -07:00
Michael Vines
5e40a5bfc1 Block until instances are confirmed to be deleted 2019-03-14 16:20:35 -07:00
Greg Fitzgerald
c60baf99f3 Rename userdata to data (#3282)
* Rename userdata to data

Instead of saying "userdata", which is ambiguous and imprecise,
say "instruction data" or "account data".

Also, add `ProgramError::InvalidInstructionData`

Fixes #2761
2019-03-14 13:04:42 -07:00
Sagar Dhawan
de04884c1b Fix flag to disable leader-rotation (#3243) 2019-03-14 12:08:53 -07:00
carllin
e666509409 Don't vote for empty leader transmissions (#3248)
* Don't vote for empty leader transmissions

* Add is_delta flag to bank to detect empty leader transmissions

* Plumb new is_votable flag through replay stage

* Fix PohRecorder tests

* Change is_delta to AtomicBool to avoid making Bank references mutable

* Reset start slot in poh_recorder when working bank is cleared, so that connsecutive TPU's will start from the correct place

* Use proper max tick height calculation

* Test for not voting on empty transmission

* tests for is_votable
2019-03-13 14:32:04 -07:00
Michael Vines
28aff96d21 Replace stale --no-signer usage with --no-voting 2019-03-13 13:56:57 -07:00
Michael Vines
242975f8cd Remove duplicate --rpc-drone-address 2019-03-13 13:23:18 -07:00
Michael Vines
c6ba6cac83 Revert "Add case for --rpc-drone-address"
This reverts commit dc67dd3357.
2019-03-13 13:15:49 -07:00
Michael Vines
dc67dd3357 Add case for --rpc-drone-address 2019-03-13 13:03:54 -07:00
Michael Vines
733c2a0b07 Enable rpc for all testnet nodes 2019-03-13 10:51:49 -07:00
Michael Vines
07d6212d18 Drop socat for iptables 2019-03-13 10:16:28 -07:00
Michael Vines
c20d60e4cf Run socat in the background 2019-03-13 08:18:10 -07:00
Rob Walker
7147f03efe tell blockexplorer to run on port 8080 (#3237)
* tell blockexplorer to run on port 8080

* forward port 80 to 5000 for a blockexplorer node
2019-03-13 07:37:28 -07:00
carllin
6740cb5b02 Replay Stage start_leader() can use wrong parent fork() (#3238)
*  Make sure start_leader starts on the last voted block, not necessarily the biggest indexed bank in frozen_slots()

* Fix tvu test
2019-03-13 03:16:13 -07:00
Tyera Eulberg
1e8e99cc3e Move and rename cluster_client 2019-03-12 23:07:48 -06:00
Tyera Eulberg
ef7f30e09f Update publish script 2019-03-12 23:07:48 -06:00
Tyera Eulberg
ca8e0ec7ae Move thin client tests to integration test suite 2019-03-12 23:07:48 -06:00
Tyera Eulberg
2a4f4b3e53 Update crate references 2019-03-12 23:07:48 -06:00
Tyera Eulberg
7cecd3851a Add solana-client crate 2019-03-12 23:07:48 -06:00
Tyera Eulberg
4d189f2c38 Cargo.lock 2019-03-12 23:07:48 -06:00
Michael Vines
9a232475a7 0.12.1 2019-03-12 13:42:47 -07:00
Michael Vines
09c9897591 Adjust crate list 2019-03-12 13:36:18 -07:00
Michael Vines
06d7573478 Adjust readme path 2019-03-12 13:36:13 -07:00
Michael Vines
0b55ffa368 Move programs/system into runtime/ 2019-03-12 12:25:47 -05:00
Sagar Dhawan
ae750bb16b Filter vote accounts with no delegate from being selected in Rotation (#3224) 2019-03-11 21:32:19 -07:00
Pankaj Garg
80b2f2f6b7 Update current leader information in metrics and dashboard 2019-03-11 18:47:27 -07:00
Pankaj Garg
6684d84fbc Provide drone's host address while setting up staking account 2019-03-11 18:20:27 -07:00
Michael Vines
dc02abae3c Keep stable dashboard on stable channel at all times 2019-03-11 16:19:35 -07:00
Michael Vines
6caec655d3 Move testnet/testnet-perf to the stable channel 2019-03-11 16:15:47 -07:00
936 changed files with 34762 additions and 104738 deletions

View File

@@ -1,42 +0,0 @@
version: '{build}'
branches:
only:
- master
- /^v[0-9.]+\.[0-9.]+/
cache:
- '%USERPROFILE%\.cargo'
- '%APPVEYOR_BUILD_FOLDER%\target'
clone_folder: d:\projects\solana
build_script:
- bash ci/publish-tarball.sh
notifications:
- provider: Slack
incoming_webhook:
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
channel: ci-status
on_build_success: false
on_build_failure: true
on_build_status_changed: true
deploy:
- provider: S3
access_key_id:
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
secret_access_key:
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
bucket: release.solana.com
region: us-west-1
set_public: true
- provider: GitHub
auth_token:
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
draft: false
prerelease: false
on:
appveyor_repo_tag: true

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:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:rHBSqXK7uSnveA4qwUxARZjTNZcA0hXU:ko8lLGwPECpVm19znWBRxKEpMF7xpTHBCEzVOxRar2wDThw4lNDAKqTS61vtkJLtdkHtug==]",
"CRATES_IO_TOKEN": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:NzN6y0ooXJBYvxB589khepthSxhKFkLB:ZTTFZh2A/kB2SAgjJJAMbwAfanRlzxOCNMVcA2MXBCpQHJeeZGULg+0MLACYswfS]",
"GITHUB_TOKEN": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:iy0Fnxeo0aslTCvgXc5Ddj2ly6ZsQ8gK:GNOOj/kZUJ2rYKxTbLyVKtajWNoGQ3PcChwfEB4HdN18qDHlB96Z7gx01Pcf0qeIHODOWRtxlH4=]",
"INFLUX_DATABASE": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:Ly/TpIRF0oCxmiBWv225S3mX8s6pfQR+:+tXGB2c9rRCVDcgNO1IDOo89]",
"INFLUX_PASSWORD": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:ycrq1uQLoSfI932czD+krUOaJeLWpeq6:2iS7ukp/C7wVD3IT0GvQVcwccWGyLr4UocStF/XiDi0OB/N3YKIKN8SQU4ob1b6StAPZ/XOHmag=]",
"INFLUX_USERNAME": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:35hBKofakZ4Db/u0TOW53RXoNWzJTIcl:HWREcMTrgZ8DGB0ZupgSzNWr/tVyE06P]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:+7nLVR8NlnN48zgaJPPXF9JOZDXVNHDZLeARlCFHyRk=:kRz8CyJYKAg/AiwgLrcRNDJAmlRX2zvX:uV1XV6y2Fb+dN4Z9BIMPBRiNS3n+NL8GlJXyu1i7meIsph1DzfLg4Thcp5Mj9nUsFNLgqQgjnsa5C4XNY/h5AgMSzRrJxVj7RhVTRmDJ5/Vjq6v7wCMRfBOvF3rITsV4zTwWSV8yafFmS+ZQ+QJTRgtYsuoYAUNZ06IEebfDHcuNwws72hEGoD9w43hOLSpyEOmXbtZ9h1lIRxrgsrhYDpBlU5LkhDeTXAX5M5dwYxyquJFRwd5quGDV5DYsCh9bAkbjAyjWYymVJ78U9YJIQHT9izzQqTDlMQN49EbLo7MDIaC7O7HVtb7unDJs+DRejbHacoyWVulqVVwu3GRiZezu8zdjwzGHphMMxOtKQaidnqYgflNp/O01I8wZRgR1alsGcmIhEhI8YV/IvQ==]"
}
}

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
@@ -33,10 +31,3 @@ source ci/env.sh
kill -9 "$victim" || true
done
)
# HACK: These are in our docker images, need to be removed from CARGO_HOME
# because we try to cache downloads across builds with CARGO_HOME
# cargo lacks a facility for "system" tooling, always tries CARGO_HOME first
cargo uninstall cargo-audit || true
cargo uninstall svgbob_cli || true
cargo uninstall mdbook || true

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

View File

@@ -10,22 +10,11 @@
set -e
cd "$(dirname "$0")"/..
if [[ -n $BUILDKITE_TAG ]]; then
buildkite-agent annotate --style info --context release-tag \
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
buildkite-agent pipeline upload ci/buildkite-release.yml
else
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
buildkite-agent pipeline upload ci/buildkite.yml
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
# Add helpful link back to the corresponding Github Pull Request
buildkite-agent annotate --style info --context pr-backlink \
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
fi
if [[ $BUILDKITE_MESSAGE =~ GitBook: ]]; then
buildkite-agent annotate --style info --context gitbook-ci-skip \
"GitBook commit detected, CI skipped"
exit
fi
buildkite-agent pipeline upload ci/buildkite.yml
fi

View File

@@ -1,5 +0,0 @@
root: ./book/src
structure:
readme: introduction.md
summary: SUMMARY.md

24
.github/stale.yml vendored
View File

@@ -1,24 +0,0 @@
only: pulls
# Number of days of inactivity before a pull request becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale pull request is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- security
# Label to use when marking a pull request as stale
staleLabel: stale
# Comment to post when marking a pull request as stale. Set to `false` to disable
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
# Comment to post when closing a stale pull request. Set to `false` to disable
closeComment: >
This stale pull request has been automatically closed.
Thank you for your contributions.

17
.gitignore vendored
View File

@@ -1,16 +1,18 @@
/book/html/
/book/src/tests.ok
/farf/
/solana-release/
/solana-release.tar.bz2
/solana-metrics/
/solana-metrics.tar.bz2
/target/
/ledger-tool/target/
/wallet/target/
/core/target/
/book/html/
/book/src/img/
/book/src/tests.ok
**/*.rs.bk
.cargo
# node config that is rsynced
/config/
# node config that remains local
/config-local/
# log files
*.log
@@ -19,4 +21,3 @@ log-*.txt
# intellij files
/.idea/
/solana.iml
/.vscode/

View File

@@ -1,77 +0,0 @@
# Validate your changes with:
#
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
#
# https://doc.mergify.io/
pull_request_rules:
- name: remove outdated reviews
conditions:
- base=master
actions:
dismiss_reviews:
changes_requested: true
- name: set automerge label on mergify backport PRs
conditions:
- author=mergify[bot]
- head~=^mergify/bp/
- "#status-failure=0"
actions:
label:
add:
- automerge
- name: v0.16 backport
conditions:
- base=master
- label=v0.16
actions:
backport:
branches:
- v0.16
- name: v0.17 backport
conditions:
- base=master
- label=v0.17
actions:
backport:
branches:
- v0.17
- name: v0.18 backport
conditions:
- base=master
- label=v0.18
actions:
backport:
branches:
- v0.18
- name: v0.19 backport
conditions:
- base=master
- label=v0.19
actions:
backport:
branches:
- v0.19
- name: v0.20 backport
conditions:
- base=master
- label=v0.20
actions:
backport:
branches:
- v0.20
- name: v0.21 backport
conditions:
- base=master
- label=v0.21
actions:
backport:
branches:
- v0.21
- name: v0.22 backport
conditions:
- base=master
- label=v0.22
actions:
backport:
branches:
- v0.22

View File

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

View File

@@ -46,17 +46,10 @@ and longer descriptions detailing what problem it solves and how it solves it.
Draft Pull Requests
---
If you want early feedback on your PR, use GitHub's "Draft Pull Request"
mechanism. Draft PRs are a convenient way to collaborate with the Solana
maintainers without triggering notifications as you make changes. When you feel
your PR is ready for a broader audience, you can transition your draft PR to a
standard PR with the click of a button.
Do not add reviewers to draft PRs. GitHub doesn't automatically clear approvals
when you click "Ready for Review", so a review that meant "I approve of the
direction" suddenly has the appearance of "I approve of these changes." Instead,
add a comment that mentions the usernames that you would like a review from. Ask
explicitly what you would like feedback on.
If you want early feedback on your PR, use GitHub's "Draft Pull Request" mechanism. Draft
PRs are a convenient way to collaborate with the Solana maintainers without triggering
notifications as you make changes. When you feel your PR is ready for a broader audience,
you can transition your draft PR to a standard PR with the click of a button.
Rust coding conventions
---
@@ -96,23 +89,24 @@ understood. Avoid introducing new 3-letter terms, which can be confused with 3-l
[Terms currently in use](book/src/terminology.md)
Design Proposals
Proposing architectural changes
---
Solana's architecture is described by a book generated from markdown files in
the `book/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to at least propose a change the content
under the [Accepted Design
Proposals](https://docs.solana.com/book/v/master/proposals) chapter.
Here's the full process:
change the architecture, you'll need to at least propose a change the content
under the [Proposed
Changes](https://solana-labs.github.io/book-edge/proposals.html) chapter. Here's
the full process:
1. Propose a design by creating a PR that adds a markdown document to the
directory `book/src/` and references it from the [table of
contents](book/src/SUMMARY.md). Add any relevant *maintainers* to the PR review.
1. Propose to a change to the architecture by creating a PR that adds a
markdown document to the directory `book/src/` and references it from the
[table of contents](book/src/SUMMARY.md). Add the editor and any relevant
*maintainers* to the PR review.
2. The PR being merged indicates your proposed change was accepted and that the
maintainers support your plan of attack.
editor and maintainers support your plan of attack.
3. Submit PRs that implement the proposal. When the implementation reveals the
need for tweaks to the proposal, be sure to update the proposal and have
need for tweaks to the architecture, be sure to update the proposal and have
that change reviewed by the same people as in step 1.
4. Once the implementation is complete, submit a PR that moves the link from
the Accepted Proposals to the Implemented Proposals section.
4. Once the implementation is complete, the editor will then work to integrate
the document into the book.

5108
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,127 +1,92 @@
[package]
name = "solana-workspace"
description = "Blockchain, Rebuilt for Scale"
version = "0.12.3"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "README.md"
repository = "https://github.com/solana-labs/solana"
authors = ["Solana Maintainers <maintainers@solana.com>"]
license = "Apache-2.0"
edition = "2018"
[badges]
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
[features]
chacha = ["solana/chacha"]
cuda = ["solana/cuda"]
erasure = ["solana/erasure"]
[dev-dependencies]
bincode = "1.1.2"
bs58 = "0.2.0"
hashbrown = "0.1.8"
log = "0.4.2"
rand = "0.6.5"
rayon = "1.0.0"
reqwest = "0.9.11"
serde_json = "1.0.39"
solana = { path = "core", version = "0.12.3" }
solana-budget-program = { path = "programs/budget", version = "0.12.3" }
solana-client = { path = "client", version = "0.12.3" }
solana-logger = { path = "logger", version = "0.12.3" }
solana-netutil = { path = "netutil", version = "0.12.3" }
solana-runtime = { path = "runtime", version = "0.12.3" }
solana-sdk = { path = "sdk", version = "0.12.3" }
solana-vote-api = { path = "programs/vote_api", version = "0.12.3" }
sys-info = "0.5.6"
[[bench]]
name = "banking_stage"
[[bench]]
name = "blocktree"
[[bench]]
name = "ledger"
[[bench]]
name = "gen_keys"
[[bench]]
name = "sigverify"
[[bench]]
required-features = ["chacha"]
name = "chacha"
[workspace]
# The members list excluding the `validator-cuda` crate
default-members = [
"bench-exchange",
"bench-streamer",
"bench-tps",
"banking_bench",
"chacha-sys",
"client",
"core",
"drone",
"validator",
"genesis",
"genesis_programs",
"gossip",
"install",
"keygen",
"kvstore",
"ledger-tool",
"local_cluster",
"logger",
"merkle-tree",
"measure",
"metrics",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
"programs/budget_api",
"programs/budget_program",
"programs/btc_spv_program",
"programs/btc_spv_api",
"programs/btc_spv_bin",
"programs/config_api",
"programs/config_program",
"programs/config_tests",
"programs/exchange_api",
"programs/exchange_program",
"programs/failure_program",
"programs/move_loader_api",
"programs/move_loader_program",
"programs/librapay_api",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/stake_tests",
"programs/storage_api",
"programs/storage_program",
"programs/token_api",
"programs/token_program",
"programs/vote_api",
"programs/vote_program",
"replicator",
"runtime",
"sdk",
"sdk-c",
"upload-perf",
"netutil",
"fixed-buf",
"vote-signer",
"cli",
"rayon-threadlimit",
]
# The default-members list and the `validator-cuda` crate
members = [
"bench-exchange",
".",
"bench-streamer",
"bench-tps",
"banking_bench",
"chacha-sys",
"client",
"core",
"drone",
"validator",
"fullnode",
"genesis",
"genesis_programs",
"gossip",
"install",
"keygen",
"kvstore",
"ledger-tool",
"local_cluster",
"logger",
"merkle-tree",
"measure",
"metrics",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
"programs/budget_api",
"programs/budget_program",
"programs/btc_spv_program",
"programs/btc_spv_api",
"programs/btc_spv_bin",
"programs/config_api",
"programs/config_program",
"programs/config_tests",
"programs/exchange_api",
"programs/exchange_program",
"programs/failure_program",
"programs/move_loader_api",
"programs/move_loader_program",
"programs/librapay_api",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/stake_tests",
"programs/storage_api",
"programs/storage_program",
"programs/token_api",
"programs/token_program",
"programs/vote_api",
"programs/vote_program",
"replicator",
"runtime",
"sdk",
"sdk-c",
"upload-perf",
"netutil",
"fixed-buf",
"vote-signer",
"cli",
"rayon-threadlimit",
"validator-cuda",
]
exclude = [
"programs/bpf",
"programs/bpf_loader",
"programs/budget",
"programs/budget_api",
"programs/token",
"programs/token_api",
"programs/failure",
"programs/noop",
"programs/rewards",
"programs/rewards_api",
"programs/storage",
"programs/storage_api",
"programs/vote",
"programs/vote_api",
"replicator",
"sdk",
"upload-perf",
"vote-signer",
"wallet",
]
exclude = ["programs/bpf/rust/noop"]

106
README.md
View File

@@ -1,5 +1,5 @@
[![Solana crate](https://img.shields.io/crates/v/solana-core.svg)](https://crates.io/crates/solana-core)
[![Solana documentation](https://docs.rs/solana-core/badge.svg)](https://docs.rs/solana-core)
[![Solana crate](https://img.shields.io/crates/v/solana.svg)](https://crates.io/crates/solana)
[![Solana documentation](https://docs.rs/solana/badge.svg)](https://docs.rs/solana)
[![Build status](https://badge.buildkite.com/8cc350de251d61483db98bdfc895b9ea0ac8ffa4a32ee850ed.svg?branch=master)](https://buildkite.com/solana-labs/solana/builds?branch=master)
[![codecov](https://codecov.io/gh/solana-labs/solana/branch/master/graph/badge.svg)](https://codecov.io/gh/solana-labs/solana)
@@ -26,43 +26,9 @@ Furthermore, and much to our surprise, it can be implemented using a mechanism t
Architecture
===
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://solana-labs.github.io/book/).
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
Release Binaries
===
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
Additionally we provide pre-release binaries for the latest code on the edge and
beta channels. Note that these pre-release binaries may be less stable than an
official release.
### Edge channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
### Beta channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
Developing
===
@@ -75,10 +41,10 @@ Install rustc, cargo and rustfmt:
```bash
$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
$ rustup component add rustfmt
$ rustup component add rustfmt-preview
```
If your rustc version is lower than 1.37.0, please update it:
If your rustc version is lower than 1.31.0, please update it:
```bash
$ rustup update
@@ -100,7 +66,7 @@ $ cd solana
Build
```bash
$ cargo build
$ cargo build --all
```
Then to run a minimal local cluster
@@ -114,22 +80,31 @@ Testing
Run the test suite:
```bash
$ cargo test
$ cargo test --all
```
To emulate all the tests that will run on a Pull Request, run:
```bash
$ ./ci/run-local.sh
```
Local Testnet
---
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://solana-labs.github.io/book/getting-started.html).
Remote Testnets
---
We maintain several testnets:
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
* `testnet` - public stable testnet accessible via testnet.solana.com, with an https proxy for web apps at api.testnet.solana.com. Runs 24/7
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
* `testnet-perf` - permissioned stable testnet running a 24/7 soak test
* `testnet-beta-perf` - permissioned beta channel testnet running a multi-hour soak test weekday mornings
* `testnet-edge-perf` - permissioned edge channel testnet running a multi-hour soak test weekday mornings
## Deploy process
@@ -156,47 +131,6 @@ can run your own testnet using the scripts in the `net/` directory.
Edit `ci/testnet-manager.sh`
## Metrics Server Maintenance
Sometimes the dashboard becomes unresponsive. This happens due to glitch in the metrics server.
The current solution is to reset the metrics server. Use the following steps.
1. The server is hosted in a GCP VM instance. Check if the VM instance is down by trying to SSH
into it from the GCP console. The name of the VM is ```metrics-solana-com```.
2. If the VM is inaccessible, reset it from the GCP console.
3. Once VM is up (or, was already up), the metrics services can be restarted from build automation.
1. Navigate to https://buildkite.com/solana-labs/metrics-dot-solana-dot-com in your web browser
2. Click on ```New Build```
3. This will show a pop up dialog. Click on ```options``` drop down.
4. Type in ```FORCE_START=true``` in ```Environment Variables``` text box.
5. Click ```Create Build```
6. This will restart the metrics services, and the dashboards should be accessible afterwards.
## Debugging Testnet
Testnet may exhibit different symptoms of failures. Primary statistics to check are
1. Rise in Confirmation Time
2. Nodes are not voting
3. Panics, and OOM notifications
Check the following if there are any signs of failure.
1. Did testnet deployment fail?
1. View buildkite logs for the last deployment: https://buildkite.com/solana-labs/testnet-management
2. Use the relevant branch
3. If the deployment failed, look at the build logs. The build artifacts for each remote node is uploaded.
It's a good first step to triage from these logs.
2. You may have to log into remote node if the deployment succeeded, but something failed during runtime.
1. Get the private key for the testnet deployment from ```metrics-solana-com``` GCP instance.
2. SSH into ```metrics-solana-com``` using GCP console and do the following.
```bash
sudo bash
cd ~buildkite-agent/.ssh
ls
```
3. Copy the relevant private key to your local machine
4. Find the public IP address of the AWS instance for the remote node using AWS console
5. ```ssh -i <private key file> ubuntu@<ip address of remote node>```
6. The logs are in ```~solana\solana``` folder
Benchmarking
---
@@ -240,3 +174,5 @@ problem is solved by this code?" On the other hand, if a test does fail and you
better way to solve the same problem, a Pull Request with your solution would most certainly be
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
send us that patch!

View File

@@ -59,146 +59,47 @@ There are three release channels that map to branches as follows:
* beta - tracks the largest (and latest) `vX.Y` stabilization branch, more stable.
* stable - tracks the second largest `vX.Y` stabilization branch, most stable.
## Steps to Create a Branch
## Release Steps
### Create the new branch
1. Check out the latest commit on `master` branch:
```
git fetch --all
git checkout upstream/master
```
1. Determine the new branch name. The name should be "v" + the first 2 version fields
### Changing channels
When cutting a new channel branch these pre-steps are required:
1. Pick your branch point for release on master.
1. Create the branch. The name should be "v" + the first 2 "version" fields
from Cargo.toml. For example, a Cargo.toml with version = "0.9.0" implies
the next branch name is "v0.9".
1. Create the new branch and push this branch to the `solana` repository:
```
git checkout -b <branchname>
git push -u origin <branchname>
```
1. Push the new branch to the solana repository
1. Update Cargo.toml on master to the next semantic version (e.g. 0.9.0 -> 0.10.0)
by running `./scripts/increment-cargo-version.sh`, then rebuild with a
`cargo build --all` to cause a refresh of `Cargo.lock`.
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
master branch
### Update master branch with the next version
At this point, ci/channel-info.sh should show your freshly cut release branch as
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
1. After the new branch has been created and pushed, update the Cargo.toml files on **master** to the next semantic version (e.g. 0.9.0 -> 0.10.0) with:
```
scripts/increment-cargo-version.sh minor
```
1. Rebuild to get an updated version of `Cargo.lock`:
```
cargo build
```
1. Push all the changed Cargo.toml and Cargo.lock files to the `master` branch with something like:
```
git co -b version_update
git ls-files -m | xargs git add
git commit -m 'Update Cargo.toml versions from X.Y to X.Y+1'
git push -u origin version_update
```
1. Confirm that your freshly cut release branch is shown as `BETA_CHANNEL` and the previous release branch as `STABLE_CHANNEL`:
```
ci/channel_info.sh
```
### Updating channels (i.e. "making a release")
## Steps to Create a Release
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
### Create the Release Tag on GitHub
1. Go to [GitHub's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
1. Go [there ;)](https://github.com/solana-labs/solana/releases).
1. Click "Draft new release". The release tag must exactly match the `version`
field in `/Cargo.toml` prefixed by `v`.
1. If the Cargo.toml verion field is **0.12.3**, then the release tag must be **v0.12.3**
1. Make sure the Target Branch field matches the branch you want to make a release on.
1. If you want to release v0.12.0, the target branch must be v0.12
1. If this is the first release on the branch (e.g. v0.13.**0**), paste in [this
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md). Engineering Lead can provide summary contents for release notes if needed.
1. Click "Save Draft", then confirm the release notes look good and the tag name and branch are correct. Go back into edit the release and click "Publish release" when ready.
### Update release branch with the next patch version
1. After the new release has been tagged, update the Cargo.toml files on **release branch** to the next semantic version (e.g. 0.9.0 -> 0.9.1) with:
```
scripts/increment-cargo-version.sh patch
```
1. Rebuild to get an updated version of `Cargo.lock`:
```
cargo build
```
1. Push all the changed Cargo.toml and Cargo.lock files to the **release branch** with something like:
```
git co -b version_update
git ls-files -m | xargs git add
git commit -m 'Update Cargo.toml versions from X.Y.Z to X.Y.Z+1'
git push -u origin version_update
```
### Verify release automation success
1. Go to [Solana Releases](https://github.com/solana-labs/solana/releases) and click on the latest release that you just published. Verify that all of the build artifacts are present. This can take up to 90 minutes after creating the tag.
1. The `solana-secondary` Buildkite pipeline handles creating the binary tarballs and updated crates. Look for a job under the tag name of the release: https://buildkite.com/solana-labs/solana-secondary
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
### Update documentation
TODO: Documentation update procedure is WIP as we move to gitbook
Document the new recommended version by updating `book/src/running-replicator.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
#### Publish updated Book
We maintain three copies of the "book" as official documentation:
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
https://solana-labs.github.io/book/
2) "Book-edge" tracks the tip of the master branch and updates automatically.
https://solana-labs.github.io/book-edge/
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
https://solana-labs.github.io/book-beta/
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
```bash
PUBLISH_BOOK_TAG=v0.16.6
```
https://buildkite.com/solana-labs/manual-update-book
### Update software on testnet.solana.com
The testnet running on testnet.solana.com is set to use a fixed release tag
which is set in the Buildkite testnet-management pipeline.
This tag needs to be updated and the testnet restarted after a new release
tag is created.
#### Update testnet schedules
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
testnet-management -> Pipeline Settings -> Schedules
Or just click here:
https://buildkite.com/solana-labs/testnet-management/settings/schedules
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
On each schedule:
1. Set TESTNET_TAG environment variable to the desired release tag.
1. Example, TESTNET_TAG=v0.13.2
1. Set the Build Branch to the branch that TESTNET_TAG is from.
1. Example: v0.13
#### Restart the testnet
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
1. Go to https://buildkite.com/solana-labs/testnet-management
2. Click "New Build" and use the following settings, then click "Create Build"
1. Commit: HEAD
1. Branch: [channel branch as set in the schedules]
1. Environment Variables:
```
TESTNET=testnet
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
TESTNET_OP=create-and-start
```
### Alert the community
Notify Discord users on #validator-support that a new release for
testnet.solana.com is available
field in `/Cargo.toml` prefixed by `v` (ie, `<branchname>.X`).
1. If the first major release on the branch (e.g. v0.8.0), paste in [this
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md)
and fill it in.
1. Test the release by generating a tag using semver's rules. First try at a
release should be `<branchname>.X-rc.0`.
1. Verify release automation:
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
1. ...
1. After testnet deployment, verify that testnets are running correct software.
http://metrics.solana.com should show testnet running on a hash from your
newly created branch.
1. Once the release has been made, update Cargo.toml on release to the next
semantic version (e.g. 0.9.0 -> 0.9.1) by running
`./scripts/increment-cargo-version.sh patch`, then rebuild with a `cargo
build --all` to cause a refresh of `Cargo.lock`.
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
release branch

View File

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

View File

@@ -1,19 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-banking-bench"
version = "0.19.0-pre0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
log = "0.4.6"
rayon = "1.2.0"
solana-core = { path = "../core", version = "0.19.0-pre0" }
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
rand = "0.6.5"
crossbeam-channel = "0.3"

View File

@@ -1,319 +0,0 @@
#[macro_use]
extern crate solana_core;
extern crate crossbeam_channel;
use crossbeam_channel::unbounded;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana_core::bank_forks::BankForks;
use solana_core::banking_stage::{create_test_recorder, BankingStage};
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
use solana_core::cluster_info::ClusterInfo;
use solana_core::cluster_info::Node;
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::PohRecorder;
use solana_core::poh_recorder::WorkingBankEntry;
use solana_core::service::Service;
use solana_measure::measure::Measure;
use solana_runtime::bank::Bank;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::Signature;
use solana_sdk::system_transaction;
use solana_sdk::timing::{duration_as_us, timestamp};
use solana_sdk::transaction::Transaction;
use std::iter;
use std::sync::atomic::Ordering;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex, RwLock};
use std::thread::sleep;
use std::time::{Duration, Instant};
fn check_txs(
receiver: &Arc<Receiver<WorkingBankEntry>>,
ref_tx_count: usize,
poh_recorder: &Arc<Mutex<PohRecorder>>,
) -> bool {
let mut total = 0;
let now = Instant::now();
let mut no_bank = false;
loop {
if let Ok((_bank, (entry, _tick_count))) = receiver.recv_timeout(Duration::from_millis(10))
{
total += entry.transactions.len();
}
if total >= ref_tx_count {
break;
}
if now.elapsed().as_secs() > 60 {
break;
}
if poh_recorder.lock().unwrap().bank().is_none() {
trace!("no bank");
no_bank = true;
break;
}
}
if !no_bank {
assert!(total >= ref_tx_count);
}
no_bank
}
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
(0..txes)
.into_par_iter()
.map(|_| {
let mut new = dummy.clone();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
new.message.account_keys[0] = Pubkey::new_rand();
new.message.account_keys[1] = Pubkey::new_rand();
new.signatures = vec![Signature::new(&sig[0..64])];
new
})
.collect()
}
struct Config {
packets_per_batch: usize,
chunk_len: usize,
num_threads: usize,
}
impl Config {
fn get_transactions_index(&self, chunk_index: usize) -> usize {
chunk_index * (self.chunk_len / self.num_threads) * self.packets_per_batch
}
}
fn bytes_as_usize(bytes: &[u8]) -> usize {
bytes[0] as usize | (bytes[1] as usize) << 8
}
fn main() {
solana_logger::setup();
let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk duplicates to avoid races
const CHUNKS: usize = 8 * 2;
const PACKETS_PER_BATCH: usize = 192;
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
let mint_total = 1_000_000_000_000;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(mint_total);
let (verified_sender, verified_receiver) = unbounded();
let (vote_sender, vote_receiver) = unbounded();
let bank0 = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank0);
let mut bank = bank_forks.working_bank();
info!("threads: {} txs: {}", num_threads, txes);
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_block.hash());
// fund all the accounts
transactions.iter().for_each(|tx| {
let fund = system_transaction::transfer(
&mint_keypair,
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
);
let x = bank.process_transaction(&fund);
x.unwrap();
});
//sanity check, make sure all the transactions can execute sequentially
transactions.iter().for_each(|tx| {
let res = bank.process_transaction(&tx);
assert!(res.is_ok(), "sanity test transactions");
});
bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
for r in res {
assert!(r.is_ok(), "sanity parallel execution");
}
bank.clear_signatures();
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
.into_iter()
.map(|x| {
let len = x.packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
let ledger_path = get_tmp_ledger_path!();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, signal_receiver) =
create_test_recorder(&bank, &blocktree);
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let _banking_stage = BankingStage::new(
&cluster_info,
&poh_recorder,
verified_receiver,
vote_receiver,
);
poh_recorder.lock().unwrap().set_bank(&bank);
let chunk_len = verified.len() / CHUNKS;
let mut start = 0;
// This is so that the signal_receiver does not go out of scope after the closure.
// If it is dropped before poh_service, then poh_service will error when
// calling send() on the channel.
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
let mut total = 0;
let mut tx_total = 0;
let mut txs_processed = 0;
let mut root = 1;
let collector = Pubkey::new_rand();
const ITERS: usize = 1_000;
let config = Config {
packets_per_batch: PACKETS_PER_BATCH,
chunk_len,
num_threads,
};
for _ in 0..ITERS {
let now = Instant::now();
let mut sent = 0;
for (i, v) in verified[start..start + chunk_len]
.chunks(chunk_len / num_threads)
.enumerate()
{
let mut byte = 0;
let index = config.get_transactions_index(start + i);
if index < transactions.len() {
byte = bytes_as_usize(transactions[index].signatures[0].as_ref());
}
trace!(
"sending... {}..{} {} v.len: {} sig: {} transactions.len: {} index: {}",
start + i,
start + chunk_len,
timestamp(),
v.len(),
byte,
transactions.len(),
index,
);
for xv in v {
sent += xv.0.packets.len();
}
verified_sender.send(v.to_vec()).unwrap();
}
let start_tx_index = config.get_transactions_index(start);
let end_tx_index = config.get_transactions_index(start + chunk_len);
for tx in &transactions[start_tx_index..end_tx_index] {
loop {
if bank.get_signature_status(&tx.signatures[0]).is_some() {
break;
}
if poh_recorder.lock().unwrap().bank().is_none() {
break;
}
sleep(Duration::from_millis(5));
}
}
if check_txs(&signal_receiver2, txes / CHUNKS, &poh_recorder) {
debug!(
"resetting bank {} tx count: {} txs_proc: {}",
bank.slot(),
bank.transaction_count(),
txs_processed
);
assert!(txs_processed < bank.transaction_count());
txs_processed = bank.transaction_count();
tx_total += duration_as_us(&now.elapsed());
let mut poh_time = Measure::start("poh_time");
poh_recorder.lock().unwrap().reset(
bank.last_blockhash(),
bank.slot(),
Some((bank.slot(), bank.slot() + 1)),
);
poh_time.stop();
let mut new_bank_time = Measure::start("new_bank");
let new_bank = Bank::new_from_parent(&bank, &collector, bank.slot() + 1);
new_bank_time.stop();
let mut insert_time = Measure::start("insert_time");
bank_forks.insert(new_bank);
bank = bank_forks.working_bank();
insert_time.stop();
poh_recorder.lock().unwrap().set_bank(&bank);
assert!(poh_recorder.lock().unwrap().bank().is_some());
if bank.slot() > 32 {
bank_forks.set_root(root, &None);
root += 1;
}
debug!(
"new_bank_time: {}us insert_time: {}us poh_time: {}us",
new_bank_time.as_us(),
insert_time.as_us(),
poh_time.as_us(),
);
} else {
tx_total += duration_as_us(&now.elapsed());
}
// This signature clear may not actually clear the signatures
// in this chunk, but since we rotate between CHUNKS then
// we should clear them by the time we come around again to re-use that chunk.
bank.clear_signatures();
total += duration_as_us(&now.elapsed());
debug!(
"time: {} us checked: {} sent: {}",
duration_as_us(&now.elapsed()),
txes / CHUNKS,
sent,
);
if bank.slot() > 0 && bank.slot() % 16 == 0 {
for tx in transactions.iter_mut() {
tx.message.recent_blockhash = bank.last_blockhash();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
tx.signatures[0] = Signature::new(&sig[0..64]);
}
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
.into_iter()
.map(|x| {
let len = x.packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
}
start += chunk_len;
start %= verified.len();
}
eprintln!(
"{{'name': 'banking_bench_total', 'median': '{}'}}",
total / ITERS as u64,
);
eprintln!(
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
tx_total / ITERS as u64,
);
drop(vote_sender);
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
sleep(Duration::from_secs(1));
debug!("waited for poh_service");
}
let _unused = Blocktree::destroy(&ledger_path);
}

View File

@@ -1,4 +0,0 @@
/target/
/config/
/config-local/
/farf/

View File

@@ -1,39 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.19.0-pre0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
bincode = "1.1.4"
bs58 = "0.3.0"
clap = "2.32.0"
env_logger = "0.6.2"
itertools = "0.8.0"
log = "0.4.8"
num-derive = "0.2"
num-traits = "0.2"
rand = "0.6.5"
rayon = "1.2.0"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
# solana-runtime = { path = "../solana/runtime"}
solana-core = { path = "../core", version = "0.19.0-pre0" }
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
solana-client = { path = "../client", version = "0.19.0-pre0" }
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.19.0-pre0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.19.0-pre0" }
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
untrusted = "0.7.0"
ws = "0.9.0"

View File

@@ -1,479 +0,0 @@
# token-exchange
Solana Token Exchange Bench
If you can't wait; jump to [Running the exchange](#Running-the-exchange) to
learn how to start and interact with the exchange.
### Table of Contents
[Overview](#Overview)<br>
[Premise](#Premise)<br>
[Exchange startup](#Exchange-startup)<br>
[Order Requests](#Trade-requests)<br>
[Order Cancellations](#Trade-cancellations)<br>
[Trade swap](#Trade-swap)<br>
[Exchange program operations](#Exchange-program-operations)<br>
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
[Investor strategies](#Investor-strategies)<br>
[Running the exchange](#Running-the-exchange)<br>
## Overview
An exchange is a marketplace where one asset can be traded for another. This
demo demonstrates one way to host an exchange on the Solana blockchain by
emulating a currency exchange.
The assets are virtual tokens held by investors who may post order requests to
the exchange. A Matcher monitors the exchange and posts swap requests for
matching orders. All the transactions can execute concurrently.
## Premise
- Exchange
- An exchange is a marketplace where one asset can be traded for another.
The exchange in this demo is the on-chain program that implements the
tokens and the policies for trading those tokens.
- Token
- A virtual asset that can be owned, traded, and holds virtual intrinsic value
compared to other assets. There are four types of tokens in this demo, A,
B, C, D. Each one may be traded for another.
- Token account
- An account owned by the exchange that holds a quantity of one type of token.
- Account request
- A request to create a token account
- Token request
- A request to deposit tokens of a particular type into a token account.
- Asset pair
- A struct with fields Base and Quote, representing the two assets which make up a
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
numerator and the Quote is the denominator for pricing purposes.
- Order side
- Describes which side of the market an investor wants to place a trade on. Options
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
for the Quote asset.
- Price ratio
- An expression of the relative prices of two tokens. Calculated with the Base
Asset as the numerator and the Quote Asset as the denominator. Ratios are
represented as fixed point numbers. The fixed point scaler is defined in
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
- Order request
- A Solana transaction sent by a trader to the exchange to submit an order.
Order requests are made up of the token pair, the order side (bid or ask),
quantity of the primary token, the price ratio, and the two token accounts
to be credited/deducted. An example trade request looks like "T AB 5 2"
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
deducted and 10 B tokens credited to the trade initiator's token accounts.
Successful order requests result in an order.
- Order
- The result of a successful order request. orders are stored in
accounts owned by the submitter of the order request. They can only be
canceled by their owner but can be used by anyone in a trade swap. They
contain the same information as the order request.
- Price spread
- The difference between the two matching orders. The spread is the
profit of the Matcher initiating the swap request.
- Match requirements
- Policies that result in a successful trade swap.
- Match request
- A request to fill two complementary orders (bid/ask), resulting if successful,
in a trade being created.
- Trade
- A successful trade is created from two matching orders that meet
swap requirements which are submitted in a Match Request by a Matcher and
executed by the exchange. A trade may not wholly satisfy one or both of the
orders in which case the orders are adjusted appropriately. Upon execution,
tokens are distributed to the traders' accounts and any overlap or
"negative spread" between orders is deposited into the Matcher's profit
account. All successful trades are recorded in the data of a new solana
account for posterity.
- Investor
- Individual investors who hold a number of tokens and wish to trade them on
the exchange. Investors operate as Solana thin clients who own a set of
accounts containing tokens and/or order requests. Investors post
transactions to the exchange in order to request tokens and post or cancel
order requests.
- Matcher
- An agent who facilitates trading between investors. Matchers operate as
Solana thin clients who monitor all the orders looking for a trade
match. Once found, the Matcher issues a swap request to the exchange.
Matchers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Matchers also
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
information on demand via a public network port.
- Transaction fees
- Solana transaction fees are paid for by the transaction submitters who are
the Investors and Matchers.
## Exchange startup
The exchange is up and running when it reaches a state where it can take
investors' trades and Matchers' match requests. To achieve this state the
following must occur in order:
- Start the Solana blockchain
- Start the thin-client
- The Matcher subscribes to change notifications for all the accounts owned by
the exchange program id. The subscription is managed via Solana's JSON RPC
interface.
- The Matcher starts responding to queries for bid/ask price and OHLCV
The Matcher responding successfully to price and OHLCV requests is the signal to
the investors that trades submitted after that point will be analyzed. <!--This
is not ideal, and instead investors should be able to submit trades at any time,
and the Matcher could come and go without missing a trade. One way to achieve
this is for the Matcher to read the current state of all accounts looking for all
open orders.-->
Investors will initially query the exchange to discover their current balance
for each type of token. If the investor does not already have an account for
each type of token, they will submit account requests. Matcher as well will
request accounts to hold the tokens they earn by initiating trade swaps.
```rust
/// Supported token types
pub enum Token {
A,
B,
C,
D,
}
/// Supported token pairs
pub enum TokenPair {
AB,
AC,
AD,
BC,
BD,
CD,
}
pub enum ExchangeInstruction {
/// New token account
/// key 0 - Signer
/// key 1 - New token account
AccountRequest,
}
/// Token accounts are populated with this structure
pub struct TokenAccountInfo {
/// Investor who owns this account
pub owner: Pubkey,
/// Current number of tokens this account holds
pub tokens: Tokens,
}
```
For this demo investors or Matcher can request more tokens from the exchange at
any time by submitting token requests. In non-demos, an exchange of this type
would provide another way to exchange a 3rd party asset into tokens.
To request tokens, investors submit transfer requests:
```rust
pub enum ExchangeInstruction {
/// Transfer tokens between two accounts
/// key 0 - Account to transfer tokens to
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
/// the exchange has a limitless number of tokens it can transfer.
TransferRequest(Token, u64),
}
```
## Order Requests
When an investor decides to exchange a token of one type for another, they
submit a transaction to the Solana Blockchain containing an order request, which,
if successful, is turned into an order. orders do not expire but are
cancellable. <!-- orders should have a timestamp to enable trade
expiration --> When an order is created, tokens are deducted from a token
account and the order acts as an escrow. The tokens are held until the
order is fulfilled or canceled. If the direction is `To`, then the number
of `tokens` are deducted from the primary account, if `From` then `tokens`
multiplied by `price` are deducted from the secondary account. orders are
no longer valid when the number of `tokens` goes to zero, at which point they
can no longer be used. <!-- Could support refilling orders, so order
accounts are refilled rather than accumulating -->
```rust
/// Direction of the exchange between two tokens in a pair
pub enum Direction {
/// Trade first token type (primary) in the pair 'To' the second
To,
/// Trade first token type in the pair 'From' the second (secondary)
From,
}
pub struct OrderRequestInfo {
/// Direction of trade
pub direction: Direction,
/// Token pair to trade
pub pair: TokenPair,
/// Number of tokens to exchange; refers to the primary or the secondary depending on the direction
pub tokens: u64,
/// The price ratio the primary price over the secondary price. The primary price is fixed
/// and equal to the variable `SCALER`.
pub price: u64,
/// Token account to deposit tokens on successful swap
pub dst_account: Pubkey,
}
pub enum ExchangeInstruction {
/// order request
/// key 0 - Signer
/// key 1 - Account in which to record the swap
/// key 2 - Token account associated with this trade
TradeRequest(TradeRequestInfo),
}
/// Trade accounts are populated with this structure
pub struct TradeOrderInfo {
/// Owner of the order
pub owner: Pubkey,
/// Direction of the exchange
pub direction: Direction,
/// Token pair indicating two tokens to exchange, first is primary
pub pair: TokenPair,
/// Number of tokens to exchange; primary or secondary depending on direction
pub tokens: u64,
/// Scaled price of the secondary token given the primary is equal to the scale value
/// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens
pub price: u64,
/// account which the tokens were source from. The trade account holds the tokens in escrow
/// until either one or more part of a swap or the trade is canceled.
pub src_account: Pubkey,
/// account which the tokens the tokens will be deposited into on a successful trade
pub dst_account: Pubkey,
}
```
## Order cancellations
An investor may cancel a trade at anytime, but only trades they own. If the
cancellation is successful, any tokens held in escrow are returned to the
account from which they came.
```rust
pub enum ExchangeInstruction {
/// order cancellation
/// key 0 - Signer
/// key 1 -order to cancel
TradeCancellation,
}
```
## Trade swaps
The Matcher is monitoring the accounts assigned to the exchange program and
building a trade-order table. The order table is used to identify
matching orders which could be fulfilled. When a match is found the
Matcher should issue a swap request. Swap requests may not satisfy the entirety
of either order, but the exchange will greedily fulfill it. Any leftover tokens
in either account will keep the order valid for further swap requests in
the future.
Matching orders are defined by the following swap requirements:
- Opposite polarity (one `To` and one `From`)
- Operate on the same token pair
- The price ratio of the `From` order is greater than or equal to the `To` order
- There are sufficient tokens to perform the trade
Orders can be written in the following format:
`investor direction pair quantity price-ratio`
For example:
- `1 T AB 2 1`
- Investor 1 wishes to exchange 2 A tokens to B tokens at a ratio of 1 A to 1
B
- `2 F AC 6 1.2`
- Investor 2 wishes to exchange A tokens from 6 B tokens at a ratio of 1 A
from 1.2 B
An order table could look something like the following. Notice how the columns
are sorted low to high and high to low, respectively. Prices are dramatic and
whole for clarity.
|Row| To | From |
|---|-------------|------------|
| 1 | 1 T AB 2 4 | 2 F AB 2 8 |
| 2 | 1 T AB 1 4 | 2 F AB 2 8 |
| 3 | 1 T AB 6 6 | 2 F AB 2 7 |
| 4 | 1 T AB 2 8 | 2 F AB 3 6 |
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
As part of a successful swap request, the exchange will credit tokens to the
Matcher's account equal to the difference in the price ratios or the two orders.
These tokens are considered the Matcher's profit for initiating the trade.
The Matcher would initiate the following swap on the order table above:
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
- Matcher takes 8 B tokens as profit
Both row 1 trades are fully realized, table becomes:
|Row| To | From |
|---|-------------|------------|
| 1 | 1 T AB 1 4 | 2 F AB 2 8 |
| 2 | 1 T AB 6 6 | 2 F AB 2 7 |
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
The Matcher would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
- Matcher takes 4 B tokens as profit
Row 1 From is not fully realized, table becomes:
|Row| To | From |
|---|-------------|------------|
| 1 | 1 T AB 6 6 | 2 F AB 1 8 |
| 2 | 1 T AB 2 8 | 2 F AB 2 7 |
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
| 4 | | 2 F AB 1 5 |
The Matcher would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
- Matcher takes 2 B tokens as profit
Row 1 To is now fully realized, table becomes:
|Row| To | From |
|---|-------------|------------|
| 1 | 1 T AB 5 6 | 2 F AB 2 7 |
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
The Matcher would initiate the following last swap:
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
- Matcher takes 4 B tokens as profit
Table becomes:
|Row| To | From |
|---|-------------|------------|
| 1 | 1 T AB 3 6 | 2 F AB 3 5 |
| 2 | 1 T AB 2 8 | 2 F AB 1 5 |
| 3 | 1 T AB 2 10 | |
At this point the lowest To's price is larger than the largest From's price so
no more swaps would be initiated until new orders came in.
```rust
pub enum ExchangeInstruction {
/// Trade swap request
/// key 0 - Signer
/// key 1 - Account in which to record the swap
/// key 2 - 'To' order
/// key 3 - `From` order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
SwapRequest,
}
/// Swap accounts are populated with this structure
pub struct TradeSwapInfo {
/// Pair swapped
pub pair: TokenPair,
/// `To` order
pub to_trade_order: Pubkey,
/// `From` order
pub from_trade_order: Pubkey,
/// Number of primary tokens exchanged
pub primary_tokens: u64,
/// Price the primary tokens were exchanged for
pub primary_price: u64,
/// Number of secondary tokens exchanged
pub secondary_tokens: u64,
/// Price the secondary tokens were exchanged for
pub secondary_price: u64,
}
```
## Exchange program operations
Putting all the commands together from above, the following operations will be
supported by the on-chain exchange program:
```rust
pub enum ExchangeInstruction {
/// New token account
/// key 0 - Signer
/// key 1 - New token account
AccountRequest,
/// Transfer tokens between two accounts
/// key 0 - Account to transfer tokens to
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
/// the exchange has a limitless number of tokens it can transfer.
TransferRequest(Token, u64),
/// order request
/// key 0 - Signer
/// key 1 - Account in which to record the swap
/// key 2 - Token account associated with this trade
TradeRequest(TradeRequestInfo),
/// order cancellation
/// key 0 - Signer
/// key 1 -order to cancel
TradeCancellation,
/// Trade swap request
/// key 0 - Signer
/// key 1 - Account in which to record the swap
/// key 2 - 'To' order
/// key 3 - `From` order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
SwapRequest,
}
```
## Quotes and OHLCV
The Matcher will provide current bid/ask price quotes based on trade actively and
also provide OHLCV based on some time window. The details of how the bid/ask
price quotes are calculated are yet to be decided.
## Investor strategies
To make a compelling demo, the investors needs to provide interesting trade
behavior. Something as simple as a randomly twiddled baseline would be a
minimum starting point.
## Running the exchange
The exchange bench posts trades and swaps matches as fast as it can.
You might want to bump the duration up
to 60 seconds and the batch size to 1000 for better numbers. You can modify those
in client_demo/src/demo.rs::test_exchange_local_cluster.
The following command runs the bench:
```bash
$ RUST_LOG=solana_bench_exchange=info cargo test --release -- --nocapture test_exchange_local_cluster
```
To also see the cluster messages:
```bash
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
```

View File

@@ -1,971 +0,0 @@
#![allow(clippy::useless_attribute)]
use crate::order_book::*;
use itertools::izip;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*;
use solana_exchange_api::id;
use solana_genesis::PrimordialAccountDetails;
use solana_metrics::datapoint_info;
use solana_sdk::client::Client;
use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::transaction::Transaction;
use solana_sdk::{system_instruction, system_program};
use std::cmp;
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::prelude::*;
use std::mem;
use std::net::SocketAddr;
use std::path::Path;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, RwLock};
use std::thread::{sleep, Builder};
use std::time::{Duration, Instant};
// TODO Chunk length as specified results in a bunch of failures, divide by 10 helps...
// Assume 4MB network buffers, and 512 byte packets
const FUND_CHUNK_LEN: usize = 4 * 1024 * 1024 / 512;
// Maximum system transfers per transaction
const MAX_TRANSFERS_PER_TX: u64 = 4;
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<Transaction>>>>;
pub struct Config {
pub identity: Keypair,
pub threads: usize,
pub duration: Duration,
pub transfer_delay: u64,
pub fund_amount: u64,
pub batch_size: usize,
pub chunk_size: usize,
pub account_groups: usize,
pub client_ids_and_stake_file: String,
pub read_from_client_file: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
identity: Keypair::new(),
threads: 4,
duration: Duration::new(u64::max_value(), 0),
transfer_delay: 0,
fund_amount: 100_000,
batch_size: 10,
chunk_size: 10,
account_groups: 100,
client_ids_and_stake_file: String::new(),
read_from_client_file: false,
}
}
}
pub fn create_client_accounts_file(
client_ids_and_stake_file: &str,
batch_size: usize,
account_groups: usize,
fund_amount: u64,
) {
let accounts_in_groups = batch_size * account_groups;
const NUM_KEYPAIR_GROUPS: u64 = 2;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
let keypairs = generate_keypairs(total_keys);
let mut accounts = HashMap::new();
keypairs.iter().for_each(|keypair| {
accounts.insert(
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
PrimordialAccountDetails {
balance: fund_amount,
executable: false,
owner: system_program::id().to_string(),
data: String::new(),
},
);
});
let serialized = serde_yaml::to_string(&accounts).unwrap();
let path = Path::new(&client_ids_and_stake_file);
let mut file = File::create(path).unwrap();
file.write_all(&serialized.into_bytes()).unwrap();
}
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
where
T: 'static + Client + Send + Sync,
{
let Config {
identity,
threads,
duration,
transfer_delay,
fund_amount,
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
read_from_client_file,
} = config;
info!(
"Exchange client: threads {} duration {} fund_amount {}",
threads,
duration_as_s(&duration),
fund_amount
);
info!(
"Exchange client: transfer delay {} batch size {} chunk size {}",
transfer_delay, batch_size, chunk_size
);
let accounts_in_groups = batch_size * account_groups;
const NUM_KEYPAIR_GROUPS: u64 = 2;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
let mut signer_keypairs = if read_from_client_file {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
let accounts: HashMap<String, PrimordialAccountDetails> =
serde_yaml::from_reader(file).unwrap();
accounts
.into_iter()
.map(|(keypair, _)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
Keypair::from_bytes(&bytes).unwrap()
})
.collect()
} else {
info!("Generating {:?} signer keys", total_keys);
generate_keypairs(total_keys)
};
let trader_signers: Vec<_> = signer_keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let swapper_signers: Vec<_> = signer_keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
let client = clients[0].as_ref();
if !read_from_client_file {
info!("Fund trader accounts");
fund_keys(client, &identity, &trader_signers, fund_amount);
info!("Fund swapper accounts");
fund_keys(client, &identity, &swapper_signers, fund_amount);
}
info!("Generating {:?} account keys", total_keys);
let mut account_keypairs = generate_keypairs(total_keys);
let src_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
let profit_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
info!("Create {:?} source token accounts", src_pubkeys.len());
create_token_accounts(client, &trader_signers, &src_pubkeys);
info!("Create {:?} profit token accounts", profit_pubkeys.len());
create_token_accounts(client, &swapper_signers, &profit_pubkeys);
// Collect the max transaction rate and total tx count seen (single node only)
let sample_stats = Arc::new(RwLock::new(Vec::new()));
let sample_period = 1; // in seconds
info!("Sampling clients for tps every {} s", sample_period);
info!(
"Requesting and swapping trades with {} ms delay per thread...",
transfer_delay
);
let exit_signal = Arc::new(AtomicBool::new(false));
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
let total_txs_sent_count = Arc::new(AtomicUsize::new(0));
let s_threads: Vec<_> = (0..threads)
.map(|_| {
let exit_signal = exit_signal.clone();
let shared_txs = shared_txs.clone();
let total_txs_sent_count = total_txs_sent_count.clone();
let client = clients[0].clone();
Builder::new()
.name("solana-exchange-transfer".to_string())
.spawn(move || {
do_tx_transfers(&exit_signal, &shared_txs, &total_txs_sent_count, &client)
})
.unwrap()
})
.collect();
trace!("Start swapper thread");
let (swapper_sender, swapper_receiver) = channel();
let swapper_thread = {
let exit_signal = exit_signal.clone();
let shared_txs = shared_txs.clone();
let client = clients[0].clone();
Builder::new()
.name("solana-exchange-swapper".to_string())
.spawn(move || {
swapper(
&exit_signal,
&swapper_receiver,
&shared_txs,
&swapper_signers,
&profit_pubkeys,
transfer_delay,
batch_size,
chunk_size,
account_groups,
&client,
)
})
.unwrap()
};
trace!("Start trader thread");
let trader_thread = {
let exit_signal = exit_signal.clone();
let shared_txs = shared_txs.clone();
let client = clients[0].clone();
Builder::new()
.name("solana-exchange-trader".to_string())
.spawn(move || {
trader(
&exit_signal,
&swapper_sender,
&shared_txs,
&trader_signers,
&src_pubkeys,
transfer_delay,
batch_size,
chunk_size,
account_groups,
&client,
)
})
.unwrap()
};
let sample_threads: Vec<_> = clients
.iter()
.map(|client| {
let exit_signal = exit_signal.clone();
let sample_stats = sample_stats.clone();
let client = client.clone();
Builder::new()
.name("solana-exchange-sample".to_string())
.spawn(move || sample_txs(&exit_signal, &sample_stats, sample_period, &client))
.unwrap()
})
.collect();
sleep(duration);
info!("Stopping threads");
exit_signal.store(true, Ordering::Relaxed);
info!("Wait for trader thread");
let _ = trader_thread.join();
info!("Waiting for swapper thread");
let _ = swapper_thread.join();
info!("Wait for tx threads");
for t in s_threads {
let _ = t.join();
}
info!("Wait for sample threads");
for t in sample_threads {
let _ = t.join();
}
compute_and_report_stats(
&sample_stats,
total_txs_sent_count.load(Ordering::Relaxed) as u64,
);
}
fn do_tx_transfers<T>(
exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions,
total_txs_sent_count: &Arc<AtomicUsize>,
client: &Arc<T>,
) where
T: Client,
{
loop {
let txs;
{
let mut shared_txs_wl = shared_txs.write().unwrap();
txs = shared_txs_wl.pop_front();
}
if let Some(txs0) = txs {
let n = txs0.len();
let now = Instant::now();
for tx in txs0 {
client.async_send_transaction(tx).expect("Transfer");
}
let duration = now.elapsed();
total_txs_sent_count.fetch_add(n, Ordering::Relaxed);
datapoint_info!(
"bench-exchange-do_tx_transfers",
("duration", duration_as_ms(&duration), i64),
("count", n, i64)
);
}
if exit_signal.load(Ordering::Relaxed) {
return;
}
}
}
struct TradeInfo {
trade_account: Pubkey,
order_info: OrderInfo,
}
#[allow(clippy::too_many_arguments)]
fn swapper<T>(
exit_signal: &Arc<AtomicBool>,
receiver: &Receiver<Vec<TradeInfo>>,
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
profit_pubkeys: &[Pubkey],
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
client: &Arc<T>,
) where
T: Client,
{
let mut order_book = OrderBook::default();
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
// Chunks may have been dropped and we don't want to wait a long time
// for each time, Back-off each time we fail to confirm a chunk
const CHECK_TX_TIMEOUT_MAX_MS: u64 = 15000;
const CHECK_TX_DELAY_MS: u64 = 100;
let mut max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
// If we dump too many chunks maybe we are just waiting on a back-log
// rather than a series of dropped packets, reset to max waits
const MAX_DUMPS: u64 = 50;
let mut dumps = 0;
'outer: loop {
if let Ok(trade_infos) = receiver.try_recv() {
let mut tries = 0;
let mut trade_index = 0;
while client
.get_balance(&trade_infos[trade_index].trade_account)
.unwrap_or(0)
== 0
{
tries += 1;
if tries >= max_tries {
if exit_signal.load(Ordering::Relaxed) {
break 'outer;
}
error!("Give up and dump batch");
if dumps >= MAX_DUMPS {
error!("Max batches dumped, reset wait back-off");
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
} else {
dumps += 1;
max_tries /= 2;
}
continue 'outer;
}
debug!("{} waiting for trades batch to clear", tries);
sleep(Duration::from_millis(CHECK_TX_DELAY_MS));
trade_index = thread_rng().gen_range(0, trade_infos.len());
}
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
trade_infos.iter().for_each(|info| {
order_book
.push(info.trade_account, info.order_info)
.expect("Failed to push to order_book");
});
let mut swaps = Vec::new();
while let Some((to, from)) = order_book.pop() {
swaps.push((to, from));
if swaps.len() >= batch_size {
break;
}
}
let swaps_size = swaps.len();
let mut to_swap = vec![];
let start = account_group * swaps_size as usize;
let end = account_group * swaps_size as usize + batch_size as usize;
for (signer, swap, profit) in izip!(
signers[start..end].iter(),
swaps,
profit_pubkeys[start..end].iter(),
) {
to_swap.push((signer, swap, profit));
}
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
.par_iter()
.map(|(signer, swap, profit)| {
let s: &Keypair = &signer;
let owner = &signer.pubkey();
Transaction::new_signed_instructions(
&[s],
vec![exchange_instruction::swap_request(
owner,
&swap.0.pubkey,
&swap.1.pubkey,
&profit,
)],
blockhash,
)
})
.collect();
txs += to_swap_txs.len() as u64;
total_txs += to_swap_txs.len() as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Swapper {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
}
datapoint_info!("bench-exchange-swaps", ("count", to_swap_txs.len(), i64));
let chunks: Vec<_> = to_swap_txs.chunks(chunk_size).collect();
{
let mut shared_txs_wl = shared_txs.write().unwrap();
for chunk in chunks {
shared_txs_wl.push_back(chunk.to_vec());
}
}
// Throttle the swapper so it doesn't try to catchup unbridled
sleep(Duration::from_millis(transfer_delay / 2));
}
if exit_signal.load(Ordering::Relaxed) {
break 'outer;
}
}
info!(
"Swapper sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
);
assert_eq!(
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
0
);
}
#[allow(clippy::too_many_arguments)]
fn trader<T>(
exit_signal: &Arc<AtomicBool>,
sender: &Sender<Vec<TradeInfo>>,
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
srcs: &[Pubkey],
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
client: &Arc<T>,
) where
T: Client,
{
// TODO Hard coded for now
let pair = AssetPair::default();
let tokens = 1;
let price = 1000;
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
loop {
let trade_keys = generate_keypairs(batch_size as u64);
let mut trades = vec![];
let mut trade_infos = vec![];
let start = account_group * batch_size as usize;
let end = account_group * batch_size as usize + batch_size as usize;
let mut side = OrderSide::Ask;
for (signer, trade, src) in izip!(
signers[start..end].iter(),
trade_keys,
srcs[start..end].iter(),
) {
side = if side == OrderSide::Ask {
OrderSide::Bid
} else {
OrderSide::Ask
};
let order_info = OrderInfo {
/// Owner of the trade order
owner: Pubkey::default(), // don't care
side,
pair,
tokens,
price,
tokens_settled: 0,
};
trade_infos.push(TradeInfo {
trade_account: trade.pubkey(),
order_info,
});
trades.push((signer, trade.pubkey(), side, src));
}
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| {
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, trade, side, src)| {
let s: &Keypair = &signer;
let owner = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
Transaction::new_signed_instructions(
&[s],
vec![
system_instruction::create_account(owner, trade, 1, space, &id()),
exchange_instruction::trade_request(
owner, trade, *side, pair, tokens, price, src,
),
],
blockhash,
)
})
.collect();
{
txs += chunk_size as u64;
total_txs += chunk_size as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Trader {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
}
datapoint_info!("bench-exchange-trades", ("count", trades_txs.len(), i64));
{
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
shared_txs_wl.push_back(trades_txs);
}
}
if transfer_delay > 0 {
sleep(Duration::from_millis(transfer_delay));
}
});
if exit_signal.load(Ordering::Relaxed) {
info!(
"Trader sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
);
return;
}
// TODO chunk the trade infos and send them when the batch is sent
sender
.send(trade_infos)
.expect("Failed to send trades to swapper");
}
}
fn verify_transfer<T>(sync_client: &T, tx: &Transaction) -> bool
where
T: SyncClient + ?Sized,
{
for s in &tx.signatures {
if let Ok(Some(r)) = sync_client.get_signature_status(s) {
match r {
Ok(_) => {
return true;
}
Err(e) => {
info!("error: {:?}", e);
}
}
}
}
false
}
fn verify_funding_transfer<T: SyncClient + ?Sized>(
client: &T,
tx: &Transaction,
amount: u64,
) -> bool {
for a in &tx.message().account_keys[1..] {
if client.get_balance(a).unwrap_or(0) >= amount {
return true;
}
}
false
}
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
let total = lamports * (dests.len() as u64 + 1);
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
info!(
" Funding {} keys with {} lamports each",
dests.len(),
lamports
);
while !notfunded.is_empty() {
if funded.is_empty() {
panic!("No funded accounts left to fund remaining");
}
let mut new_funded: Vec<(&Keypair, u64)> = vec![];
let mut to_fund = vec![];
debug!(" Creating from... {}", funded.len());
for f in &mut funded {
let max_units = cmp::min(
cmp::min(notfunded.len() as u64, MAX_TRANSFERS_PER_TX),
(f.1 - lamports) / lamports,
);
if max_units == 0 {
continue;
}
let per_unit = ((f.1 - lamports) / lamports / max_units) * lamports;
f.1 -= per_unit * max_units;
let start = notfunded.len() - max_units as usize;
let moves: Vec<_> = notfunded[start..]
.iter()
.map(|k| (k.pubkey(), per_unit))
.collect();
notfunded[start..]
.iter()
.for_each(|k| new_funded.push((k, per_unit)));
notfunded.truncate(start);
if !moves.is_empty() {
to_fund.push((f.0, moves));
}
}
to_fund.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
#[allow(clippy::clone_double_ref)] // sigh
let mut to_fund_txs: Vec<_> = chunk
.par_iter()
.map(|(k, m)| {
(
k.clone(),
Transaction::new_unsigned_instructions(system_instruction::transfer_many(
&k.pubkey(),
&m,
)),
)
})
.collect();
let mut retries = 0;
let amount = chunk[0].1[0].1;
while !to_fund_txs.is_empty() {
let receivers = to_fund_txs
.iter()
.fold(0, |len, (_, tx)| len + tx.message().instructions.len());
debug!(
" {} to {} in {} txs",
if retries == 0 {
" Transferring"
} else {
" Retrying"
},
receivers,
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) =
client.get_recent_blockhash().expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
to_fund_txs.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
});
let mut waits = 0;
loop {
sleep(Duration::from_millis(200));
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, &tx, amount));
if to_fund_txs.is_empty() {
break;
}
debug!(
" {} transactions outstanding, {:?} waits",
to_fund_txs.len(),
waits
);
waits += 1;
if waits >= 5 {
break;
}
}
if !to_fund_txs.is_empty() {
retries += 1;
debug!(" Retry {:?}", retries);
if retries >= 10 {
error!("fund_keys: Too many retries ({}), give up", retries);
exit(1);
}
}
}
});
funded.append(&mut new_funded);
funded.retain(|(k, b)| {
client.get_balance(&k.pubkey()).unwrap_or(0) > lamports && *b > lamports
});
debug!(" Funded: {} left: {}", funded.len(), notfunded.len());
}
}
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
let mut notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = signers.iter().zip(accounts).collect();
while !notfunded.is_empty() {
notfunded.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
let mut to_create_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, new)| {
let owner_pubkey = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
let create_ix =
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
(
signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
)
})
.collect();
let accounts = to_create_txs
.iter()
.fold(0, |len, (_, tx)| len + tx.message().instructions.len() / 2);
debug!(
" Creating {} accounts in {} txs",
accounts,
to_create_txs.len(),
);
let mut retries = 0;
while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
let kp: &Keypair = k;
tx.sign(&[kp], blockhash);
});
to_create_txs.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
});
let mut waits = 0;
while !to_create_txs.is_empty() {
sleep(Duration::from_millis(200));
to_create_txs.retain(|(_, tx)| !verify_transfer(client, &tx));
if to_create_txs.is_empty() {
break;
}
debug!(
" {} transactions outstanding, waits {:?}",
to_create_txs.len(),
waits
);
waits += 1;
if waits >= 5 {
break;
}
}
if !to_create_txs.is_empty() {
retries += 1;
debug!(" Retry {:?}", retries);
if retries >= 20 {
error!(
"create_token_accounts: Too many retries ({}), give up",
retries
);
exit(1);
}
}
}
});
let mut new_notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = vec![];
for f in &notfunded {
if client.get_balance(&f.1).unwrap_or(0) == 0 {
new_notfunded.push(*f)
}
}
notfunded = new_notfunded;
debug!(" Left: {}", notfunded.len());
}
}
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, total_txs_sent: u64) {
let mut max_txs = 0;
let mut max_elapsed = Duration::new(0, 0);
info!("| Max TPS | Total Transactions");
info!("+---------------+--------------------");
for (_sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
0 => "!!!!!",
_ => "",
};
info!("| {:13.2} | {} {}", stats.tps, stats.txs, maybe_flag);
if stats.elapsed > max_elapsed {
max_elapsed = stats.elapsed;
}
if stats.txs > max_txs {
max_txs = stats.txs;
}
}
info!("+---------------+--------------------");
if max_txs >= total_txs_sent {
info!(
"Warning: Average TPS might be under reported, there were no txs sent for a portion of the duration"
);
max_txs = total_txs_sent;
}
info!(
"{} txs outstanding when test ended (lag) ({:.2}%)",
total_txs_sent - max_txs,
(total_txs_sent - max_txs) as f64 / total_txs_sent as f64 * 100_f64
);
info!(
"\tAverage TPS: {:.2}",
max_txs as f32 / max_elapsed.as_secs() as f32
);
}
fn generate_keypairs(num: u64) -> Vec<Keypair> {
let mut seed = [0_u8; 32];
seed.copy_from_slice(&Keypair::new().pubkey().as_ref());
let mut rnd = GenKeys::new(seed);
rnd.gen_n_keypairs(num)
}
pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
let balance = client.get_balance(&id.pubkey());
let balance = balance.unwrap_or(0);
if balance >= amount {
return;
}
let amount_to_drop = amount - balance;
info!(
"Airdropping {:?} lamports from {} for {}",
amount_to_drop,
drone_addr,
id.pubkey(),
);
let mut tries = 0;
loop {
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap();
for _ in 0..30 {
if let Ok(Some(_)) = client.get_signature_status(&signature) {
break;
}
sleep(Duration::from_millis(100));
}
if client.get_balance(&id.pubkey()).unwrap_or(0) >= amount {
break;
}
}
Err(err) => {
panic!(
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
err, drone_addr, amount
);
}
};
debug!(" Retry...");
tries += 1;
if tries > 50 {
error!("airdrop_lamports: Too many retries ({}), give up", tries);
exit(1);
}
sleep(Duration::from_secs(2));
}
}

View File

@@ -1,218 +0,0 @@
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::DRONE_PORT;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub identity: Keypair,
pub threads: usize,
pub num_nodes: usize,
pub duration: Duration,
pub transfer_delay: u64,
pub fund_amount: u64,
pub batch_size: usize,
pub chunk_size: usize,
pub account_groups: usize,
pub client_ids_and_stake_file: String,
pub write_to_client_file: bool,
pub read_from_client_file: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
identity: Keypair::new(),
num_nodes: 1,
threads: 4,
duration: Duration::new(u64::max_value(), 0),
transfer_delay: 0,
fund_amount: 100_000,
batch_size: 100,
chunk_size: 100,
account_groups: 100,
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
}
}
}
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!())
.about(crate_description!())
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
.short("n")
.long("entrypoint")
.value_name("HOST:PORT")
.takes_value(true)
.required(false)
.default_value("127.0.0.1:8001")
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
.short("d")
.long("drone")
.value_name("HOST:PORT")
.takes_value(true)
.required(false)
.default_value("127.0.0.1:9900")
.help("Location of the drone; defaults to 127.0.0.1:9900"),
)
.arg(
Arg::with_name("identity")
.short("i")
.long("identity")
.value_name("PATH")
.takes_value(true)
.help("File containing a client identity (keypair)"),
)
.arg(
Arg::with_name("threads")
.long("threads")
.value_name("<threads>")
.takes_value(true)
.required(false)
.default_value("1")
.help("Number of threads submitting transactions"),
)
.arg(
Arg::with_name("num-nodes")
.long("num-nodes")
.value_name("NUM")
.takes_value(true)
.required(false)
.default_value("1")
.help("Wait for NUM nodes to converge"),
)
.arg(
Arg::with_name("duration")
.long("duration")
.value_name("SECS")
.takes_value(true)
.default_value("60")
.help("Seconds to run benchmark, then exit; default is forever"),
)
.arg(
Arg::with_name("transfer-delay")
.long("transfer-delay")
.value_name("<delay>")
.takes_value(true)
.required(false)
.default_value("0")
.help("Delay between each chunk"),
)
.arg(
Arg::with_name("fund-amount")
.long("fund-amount")
.value_name("<fund>")
.takes_value(true)
.required(false)
.default_value("100000")
.help("Number of lamports to fund to each signer"),
)
.arg(
Arg::with_name("batch-size")
.long("batch-size")
.value_name("<batch>")
.takes_value(true)
.required(false)
.default_value("1000")
.help("Number of transactions before the signer rolls over"),
)
.arg(
Arg::with_name("chunk-size")
.long("chunk-size")
.value_name("<cunk>")
.takes_value(true)
.required(false)
.default_value("500")
.help("Number of transactions to generate and send at a time"),
)
.arg(
Arg::with_name("account-groups")
.long("account-groups")
.value_name("<groups>")
.takes_value(true)
.required(false)
.default_value("10")
.help("Number of account groups to cycle for each batch"),
)
.arg(
Arg::with_name("write-client-keys")
.long("write-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Generate client keys and stakes and write the list to YAML file"),
)
.arg(
Arg::with_name("read-client-keys")
.long("read-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Read client keys and stakes from the YAML file"),
)
}
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse drone address: {}", e);
exit(1)
});
if matches.is_present("identity") {
args.identity = read_keypair(matches.value_of("identity").unwrap())
.expect("can't read client identity");
} else {
args.identity = {
let seed = [42_u8; 32];
let mut rnd = GenKeys::new(seed);
rnd.gen_keypair()
};
}
args.threads = value_t!(matches.value_of("threads"), usize).expect("Failed to parse threads");
args.num_nodes =
value_t!(matches.value_of("num-nodes"), usize).expect("Failed to parse num-nodes");
let duration = value_t!(matches.value_of("duration"), u64).expect("Failed to parse duration");
args.duration = Duration::from_secs(duration);
args.transfer_delay =
value_t!(matches.value_of("transfer-delay"), u64).expect("Failed to parse transfer-delay");
args.fund_amount =
value_t!(matches.value_of("fund-amount"), u64).expect("Failed to parse fund-amount");
args.batch_size =
value_t!(matches.value_of("batch-size"), usize).expect("Failed to parse batch-size");
args.chunk_size =
value_t!(matches.value_of("chunk-size"), usize).expect("Failed to parse chunk-size");
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
.expect("Failed to parse account-groups");
if let Some(s) = matches.value_of("write-client-keys") {
args.write_to_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(s) = matches.value_of("read-client-keys") {
assert!(!args.write_to_client_file);
args.read_from_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
args
}

View File

@@ -1,3 +0,0 @@
pub mod bench;
pub mod cli;
mod order_book;

View File

@@ -1,83 +0,0 @@
pub mod bench;
mod cli;
pub mod order_book;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::KeypairUtil;
fn main() {
solana_logger::setup();
solana_metrics::set_panic_hook("bench-exchange");
let matches = cli::build_args().get_matches();
let cli_config = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
drone_addr,
identity,
threads,
num_nodes,
duration,
transfer_delay,
fund_amount,
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
..
} = cli_config;
let config = Config {
identity,
threads,
duration,
transfer_delay,
fund_amount,
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
read_from_client_file,
};
if write_to_client_file {
create_client_accounts_file(
&config.client_ids_and_stake_file,
config.batch_size,
config.account_groups,
config.fund_amount,
);
} else {
info!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let (client, num_clients) = get_multi_client(&nodes);
info!("{} nodes found", num_clients);
if num_clients < num_nodes {
panic!("Error: Insufficient nodes discovered");
}
if !read_from_client_file {
info!("Funding keypair: {}", config.identity.pubkey());
let accounts_in_groups = batch_size * account_groups;
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&client,
&drone_addr,
&config.identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
}
do_bench_exchange(vec![client], config);
}
}

View File

@@ -1,134 +0,0 @@
use itertools::EitherOrBoth::{Both, Left, Right};
use itertools::Itertools;
use log::*;
use solana_exchange_api::exchange_state::*;
use solana_sdk::pubkey::Pubkey;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::{error, fmt};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ToOrder {
pub pubkey: Pubkey,
pub info: OrderInfo,
}
impl Ord for ToOrder {
fn cmp(&self, other: &Self) -> Ordering {
other.info.price.cmp(&self.info.price)
}
}
impl PartialOrd for ToOrder {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FromOrder {
pub pubkey: Pubkey,
pub info: OrderInfo,
}
impl Ord for FromOrder {
fn cmp(&self, other: &Self) -> Ordering {
self.info.price.cmp(&other.info.price)
}
}
impl PartialOrd for FromOrder {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Default)]
pub struct OrderBook {
// TODO scale to x token types
to_ab: BinaryHeap<ToOrder>,
from_ab: BinaryHeap<FromOrder>,
}
impl fmt::Display for OrderBook {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"+-Order Book--------------------------+-------------------------------------+"
)?;
for (i, it) in self
.to_ab
.iter()
.zip_longest(self.from_ab.iter())
.enumerate()
{
match it {
Both(to, from) => writeln!(
f,
"| T AB {:8} for {:8}/{:8} | F AB {:8} for {:8}/{:8} |{}",
to.info.tokens,
SCALER,
to.info.price,
from.info.tokens,
SCALER,
from.info.price,
i
)?,
Left(to) => writeln!(
f,
"| T AB {:8} for {:8}/{:8} | |{}",
to.info.tokens, SCALER, to.info.price, i
)?,
Right(from) => writeln!(
f,
"| | F AB {:8} for {:8}/{:8} |{}",
from.info.tokens, SCALER, from.info.price, i
)?,
}
}
write!(
f,
"+-------------------------------------+-------------------------------------+"
)?;
Ok(())
}
}
impl OrderBook {
// TODO
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
// Ok(())
// }
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
check_trade(info.side, info.tokens, info.price)?;
match info.side {
OrderSide::Ask => {
self.to_ab.push(ToOrder { pubkey, info });
}
OrderSide::Bid => {
self.from_ab.push(FromOrder { pubkey, info });
}
}
Ok(())
}
pub fn pop(&mut self) -> Option<(ToOrder, FromOrder)> {
if let Some(pair) = Self::pop_pair(&mut self.to_ab, &mut self.from_ab) {
return Some(pair);
}
None
}
pub fn get_num_outstanding(&self) -> (usize, usize) {
(self.to_ab.len(), self.from_ab.len())
}
fn pop_pair(
to_ab: &mut BinaryHeap<ToOrder>,
from_ab: &mut BinaryHeap<FromOrder>,
) -> Option<(ToOrder, FromOrder)> {
let to = to_ab.peek()?;
let from = from_ab.peek()?;
if from.info.price < to.info.price {
debug!("Trade not viable");
return None;
}
let to = to_ab.pop()?;
let from = from_ab.pop()?;
Some((to, from))
}
}

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
use clap::{crate_description, crate_name, crate_version, App, Arg};
use solana_core::packet::PacketsRecycler;
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
use solana_core::result::Result;
use solana_core::streamer::{receiver, PacketReceiver};
use clap::{App, Arg};
use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE};
use solana::result::Result;
use solana::streamer::{receiver, PacketReceiver};
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@@ -15,19 +14,19 @@ use std::time::SystemTime;
fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
let mut msgs = Packets::default();
msgs.packets.resize(10, Packet::default());
for w in msgs.packets.iter_mut() {
let msgs = SharedPackets::default();
let msgs_ = msgs.clone();
msgs.write().unwrap().packets.resize(10, Packet::default());
for w in &mut msgs.write().unwrap().packets {
w.meta.size = PACKET_DATA_SIZE;
w.meta.set_addr(&addr);
}
let msgs = Arc::new(msgs);
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let mut num = 0;
for p in &msgs.packets {
for p in &msgs_.read().unwrap().packets {
let a = p.meta.addr();
assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();
@@ -44,7 +43,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
}
let timer = Duration::new(1, 0);
if let Ok(msgs) = r.recv_timeout(timer) {
rvs.fetch_add(msgs.packets.len(), Ordering::Relaxed);
rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed);
}
})
}
@@ -52,9 +51,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
fn main() -> Result<()> {
let mut num_sockets = 1usize;
let matches = App::new(crate_name!())
.about(crate_description!())
.version(crate_version!())
let matches = App::new("solana-bench-streamer")
.arg(
Arg::with_name("num-recv-sockets")
.long("num-recv-sockets")
@@ -75,7 +72,6 @@ fn main() -> Result<()> {
let mut read_channels = Vec::new();
let mut read_threads = Vec::new();
let recycler = PacketsRecycler::default();
for _ in 0..num_sockets {
let read = solana_netutil::bind_to(port, false).unwrap();
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
@@ -85,13 +81,7 @@ fn main() -> Result<()> {
let (s_reader, r_reader) = channel();
read_channels.push(r_reader);
read_threads.push(receiver(
Arc::new(read),
&exit,
s_reader,
recycler.clone(),
"bench-streamer-test",
));
read_threads.push(receiver(Arc::new(read), &exit, s_reader, "bench-streamer"));
}
let t_producer1 = producer(&addr, exit.clone());

View File

@@ -1,4 +0,0 @@
/target/
/config/
/config-local/
/farf/

View File

@@ -2,34 +2,21 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.19.0-pre0"
version = "0.12.3"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
bincode = "1.1.4"
clap = "2.33.0"
log = "0.4.8"
rayon = "1.2.0"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
solana-core = { path = "../core", version = "0.19.0-pre0" }
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
solana-client = { path = "../client", version = "0.19.0-pre0" }
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
solana-librapay-api = { path = "../programs/librapay_api", version = "0.19.0-pre0" }
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.19.0-pre0" }
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.19.0-pre0" }
clap = "2.32.0"
rayon = "1.0.3"
serde_json = "1.0.39"
solana = { path = "../core", version = "0.12.3" }
solana-client = { path = "../client", version = "0.12.3" }
solana-drone = { path = "../drone", version = "0.12.3" }
solana-logger = { path = "../logger", version = "0.12.3" }
solana-metrics = { path = "../metrics", version = "0.12.3" }
solana-sdk = { path = "../sdk", version = "0.12.3" }
[dev-dependencies]
serial_test = "0.2.0"
serial_test_derive = "0.2.0"
[features]
cuda = ["solana/cuda"]

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,13 @@ use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
use clap::{crate_version, App, Arg, ArgMatches};
use solana_drone::drone::DRONE_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = 64 * 1024;
/// Holds the configuration for a single run of the benchmark
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub network_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub id: Keypair,
pub threads: usize,
@@ -20,18 +17,14 @@ pub struct Config {
pub tx_count: usize,
pub thread_batch_sleep_ms: usize,
pub sustained: bool,
pub client_ids_and_stake_file: String,
pub write_to_client_file: bool,
pub read_from_client_file: bool,
pub target_lamports_per_signature: u64,
pub use_move: bool,
pub num_lamports_per_account: u64,
pub reject_extra_nodes: bool,
pub converge_only: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
id: Keypair::new(),
threads: 4,
@@ -40,27 +33,23 @@ impl Default for Config {
tx_count: 500_000,
thread_batch_sleep_ms: 0,
sustained: false,
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
use_move: false,
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
reject_extra_nodes: false,
converge_only: false,
}
}
}
/// Defines and builds the CLI args for a run of the benchmark
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
App::new("solana-bench-tps")
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
Arg::with_name("network")
.short("n")
.long("entrypoint")
.long("network")
.value_name("HOST:PORT")
.takes_value(true)
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@@ -68,7 +57,7 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.long("drone")
.value_name("HOST:PORT")
.takes_value(true)
.help("Location of the drone; defaults to entrypoint:DRONE_PORT"),
.help("Location of the drone; defaults to network:DRONE_PORT"),
)
.arg(
Arg::with_name("identity")
@@ -86,6 +75,11 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.help("Wait for NUM nodes to converge"),
)
.arg(
Arg::with_name("reject-extra-nodes")
.long("reject-extra-nodes")
.help("Require exactly `num-nodes` on convergence. Appropriate only for internal networks"),
)
.arg(
Arg::with_name("threads")
.short("t")
@@ -101,16 +95,16 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.help("Seconds to run benchmark, then exit; default is forever"),
)
.arg(
Arg::with_name("converge-only")
.long("converge-only")
.help("Exit immediately after converging"),
)
.arg(
Arg::with_name("sustained")
.long("sustained")
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
)
.arg(
Arg::with_name("use-move")
.long("use-move")
.help("Use Move language transactions to perform transfers."),
)
.arg(
Arg::with_name("tx_count")
.long("tx_count")
@@ -126,39 +120,6 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.help("Per-thread-per-iteration sleep in ms"),
)
.arg(
Arg::with_name("write-client-keys")
.long("write-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Generate client keys and stakes and write the list to YAML file"),
)
.arg(
Arg::with_name("read-client-keys")
.long("read-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Read client keys and stakes from the YAML file"),
)
.arg(
Arg::with_name("target_lamports_per_signature")
.long("target-lamports-per-signature")
.value_name("LAMPORTS")
.takes_value(true)
.help(
"The cost in lamports that the cluster will charge for signature \
verification when the cluster is operating at target-signatures-per-slot",
),
)
.arg(
Arg::with_name("num_lamports_per_account")
.long("num-lamports-per-account")
.value_name("LAMPORTS")
.takes_value(true)
.help(
"Number of lamports per account.",
),
)
}
/// Parses a clap `ArgMatches` structure into a `Config`
@@ -169,15 +130,15 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
if let Some(addr) = matches.value_of("entrypoint") {
args.entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
if let Some(addr) = matches.value_of("network") {
args.network_addr = addr.parse().unwrap_or_else(|e| {
eprintln!("failed to parse network: {}", e);
exit(1)
});
}
if let Some(addr) = matches.value_of("drone") {
args.drone_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
args.drone_addr = addr.parse().unwrap_or_else(|e| {
eprintln!("failed to parse drone address: {}", e);
exit(1)
});
@@ -215,27 +176,8 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
}
args.sustained = matches.is_present("sustained");
if let Some(s) = matches.value_of("write-client-keys") {
args.write_to_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(s) = matches.value_of("read-client-keys") {
assert!(!args.write_to_client_file);
args.read_from_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(v) = matches.value_of("target_lamports_per_signature") {
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
}
args.use_move = matches.is_present("use-move");
if let Some(v) = matches.value_of("num_lamports_per_account") {
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");
}
args.converge_only = matches.is_present("converge-only");
args.reject_extra_nodes = matches.is_present("reject-extra-nodes");
args
}

View File

@@ -1,2 +0,0 @@
pub mod bench;
pub mod cli;

View File

@@ -1,126 +1,251 @@
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
use solana_bench_tps::cli;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_genesis::PrimordialAccountDetails;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_program;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::exit;
mod bench;
mod cli;
/// Number of signatures for all transactions in ~1 week at ~100K TPS
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
use crate::bench::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::gen_keys::GenKeys;
use solana::gossip_service::discover;
use solana_client::client::create_client;
use solana_metrics;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::collections::VecDeque;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::thread::Builder;
use std::time::Duration;
use std::time::Instant;
fn main() {
solana_logger::setup_with_filter("solana=info");
solana_logger::setup();
solana_metrics::set_panic_hook("bench-tps");
let matches = cli::build_args().get_matches();
let cli_config = cli::extract_args(&matches);
let cfg = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
network_addr: network,
drone_addr,
id,
threads,
thread_batch_sleep_ms,
num_nodes,
duration,
tx_count,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
target_lamports_per_signature,
use_move,
num_lamports_per_account,
..
} = &cli_config;
sustained,
reject_extra_nodes,
converge_only,
} = cfg;
if *write_to_client_file {
let (keypairs, _) = generate_keypairs(&id, *tx_count as u64 * 2);
let num_accounts = keypairs.len() as u64;
let max_fee = FeeCalculator::new(*target_lamports_per_signature).max_lamports_per_signature;
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
/ num_accounts
+ num_lamports_per_account;
let mut accounts = HashMap::new();
keypairs.iter().for_each(|keypair| {
accounts.insert(
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
PrimordialAccountDetails {
balance: num_lamports_per_account,
executable: false,
owner: system_program::id().to_string(),
data: String::new(),
},
);
});
let serialized = serde_yaml::to_string(&accounts).unwrap();
let path = Path::new(&client_ids_and_stake_file);
let mut file = File::create(path).unwrap();
file.write_all(&serialized.into_bytes()).unwrap();
return;
}
println!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, *num_nodes).unwrap_or_else(|err| {
let nodes = discover(&network, 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 {
if nodes.len() < num_nodes {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
);
exit(1);
}
let (keypairs, move_keypairs, keypair_balance) = if *read_from_client_file && !use_move {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
let accounts: HashMap<String, PrimordialAccountDetails> =
serde_yaml::from_reader(file).unwrap();
let mut keypairs = vec![];
let mut last_balance = 0;
accounts
.into_iter()
.for_each(|(keypair, primordial_account)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
last_balance = primordial_account.balance;
});
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
// This prevents the amount of storage needed for bench-tps accounts from creeping up
// across multiple runs.
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
(keypairs, None, last_balance)
} else {
generate_and_fund_keypairs(
&client,
Some(*drone_addr),
&id,
*tx_count,
*num_lamports_per_account,
*use_move,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
if reject_extra_nodes && nodes.len() > num_nodes {
eprintln!(
"Error: Extra nodes discovered. Expecting exactly {}",
num_nodes
);
exit(1);
})
};
}
do_bench_tps(
vec![client],
cli_config,
keypairs,
keypair_balance,
move_keypairs,
if converge_only {
return;
}
let cluster_entrypoint = nodes[0].clone(); // Pick the first node, why not?
let mut client = create_client(cluster_entrypoint.client_facing_addr(), FULLNODE_PORT_RANGE);
let mut barrier_client =
create_client(cluster_entrypoint.client_facing_addr(), FULLNODE_PORT_RANGE);
let mut seed = [0u8; 32];
seed.copy_from_slice(&id.public_key_bytes()[..32]);
let mut rnd = GenKeys::new(seed);
println!("Creating {} keypairs...", tx_count * 2);
let mut total_keys = 0;
let mut target = tx_count * 2;
while target > 0 {
total_keys += target;
target /= MAX_SPENDS_PER_TX;
}
let gen_keypairs = rnd.gen_n_keypairs(total_keys as u64);
let barrier_source_keypair = Keypair::new();
let barrier_dest_id = Keypair::new().pubkey();
println!("Get lamports...");
let num_lamports_per_account = 20;
// Sample the first keypair, see if it has lamports, if so then resume
// to avoid lamport loss
let keypair0_balance = client
.poll_get_balance(&gen_keypairs.last().unwrap().pubkey())
.unwrap_or(0);
if num_lamports_per_account > keypair0_balance {
let extra = num_lamports_per_account - keypair0_balance;
let total = extra * (gen_keypairs.len() as u64);
airdrop_lamports(&mut client, &drone_addr, &id, total);
println!("adding more lamports {}", extra);
fund_keys(&mut client, &id, &gen_keypairs, extra);
}
let start = gen_keypairs.len() - (tx_count * 2) as usize;
let keypairs = &gen_keypairs[start..];
airdrop_lamports(&mut barrier_client, &drone_addr, &barrier_source_keypair, 1);
println!("Get last ID...");
let mut blockhash = client.get_recent_blockhash();
println!("Got last ID {:?}", blockhash);
let first_tx_count = client.transaction_count();
println!("Initial transaction count {}", first_tx_count);
let exit_signal = Arc::new(AtomicBool::new(false));
// Setup a thread per validator to sample every period
// collect the max transaction rate and total tx count seen
let maxes = Arc::new(RwLock::new(Vec::new()));
let sample_period = 1; // in seconds
println!("Sampling TPS every {} second...", sample_period);
let v_threads: Vec<_> = nodes
.into_iter()
.map(|v| {
let exit_signal = exit_signal.clone();
let maxes = maxes.clone();
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_tx_count(&exit_signal, &maxes, first_tx_count, &v, sample_period);
})
.unwrap()
})
.collect();
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
let shared_tx_active_thread_count = Arc::new(AtomicIsize::new(0));
let total_tx_sent_count = Arc::new(AtomicUsize::new(0));
let s_threads: Vec<_> = (0..threads)
.map(|_| {
let exit_signal = exit_signal.clone();
let shared_txs = shared_txs.clone();
let cluster_entrypoint = cluster_entrypoint.clone();
let shared_tx_active_thread_count = shared_tx_active_thread_count.clone();
let total_tx_sent_count = total_tx_sent_count.clone();
Builder::new()
.name("solana-client-sender".to_string())
.spawn(move || {
do_tx_transfers(
&exit_signal,
&shared_txs,
&cluster_entrypoint,
&shared_tx_active_thread_count,
&total_tx_sent_count,
thread_batch_sleep_ms,
);
})
.unwrap()
})
.collect();
// generate and send transactions for the specified duration
let start = Instant::now();
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
while start.elapsed() < duration {
let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
// ping-pong between source and destination accounts for each loop iteration
// this seems to be faster than trying to determine the balance of individual
// accounts
let len = tx_count as usize;
generate_txs(
&shared_txs,
&keypairs[..len],
&keypairs[len..],
threads,
reclaim_lamports_back_to_source_account,
&cluster_entrypoint,
);
// In sustained mode overlap the transfers with generation
// this has higher average performance but lower peak performance
// in tested environments.
if !sustained {
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 {
sleep(Duration::from_millis(100));
}
}
// It's not feasible (would take too much time) to confirm each of the `tx_count / 2`
// transactions sent by `generate_txs()` so instead send and confirm a single transaction
// to validate the network is still functional.
send_barrier_transaction(
&mut barrier_client,
&mut blockhash,
&barrier_source_keypair,
&barrier_dest_id,
);
i += 1;
if should_switch_directions(num_lamports_per_account, i) {
reclaim_lamports_back_to_source_account = !reclaim_lamports_back_to_source_account;
}
}
// Stop the sampling threads so it will collect the stats
exit_signal.store(true, Ordering::Relaxed);
println!("Waiting for validator threads...");
for t in v_threads {
if let Err(err) = t.join() {
println!(" join() failed with: {:?}", err);
}
}
// join the tx send threads
println!("Waiting for transmit threads...");
for t in s_threads {
if let Err(err) = t.join() {
println!(" join() failed with: {:?}", err);
}
}
let balance = client.poll_get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
compute_and_report_stats(
&maxes,
sample_period,
&start.elapsed(),
total_tx_sent_count.load(Ordering::Relaxed),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_switch_directions() {
assert_eq!(should_switch_directions(20, 0), false);
assert_eq!(should_switch_directions(20, 1), false);
assert_eq!(should_switch_directions(20, 14), false);
assert_eq!(should_switch_directions(20, 15), true);
assert_eq!(should_switch_directions(20, 16), false);
assert_eq!(should_switch_directions(20, 19), false);
assert_eq!(should_switch_directions(20, 20), true);
assert_eq!(should_switch_directions(20, 21), false);
assert_eq!(should_switch_directions(20, 99), false);
assert_eq!(should_switch_directions(20, 100), true);
assert_eq!(should_switch_directions(20, 101), false);
}
}

248
benches/append_vec.rs Normal file
View File

@@ -0,0 +1,248 @@
#![feature(test)]
extern crate rand;
extern crate test;
use bincode::{deserialize, serialize_into, serialized_size};
use rand::{thread_rng, Rng};
use solana_runtime::append_vec::{
deserialize_account, get_serialized_size, serialize_account, AppendVec,
};
use solana_sdk::account::Account;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::env;
use std::io::Cursor;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::spawn;
use test::Bencher;
const START_SIZE: u64 = 4 * 1024 * 1024;
const INC_SIZE: u64 = 1 * 1024 * 1024;
macro_rules! align_up {
($addr: expr, $align: expr) => {
($addr + ($align - 1)) & !($align - 1)
};
}
fn get_append_vec_bench_path(path: &str) -> PathBuf {
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let mut buf = PathBuf::new();
buf.push(&format!("{}/{}", out_dir, path));
buf
}
#[bench]
fn append_vec_atomic_append(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_append");
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
bencher.iter(|| {
if vec.append(AtomicUsize::new(0)).is_none() {
assert!(vec.grow_file().is_ok());
assert!(vec.append(AtomicUsize::new(0)).is_some());
}
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn append_vec_atomic_random_access(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_ra");
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
let size = 1_000_000;
for _ in 0..size {
if vec.append(AtomicUsize::new(0)).is_none() {
assert!(vec.grow_file().is_ok());
assert!(vec.append(AtomicUsize::new(0)).is_some());
}
}
bencher.iter(|| {
let index = thread_rng().gen_range(0, size as u64);
vec.get(index * std::mem::size_of::<AtomicUsize>() as u64);
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn append_vec_atomic_random_change(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_rax");
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
let size = 1_000_000;
for k in 0..size {
if vec.append(AtomicUsize::new(k)).is_none() {
assert!(vec.grow_file().is_ok());
assert!(vec.append(AtomicUsize::new(k)).is_some());
}
}
bencher.iter(|| {
let index = thread_rng().gen_range(0, size as u64);
let atomic1 = vec.get(index * std::mem::size_of::<AtomicUsize>() as u64);
let current1 = atomic1.load(Ordering::Relaxed);
assert_eq!(current1, index as usize);
let next = current1 + 1;
let mut index = vec.append(AtomicUsize::new(next));
if index.is_none() {
assert!(vec.grow_file().is_ok());
index = vec.append(AtomicUsize::new(next));
}
let atomic2 = vec.get(index.unwrap());
let current2 = atomic2.load(Ordering::Relaxed);
assert_eq!(current2, next);
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn append_vec_atomic_random_read(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_read");
let mut vec = AppendVec::<AtomicUsize>::new(&path, true, START_SIZE, INC_SIZE);
let size = 1_000_000;
for _ in 0..size {
if vec.append(AtomicUsize::new(0)).is_none() {
assert!(vec.grow_file().is_ok());
assert!(vec.append(AtomicUsize::new(0)).is_some());
}
}
bencher.iter(|| {
let index = thread_rng().gen_range(0, size);
let atomic1 = vec.get((index * std::mem::size_of::<AtomicUsize>()) as u64);
let current1 = atomic1.load(Ordering::Relaxed);
assert_eq!(current1, 0);
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn append_vec_concurrent_lock_append(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_lock_append");
let vec = Arc::new(RwLock::new(AppendVec::<AtomicUsize>::new(
&path, true, START_SIZE, INC_SIZE,
)));
let vec1 = vec.clone();
let size = 1_000_000;
let count = Arc::new(AtomicUsize::new(0));
let count1 = count.clone();
spawn(move || loop {
let mut len = count.load(Ordering::Relaxed);
{
let rlock = vec1.read().unwrap();
loop {
if rlock.append(AtomicUsize::new(0)).is_none() {
break;
}
len = count.fetch_add(1, Ordering::Relaxed);
}
if len >= size {
break;
}
}
{
let mut wlock = vec1.write().unwrap();
if len >= size {
break;
}
assert!(wlock.grow_file().is_ok());
}
});
bencher.iter(|| {
let _rlock = vec.read().unwrap();
let len = count1.load(Ordering::Relaxed);
assert!(len < size * 2);
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn append_vec_concurrent_get_append(bencher: &mut Bencher) {
let path = get_append_vec_bench_path("bench_get_append");
let vec = Arc::new(RwLock::new(AppendVec::<AtomicUsize>::new(
&path, true, START_SIZE, INC_SIZE,
)));
let vec1 = vec.clone();
let size = 1_000_000;
let count = Arc::new(AtomicUsize::new(0));
let count1 = count.clone();
spawn(move || loop {
let mut len = count.load(Ordering::Relaxed);
{
let rlock = vec1.read().unwrap();
loop {
if rlock.append(AtomicUsize::new(0)).is_none() {
break;
}
len = count.fetch_add(1, Ordering::Relaxed);
}
if len >= size {
break;
}
}
{
let mut wlock = vec1.write().unwrap();
if len >= size {
break;
}
assert!(wlock.grow_file().is_ok());
}
});
bencher.iter(|| {
let rlock = vec.read().unwrap();
let len = count1.load(Ordering::Relaxed);
if len > 0 {
let index = thread_rng().gen_range(0, len);
rlock.get((index * std::mem::size_of::<AtomicUsize>()) as u64);
}
});
std::fs::remove_file(path).unwrap();
}
#[bench]
fn bench_account_serialize(bencher: &mut Bencher) {
let num: usize = 1000;
let account = Account::new(2, 100, &Keypair::new().pubkey());
let len = get_serialized_size(&account);
let ser_len = align_up!(len + std::mem::size_of::<u64>(), std::mem::size_of::<u64>());
let mut memory = vec![0; num * ser_len];
bencher.iter(|| {
for i in 0..num {
let start = i * ser_len;
serialize_account(&mut memory[start..start + ser_len], &account, len);
}
});
// make sure compiler doesn't delete the code.
let index = thread_rng().gen_range(0, num);
if memory[index] != 0 {
println!("memory: {}", memory[index]);
}
let start = index * ser_len;
let new_account = deserialize_account(&memory[start..start + ser_len], 0, num * len).unwrap();
assert_eq!(new_account, account);
}
#[bench]
fn bench_account_serialize_bincode(bencher: &mut Bencher) {
let num: usize = 1000;
let account = Account::new(2, 100, &Keypair::new().pubkey());
let len = serialized_size(&account).unwrap() as usize;
let mut memory = vec![0u8; num * len];
bencher.iter(|| {
for i in 0..num {
let start = i * len;
let cursor = Cursor::new(&mut memory[start..start + len]);
serialize_into(cursor, &account).unwrap();
}
});
// make sure compiler doesn't delete the code.
let index = thread_rng().gen_range(0, len);
if memory[index] != 0 {
println!("memory: {}", memory[index]);
}
let start = index * len;
let new_account: Account = deserialize(&memory[start..start + len]).unwrap();
assert_eq!(new_account, account);
}

261
benches/banking_stage.rs Normal file
View File

@@ -0,0 +1,261 @@
#![feature(test)]
extern crate test;
#[macro_use]
extern crate solana;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::banking_stage::{create_test_recorder, BankingStage};
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::cluster_info::ClusterInfo;
use solana::cluster_info::Node;
use solana::packet::to_packets_chunked;
use solana::poh_recorder::WorkingBankEntries;
use solana::service::Service;
use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::system_transaction::SystemTransaction;
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES};
use std::iter;
use std::sync::atomic::Ordering;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use test::Bencher;
fn check_txs(receiver: &Receiver<WorkingBankEntries>, ref_tx_count: usize) {
let mut total = 0;
loop {
let entries = receiver.recv_timeout(Duration::new(1, 0));
if let Ok((_, entries)) = entries {
for (entry, _) in &entries {
total += entry.transactions.len();
}
} else {
break;
}
if total >= ref_tx_count {
break;
}
}
assert_eq!(total, ref_tx_count);
}
#[bench]
#[ignore]
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
let num_threads = 4;
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 192 * 50 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let (verified_sender, verified_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let dummy = SystemTransaction::new_move(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
let mut new = dummy.clone();
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
new.account_keys[0] = Pubkey::new(&from[0..32]);
new.account_keys[1] = Pubkey::new(&to[0..32]);
new.signatures = vec![Signature::new(&sig[0..64])];
new
})
.collect();
// fund all the accounts
transactions.iter().for_each(|tx| {
let fund = SystemTransaction::new_move(
&mint_keypair,
&tx.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
let x = bank.process_transaction(&fund);
x.unwrap();
});
//sanity check, make sure all the transactions can execute sequentially
transactions.iter().for_each(|tx| {
let res = bank.process_transaction(&tx);
assert!(res.is_ok(), "sanity test transactions");
});
bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
for r in res {
assert!(r.is_ok(), "sanity parallel execution");
}
bank.clear_signatures();
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 192)
.into_iter()
.map(|x| {
let len = x.read().unwrap().packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
let ledger_path = get_tmp_ledger_path!();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, signal_receiver) =
create_test_recorder(&bank, &blocktree);
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let _banking_stage = BankingStage::new(&cluster_info, &poh_recorder, verified_receiver);
poh_recorder.lock().unwrap().set_bank(&bank);
let mut id = genesis_block.hash();
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
id = hash(&id.as_ref());
bank.register_tick(&id);
}
let half_len = verified.len() / 2;
let mut start = 0;
bencher.iter(move || {
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver, txes / 2);
bank.clear_signatures();
start += half_len;
start %= verified.len();
});
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
Blocktree::destroy(&ledger_path).unwrap();
}
#[bench]
#[ignore]
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
let progs = 4;
let num_threads = 4;
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 96 * 100 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let (verified_sender, verified_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let dummy = SystemTransaction::new_move(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
let mut new = dummy.clone();
let from: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
new.account_keys[0] = Pubkey::new(&from[0..32]);
new.account_keys[1] = Pubkey::new(&to[0..32]);
let prog = new.instructions[0].clone();
for i in 1..progs {
//generate programs that spend to random keys
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
let to_key = Pubkey::new(&to[0..32]);
new.account_keys.push(to_key);
assert_eq!(new.account_keys.len(), i + 2);
new.instructions.push(prog.clone());
assert_eq!(new.instructions.len(), i + 1);
new.instructions[i].accounts[1] = 1 + i as u8;
assert_eq!(new.key(i, 1), Some(&to_key));
assert_eq!(
new.account_keys[new.instructions[i].accounts[1] as usize],
to_key
);
}
assert_eq!(new.instructions.len(), progs);
new.signatures = vec![Signature::new(&sig[0..64])];
new
})
.collect();
transactions.iter().for_each(|tx| {
let fund = SystemTransaction::new_move(
&mint_keypair,
&tx.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
bank.process_transaction(&fund).unwrap();
});
//sanity check, make sure all the transactions can execute sequentially
transactions.iter().for_each(|tx| {
let res = bank.process_transaction(&tx);
assert!(res.is_ok(), "sanity test transactions");
});
bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
for r in res {
assert!(r.is_ok(), "sanity parallel execution");
}
bank.clear_signatures();
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 96)
.into_iter()
.map(|x| {
let len = x.read().unwrap().packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
let ledger_path = get_tmp_ledger_path!();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, signal_receiver) =
create_test_recorder(&bank, &blocktree);
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let _banking_stage = BankingStage::new(&cluster_info, &poh_recorder, verified_receiver);
poh_recorder.lock().unwrap().set_bank(&bank);
let mut id = genesis_block.hash();
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
id = hash(&id.as_ref());
bank.register_tick(&id);
}
let half_len = verified.len() / 2;
let mut start = 0;
bencher.iter(move || {
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver, txes / 2);
bank.clear_signatures();
start += half_len;
start %= verified.len();
});
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
Blocktree::destroy(&ledger_path).unwrap();
}

194
benches/blocktree.rs Normal file
View File

@@ -0,0 +1,194 @@
#![feature(test)]
use rand;
extern crate test;
#[macro_use]
extern crate solana;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::entry::{make_large_test_entries, make_tiny_test_entries, EntrySlice};
use solana::packet::{Blob, BLOB_HEADER_SIZE};
use test::Bencher;
// Given some blobs and a ledger at ledger_path, benchmark writing the blobs to the ledger
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &str) {
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
let num_blobs = blobs.len();
bench.iter(move || {
for blob in blobs.iter_mut() {
let index = blob.index();
blocktree
.put_data_blob_bytes(
blob.slot(),
index,
&blob.data[..BLOB_HEADER_SIZE + blob.size()],
)
.unwrap();
blob.set_index(index + num_blobs as u64);
}
});
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}
// Insert some blobs into the ledger in preparation for read benchmarks
fn setup_read_bench(
blocktree: &mut Blocktree,
num_small_blobs: u64,
num_large_blobs: u64,
slot: u64,
) {
// Make some big and small entries
let mut entries = make_large_test_entries(num_large_blobs as usize);
entries.extend(make_tiny_test_entries(num_small_blobs as usize));
// Convert the entries to blobs, write the blobs to the ledger
let mut blobs = entries.to_blobs();
for (index, b) in blobs.iter_mut().enumerate() {
b.set_index(index as u64);
b.set_slot(slot);
}
blocktree
.write_blobs(&blobs)
.expect("Expectd successful insertion of blobs into ledger");
}
// Write small blobs to the ledger
#[bench]
#[ignore]
fn bench_write_small(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let num_entries = 32 * 1024;
let entries = make_tiny_test_entries(num_entries);
let mut blobs = entries.to_blobs();
for (index, b) in blobs.iter_mut().enumerate() {
b.set_index(index as u64);
}
bench_write_blobs(bench, &mut blobs, &ledger_path);
}
// Write big blobs to the ledger
#[bench]
#[ignore]
fn bench_write_big(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let num_entries = 32 * 1024;
let entries = make_large_test_entries(num_entries);
let mut blobs = entries.to_blobs();
for (index, b) in blobs.iter_mut().enumerate() {
b.set_index(index as u64);
}
bench_write_blobs(bench, &mut blobs, &ledger_path);
}
#[bench]
#[ignore]
fn bench_read_sequential(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let mut blocktree =
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
// Insert some big and small blobs into the ledger
let num_small_blobs = 32 * 1024;
let num_large_blobs = 32 * 1024;
let total_blobs = num_small_blobs + num_large_blobs;
let slot = 0;
setup_read_bench(&mut blocktree, num_small_blobs, num_large_blobs, slot);
let num_reads = total_blobs / 15;
let mut rng = rand::thread_rng();
bench.iter(move || {
// Generate random starting point in the range [0, total_blobs - 1], read num_reads blobs sequentially
let start_index = rng.gen_range(0, num_small_blobs + num_large_blobs);
for i in start_index..start_index + num_reads {
let _ = blocktree.get_data_blob(slot, i as u64 % total_blobs);
}
});
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}
#[bench]
#[ignore]
fn bench_read_random(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let mut blocktree =
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
// Insert some big and small blobs into the ledger
let num_small_blobs = 32 * 1024;
let num_large_blobs = 32 * 1024;
let total_blobs = num_small_blobs + num_large_blobs;
let slot = 0;
setup_read_bench(&mut blocktree, num_small_blobs, num_large_blobs, slot);
let num_reads = total_blobs / 15;
// Generate a num_reads sized random sample of indexes in range [0, total_blobs - 1],
// simulating random reads
let mut rng = rand::thread_rng();
let indexes: Vec<usize> = (0..num_reads)
.map(|_| rng.gen_range(0, total_blobs) as usize)
.collect();
bench.iter(move || {
for i in indexes.iter() {
let _ = blocktree.get_data_blob(slot, *i as u64);
}
});
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}
#[bench]
#[ignore]
fn bench_insert_data_blob_small(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
let num_entries = 32 * 1024;
let entries = make_tiny_test_entries(num_entries);
let mut blobs = entries.to_blobs();
blobs.shuffle(&mut thread_rng());
bench.iter(move || {
for blob in blobs.iter_mut() {
let index = blob.index();
blob.set_index(index + num_entries as u64);
}
blocktree.write_blobs(&blobs).unwrap();
});
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}
#[bench]
#[ignore]
fn bench_insert_data_blob_big(bench: &mut Bencher) {
let ledger_path = get_tmp_ledger_path!();
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
let num_entries = 32 * 1024;
let entries = make_large_test_entries(num_entries);
let mut shared_blobs = entries.to_shared_blobs();
shared_blobs.shuffle(&mut thread_rng());
bench.iter(move || {
for blob in shared_blobs.iter_mut() {
let index = blob.read().unwrap().index();
blocktree.write_shared_blobs(vec![blob.clone()]).unwrap();
blob.write().unwrap().set_index(index + num_entries as u64);
}
});
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}

View File

@@ -1,9 +1,9 @@
//#![feature(test)]
//
//extern crate solana_core;
//extern crate solana;
//extern crate test;
//
//use solana_core::chacha::chacha_cbc_encrypt_files;
//use solana::chacha::chacha_cbc_encrypt_files;
//use std::fs::remove_file;
//use std::fs::File;
//use std::io::Write;

View File

@@ -2,7 +2,7 @@
extern crate test;
use solana_core::gen_keys::GenKeys;
use solana::gen_keys::GenKeys;
use test::Bencher;
#[bench]

24
benches/ledger.rs Normal file
View File

@@ -0,0 +1,24 @@
#![feature(test)]
extern crate test;
use solana::entry::{next_entries, reconstruct_entries_from_blobs, EntrySlice};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction::SystemTransaction;
use test::Bencher;
#[bench]
fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
let zero = Hash::default();
let one = hash(&zero.as_ref());
let keypair = Keypair::new();
let tx0 = SystemTransaction::new_move(&keypair, &keypair.pubkey(), 1, one, 0);
let transactions = vec![tx0; 10];
let entries = next_entries(&zero, 1, transactions);
bencher.iter(|| {
let blobs = entries.to_blobs();
assert_eq!(reconstruct_entries_from_blobs(blobs).unwrap().0, entries);
});
}

21
benches/sigverify.rs Normal file
View File

@@ -0,0 +1,21 @@
#![feature(test)]
extern crate test;
use solana::packet::to_packets;
use solana::sigverify;
use solana::test_tx::test_tx;
use test::Bencher;
#[bench]
fn bench_sigverify(bencher: &mut Bencher) {
let tx = test_tx();
// generate packet vector
let batches = to_packets(&vec![tx; 128]);
// verify packets
bencher.iter(|| {
let _ans = sigverify::ed25519_verify(&batches);
})
}

1
book/.gitattributes vendored
View File

@@ -1 +0,0 @@
theme/highlight.js binary

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 +----------+
+------------+ Leader +------------+
| | | |
| +--------------------+ |
| +--------------+ |
v v
+---------+----------+ +----------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+---+-----+----------+ +----------+-----+---+
| | | |
v v v v
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
+--------+--------+ +--------+--------+
| +--------------------->+ |
+-----------------+ Validator 1 | | Validator 2 +-------------+
| | +<---------------------+ | |
| +------+-+-+------+ +---+-+-+---------+ |
| | | | | | | |
| | | | | | | |
| +---------------------------------------------+ | | |
| | | | | | | |
| | | | | +----------------------+ | |
| | | | | | | |
| | | | +--------------------------------------------+ |
| | | | | | | |
| | | +----------------------+ | | |
| | | | | | | |
v v v v v v v v
+--------------------+ +--------------------+ +--------------------+ +--------------------+
| | | | | | | |
| Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
| Neighborhood 1 | | Neighborhood 2 | | Neighborhood 3 | | Neighborhood 4 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+

View File

@@ -1,5 +1,5 @@
.--------------------------------------.
| Validator |
| Fullnode |
| |
.--------. | .-------------------. |
| |---->| | |
@@ -25,6 +25,6 @@
| | | | | | | Downstream | |
| | .--+--. .-------+---. | | | Validators | |
`-------->| TPU +---->| Broadcast +--------------->| | |
| `-----` | Stage | | | `------------` |
| `-----` | Service | | | `------------` |
| `-----------` | `------------------`
`--------------------------------------`

View File

@@ -1,30 +0,0 @@
msc {
hscale="2.2";
VoteSigner,
Validator,
Cluster,
StakerX,
StakerY;
|||;
Validator box Validator [label="boot.."];
VoteSigner <:> Validator [label="register\n\n(optional)"];
Validator => Cluster [label="VoteState::Initialize(VoteSigner)"];
StakerX => Cluster [label="StakeState::Delegate(Validator)"];
StakerY => Cluster [label="StakeState::Delegate(Validator)"];
|||;
Validator box Cluster [label="\nvalidate\n"];
Validator => VoteSigner [label="sign(vote)"];
VoteSigner >> Validator [label="signed vote"];
Validator => Cluster [label="gossip(vote)"];
...;
... ;
Validator abox Validator [label="\nmax\nlockout\n"];
|||;
StakerX => Cluster [label="StakeState::RedeemCredits()"];
StakerY => Cluster [label="StakeState::RedeemCredits()"] ;
}

View File

@@ -1,18 +0,0 @@
+------------+
| Bank-Merkle|
+------------+
^ ^
/ \
+-----------------+ +-------------+
| Bank-Diff-Merkle| | Block-Merkle|
+-----------------+ +-------------+
^ ^
/ \
+------+ +--------------------------+
| Hash | | Previous Bank-Diff-Merkle|
+------+ +--------------------------+
^ ^
/ \
+---------------+ +---------------+
| Hash(Account1)| | Hash(Account2)|
+---------------+ +---------------+

View File

@@ -1,19 +0,0 @@
+---------------+
| Block-Merkle |
+---------------+
^ ^
/ \
+-------------+ +-------------+
| Entry-Merkle| | Entry-Merkle|
+-------------+ +-------------+
^ ^
/ \
+-------+ +-------+
| Hash | | Hash |
+-------+ +-------+
^ ^ ^ ^
/ | | \
+-----------------+ +-----------------+ +-----------------+ +---+
| Hash(T1, status)| | Hash(T2, status)| | Hash(T3, status)| | 0 |
+-----------------+ +-----------------+ +-----------------+ +---+

View File

@@ -1,17 +1,16 @@
.-------------.
| PoH Service |
`--------+----`
^ |
.------------------------------|----|--------------------.
| TPU | v |
| .-------. .-----------. .-+-------. .-----------. | .------------.
.---------. | | Fetch | | SigVerify | | Banking | | Broadcast | | | Downstream |
| Clients |--->| Stage |->| Stage |->| Stage |->| Stage |---->| Validators |
`---------` | | | | | | | | | | | |
| `-------` `-----------` `----+----` `-----------` | `------------`
.-------------------------------------------.
| TPU .-------------. |
| | PoH Service | |
| `--------+----` |
| ^ | |
| | v |
| .-------. .-----------. .-+-------. | .------------.
.---------. | | Fetch | | SigVerify | | Banking | | | Broadcast |
| Clients |--->| Stage |->| Stage |->| Stage |------>| Service |
`---------` | | | | | | | | | |
| `-------` `-----------` `----+----` | `------------`
| | |
`---------------------------------|----------------------`
`---------------------------------|---------`
|
v
.------.

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

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
make -j"$(nproc)" -B svg
if [[ -n $CI ]]; then
# In CI confirm that no svgs need to be built
git diff --exit-code
fi

View File

@@ -3,4 +3,16 @@ set -e
cd "$(dirname "$0")"
make -j"$(nproc)" test
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
cargo install "$crate"
}
export PATH=$CARGO_HOME/bin:$PATH
cargo_install_unless mdbook mdbook --help
cargo_install_unless svgbob_cli svgbob --help
make -j"$(nproc)"

View File

@@ -1,41 +1,31 @@
BOB_SRCS=$(wildcard art/*.bob)
MSC_SRCS=$(wildcard art/*.msc)
MD_SRCS=$(wildcard src/*.md)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/.gitbook/assets/%.svg) $(MSC_SRCS:art/%.msc=src/.gitbook/assets/%.svg)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg)
TARGET=html/index.html
TEST_STAMP=src/tests.ok
all: html/index.html
all: $(TARGET)
test: src/tests.ok
svg: $(SVG_IMGS)
test: $(TEST_STAMP)
open: $(TEST_STAMP)
open: all
mdbook build --open
watch: $(SVG_IMGS)
mdbook watch
src/.gitbook/assets/%.svg: art/%.bob
src/img/%.svg: art/%.bob
@mkdir -p $(@D)
svgbob < $< > $@
src/.gitbook/assets/%.svg: art/%.msc
@mkdir -p $(@D)
mscgen -T svg -i $< -o $@
src/%.md: %.md
@mkdir -p $(@D)
@cp $< $@
$(TEST_STAMP): $(TARGET)
src/tests.ok: $(SVG_IMGS) $(MD_SRCS)
mdbook test
touch $@
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
html/index.html: src/tests.ok
mdbook build
clean:

View File

@@ -1,183 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="304" width="544" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="304" width="544" x="0" y="0"/>
<g>
<line x1="4" x2="4" y1="8" y2="184"/>
<line x1="4" x2="540" y1="8" y2="8"/>
<line x1="4" x2="540" y1="184" y2="184"/>
<line x1="540" x2="540" y1="8" y2="184"/>
</g>
<g>
<line x1="28" x2="28" y1="232" y2="296"/>
<line x1="28" x2="108" y1="232" y2="232"/>
<line x1="28" x2="196" y1="296" y2="296"/>
<line x1="108" x2="164" y1="232" y2="232"/>
<line x1="164" x2="196" y1="232" y2="232"/>
<line x1="196" x2="196" y1="232" y2="296"/>
</g>
<g>
<line x1="36" x2="36" y1="40" y2="104"/>
<line x1="36" x2="180" y1="40" y2="40"/>
<line x1="36" x2="108" y1="104" y2="104"/>
<line x1="108" x2="108" y1="104" y2="176"/>
<line x1="108" x2="124" y1="104" y2="104"/>
<line x1="124" x2="124" y1="104" y2="136"/>
<line x1="124" x2="180" y1="104" y2="104"/>
<line x1="124" x2="364" y1="136" y2="136"/>
<line x1="180" x2="180" y1="40" y2="56"/>
<line x1="180" x2="180" y1="56" y2="88"/>
<line marker-end="url(#triangle)" x1="180" x2="356" y1="56" y2="56"/>
<line x1="180" x2="180" y1="88" y2="104"/>
<line x1="180" x2="184" y1="88" y2="88"/>
<line x1="364" x2="364" y1="136" y2="152"/>
<line x1="364" x2="364" y1="152" y2="176"/>
<line x1="364" x2="420" y1="152" y2="152"/>
<line x1="420" x2="420" y1="104" y2="152"/>
<line x1="420" x2="436" y1="104" y2="104"/>
<line x1="436" x2="436" y1="104" y2="176"/>
<line x1="436" x2="508" y1="104" y2="104"/>
<line x1="508" x2="508" y1="40" y2="104"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="108" x2="108" y1="192" y2="220"/>
</g>
<g>
<line x1="164" x2="164" y1="152" y2="176"/>
<line x1="164" x2="364" y1="152" y2="152"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="164" x2="164" y1="192" y2="220"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="192" x2="188" y1="88" y2="88"/>
<line x1="192" x2="364" y1="88" y2="88"/>
<line x1="364" x2="364" y1="56" y2="88"/>
<line x1="364" x2="364" y1="88" y2="104"/>
<line x1="364" x2="420" y1="104" y2="104"/>
</g>
<g>
<line x1="348" x2="348" y1="232" y2="296"/>
<line x1="348" x2="364" y1="232" y2="232"/>
<line x1="348" x2="516" y1="296" y2="296"/>
<line x1="364" x2="436" y1="232" y2="232"/>
<line x1="436" x2="516" y1="232" y2="232"/>
<line x1="516" x2="516" y1="232" y2="296"/>
</g>
<g>
<line x1="364" x2="364" y1="40" y2="56"/>
<line x1="364" x2="508" y1="40" y2="40"/>
<line x1="364" x2="360" y1="56" y2="56"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="364" x2="364" y1="192" y2="220"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="436" x2="436" y1="192" y2="220"/>
</g>
<g>
<text x="57" y="268">
Neighborhood
</text>
</g>
<g>
<text x="65" y="76">
Validator
</text>
</g>
<g>
<text x="145" y="76">
1
</text>
</g>
<g>
<text x="161" y="268">
1
</text>
</g>
<g>
<text x="217" y="44">
Neighborhood
</text>
</g>
<g>
<text x="321" y="44">
0
</text>
</g>
<g>
<text x="377" y="268">
Neighborhood
</text>
</g>
<g>
<text x="393" y="76">
Validator
</text>
</g>
<g>
<text x="473" y="76">
2
</text>
</g>
<g>
<text x="481" y="268">
2
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,322 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="400" width="856" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="400" width="856" x="0" y="0"/>
<g>
<line x1="4" x2="4" y1="8" y2="152"/>
<line x1="4" x2="852" y1="8" y2="8"/>
<line x1="4" x2="852" y1="152" y2="152"/>
<line x1="852" x2="852" y1="8" y2="152"/>
</g>
<g>
<line x1="4" x2="4" y1="248" y2="392"/>
<line x1="4" x2="852" y1="248" y2="248"/>
<line x1="4" x2="852" y1="392" y2="392"/>
<line x1="852" x2="852" y1="248" y2="392"/>
</g>
<g>
<line x1="60" x2="60" y1="56" y2="120"/>
<line x1="60" x2="196" y1="56" y2="56"/>
<line x1="60" x2="84" y1="120" y2="120"/>
<line x1="84" x2="84" y1="120" y2="144"/>
<line x1="84" x2="196" y1="120" y2="120"/>
<line x1="196" x2="196" y1="56" y2="72"/>
<line x1="196" x2="196" y1="72" y2="104"/>
<line marker-end="url(#triangle)" x1="196" x2="252" y1="72" y2="72"/>
<line x1="196" x2="196" y1="104" y2="120"/>
<line x1="196" x2="200" y1="104" y2="104"/>
</g>
<g>
<line x1="60" x2="60" y1="296" y2="360"/>
<line x1="60" x2="84" y1="296" y2="296"/>
<line x1="60" x2="196" y1="360" y2="360"/>
<line x1="84" x2="196" y1="296" y2="296"/>
<line x1="196" x2="196" y1="296" y2="312"/>
<line x1="196" x2="196" y1="312" y2="344"/>
<line marker-end="url(#triangle)" x1="196" x2="252" y1="312" y2="312"/>
<line x1="196" x2="196" y1="344" y2="360"/>
<line x1="196" x2="200" y1="344" y2="344"/>
</g>
<g>
<line x1="84" x2="84" y1="160" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="84" x2="84" y1="256" y2="284"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="208" x2="204" y1="104" y2="104"/>
<line x1="208" x2="260" y1="104" y2="104"/>
<line x1="260" x2="260" y1="72" y2="104"/>
<line x1="260" x2="260" y1="104" y2="120"/>
<line x1="260" x2="284" y1="120" y2="120"/>
<line x1="284" x2="284" y1="120" y2="144"/>
<line x1="284" x2="396" y1="120" y2="120"/>
<line x1="396" x2="396" y1="104" y2="120"/>
<line x1="396" x2="400" y1="104" y2="104"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="208" x2="204" y1="344" y2="344"/>
<line x1="208" x2="260" y1="344" y2="344"/>
<line x1="260" x2="260" y1="312" y2="344"/>
<line x1="260" x2="260" y1="344" y2="360"/>
<line x1="260" x2="396" y1="360" y2="360"/>
<line x1="396" x2="396" y1="344" y2="360"/>
<line x1="396" x2="400" y1="344" y2="344"/>
</g>
<g>
<line x1="260" x2="260" y1="56" y2="72"/>
<line x1="260" x2="396" y1="56" y2="56"/>
<line x1="260" x2="256" y1="72" y2="72"/>
<line x1="396" x2="396" y1="56" y2="72"/>
<line x1="396" x2="396" y1="72" y2="104"/>
<line marker-end="url(#triangle)" x1="396" x2="452" y1="72" y2="72"/>
</g>
<g>
<line x1="260" x2="260" y1="296" y2="312"/>
<line x1="260" x2="284" y1="296" y2="296"/>
<line x1="260" x2="256" y1="312" y2="312"/>
<line x1="284" x2="396" y1="296" y2="296"/>
<line x1="396" x2="396" y1="296" y2="312"/>
<line x1="396" x2="396" y1="312" y2="344"/>
<line marker-end="url(#triangle)" x1="396" x2="452" y1="312" y2="312"/>
</g>
<g>
<line x1="284" x2="284" y1="160" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="284" x2="284" y1="256" y2="284"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="408" x2="404" y1="104" y2="104"/>
<line x1="408" x2="460" y1="104" y2="104"/>
<line x1="460" x2="460" y1="72" y2="104"/>
<line x1="460" x2="460" y1="104" y2="120"/>
<line x1="460" x2="508" y1="120" y2="120"/>
<line x1="508" x2="508" y1="120" y2="144"/>
<line x1="508" x2="596" y1="120" y2="120"/>
<line x1="596" x2="596" y1="104" y2="120"/>
<line x1="596" x2="600" y1="104" y2="104"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="408" x2="404" y1="344" y2="344"/>
<line x1="408" x2="460" y1="344" y2="344"/>
<line x1="460" x2="460" y1="312" y2="344"/>
<line x1="460" x2="460" y1="344" y2="360"/>
<line x1="460" x2="596" y1="360" y2="360"/>
<line x1="596" x2="596" y1="344" y2="360"/>
<line x1="596" x2="600" y1="344" y2="344"/>
</g>
<g>
<line x1="460" x2="460" y1="56" y2="72"/>
<line x1="460" x2="596" y1="56" y2="56"/>
<line x1="460" x2="456" y1="72" y2="72"/>
<line x1="596" x2="596" y1="56" y2="72"/>
<line x1="596" x2="596" y1="72" y2="104"/>
<line marker-end="url(#triangle)" x1="596" x2="652" y1="72" y2="72"/>
</g>
<g>
<line x1="460" x2="460" y1="296" y2="312"/>
<line x1="460" x2="508" y1="296" y2="296"/>
<line x1="460" x2="456" y1="312" y2="312"/>
<line x1="508" x2="596" y1="296" y2="296"/>
<line x1="596" x2="596" y1="296" y2="312"/>
<line x1="596" x2="596" y1="312" y2="344"/>
<line marker-end="url(#triangle)" x1="596" x2="652" y1="312" y2="312"/>
</g>
<g>
<line x1="508" x2="508" y1="160" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="508" x2="508" y1="256" y2="284"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="608" x2="604" y1="104" y2="104"/>
<line x1="608" x2="660" y1="104" y2="104"/>
<line x1="660" x2="660" y1="72" y2="104"/>
<line x1="660" x2="660" y1="104" y2="120"/>
<line x1="660" x2="684" y1="120" y2="120"/>
<line x1="684" x2="684" y1="120" y2="144"/>
<line x1="684" x2="796" y1="120" y2="120"/>
<line x1="796" x2="796" y1="56" y2="120"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="608" x2="604" y1="344" y2="344"/>
<line x1="608" x2="660" y1="344" y2="344"/>
<line x1="660" x2="660" y1="312" y2="344"/>
<line x1="660" x2="660" y1="344" y2="360"/>
<line x1="660" x2="796" y1="360" y2="360"/>
<line x1="796" x2="796" y1="296" y2="360"/>
</g>
<g>
<line x1="660" x2="660" y1="56" y2="72"/>
<line x1="660" x2="796" y1="56" y2="56"/>
<line x1="660" x2="656" y1="72" y2="72"/>
</g>
<g>
<line x1="660" x2="660" y1="296" y2="312"/>
<line x1="660" x2="684" y1="296" y2="296"/>
<line x1="660" x2="656" y1="312" y2="312"/>
<line x1="684" x2="796" y1="296" y2="296"/>
</g>
<g>
<line x1="684" x2="684" y1="160" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="684" x2="684" y1="256" y2="284"/>
</g>
<g>
<text x="89" y="92">
Neighbor
</text>
</g>
<g>
<text x="89" y="332">
Neighbor
</text>
</g>
<g>
<text x="161" y="92">
1
</text>
</g>
<g>
<text x="161" y="332">
1
</text>
</g>
<g>
<text x="289" y="92">
Neighbor
</text>
</g>
<g>
<text x="289" y="332">
Neighbor
</text>
</g>
<g>
<text x="353" y="28">
Neighborhood
</text>
</g>
<g>
<text x="353" y="268">
Neighborhood
</text>
</g>
<g>
<text x="361" y="92">
2
</text>
</g>
<g>
<text x="361" y="332">
2
</text>
</g>
<g>
<text x="457" y="28">
Above
</text>
</g>
<g>
<text x="457" y="268">
Below
</text>
</g>
<g>
<text x="489" y="92">
Neighbor
</text>
</g>
<g>
<text x="489" y="332">
Neighbor
</text>
</g>
<g>
<text x="561" y="92">
3
</text>
</g>
<g>
<text x="561" y="332">
3
</text>
</g>
<g>
<text x="689" y="92">
Neighbor
</text>
</g>
<g>
<text x="689" y="332">
Neighbor
</text>
</g>
<g>
<text x="761" y="92">
4
</text>
</g>
<g>
<text x="761" y="332">
4
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,138 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="240" width="544" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="240" width="544" x="0" y="0"/>
<g>
<line x1="4" x2="4" y1="104" y2="232"/>
<line x1="4" x2="108" y1="104" y2="104"/>
<line x1="4" x2="540" y1="232" y2="232"/>
<line x1="108" x2="436" y1="104" y2="104"/>
<line x1="436" x2="540" y1="104" y2="104"/>
<line x1="540" x2="540" y1="104" y2="232"/>
</g>
<g>
<line x1="36" x2="36" y1="136" y2="200"/>
<line x1="36" x2="180" y1="136" y2="136"/>
<line x1="36" x2="180" y1="200" y2="200"/>
<line x1="180" x2="180" y1="136" y2="152"/>
<line x1="180" x2="180" y1="152" y2="184"/>
<line marker-end="url(#triangle)" x1="180" x2="356" y1="152" y2="152"/>
<line x1="180" x2="180" y1="184" y2="200"/>
<line x1="180" x2="184" y1="184" y2="184"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="108" x2="108" y1="40" y2="92"/>
<line x1="108" x2="212" y1="40" y2="40"/>
<line x1="212" x2="212" y1="8" y2="40"/>
<line x1="212" x2="332" y1="8" y2="8"/>
<line x1="212" x2="212" y1="40" y2="72"/>
<line x1="212" x2="332" y1="72" y2="72"/>
<line x1="332" x2="332" y1="8" y2="40"/>
<line x1="332" x2="332" y1="40" y2="72"/>
<line x1="332" x2="436" y1="40" y2="40"/>
<line marker-end="url(#triangle)" x1="436" x2="436" y1="40" y2="92"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="192" x2="188" y1="184" y2="184"/>
<line x1="192" x2="364" y1="184" y2="184"/>
<line x1="364" x2="364" y1="152" y2="184"/>
<line x1="364" x2="364" y1="184" y2="200"/>
<line x1="364" x2="508" y1="200" y2="200"/>
<line x1="508" x2="508" y1="136" y2="200"/>
</g>
<g>
<line x1="364" x2="364" y1="136" y2="152"/>
<line x1="364" x2="508" y1="136" y2="136"/>
<line x1="364" x2="360" y1="152" y2="152"/>
</g>
<g>
<text x="65" y="172">
Validator
</text>
</g>
<g>
<text x="145" y="172">
1
</text>
</g>
<g>
<text x="217" y="140">
Neighborhood
</text>
</g>
<g>
<text x="249" y="44">
Leader
</text>
</g>
<g>
<text x="321" y="140">
0
</text>
</g>
<g>
<text x="393" y="172">
Validator
</text>
</g>
<g>
<text x="473" y="172">
2
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,192 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="288" width="736" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="288" width="736" x="0" y="0"/>
<g>
<line x1="4" x2="4" y1="216" y2="280"/>
<line x1="4" x2="156" y1="216" y2="216"/>
<line x1="4" x2="172" y1="280" y2="280"/>
<line x1="156" x2="172" y1="216" y2="216"/>
<line x1="172" x2="172" y1="216" y2="280"/>
</g>
<g>
<line x1="124" x2="124" y1="104" y2="168"/>
<line x1="124" x2="204" y1="104" y2="104"/>
<line x1="124" x2="156" y1="168" y2="168"/>
<line marker-end="url(#triangle)" x1="156" x2="156" y1="168" y2="204"/>
<line x1="156" x2="204" y1="168" y2="168"/>
<line x1="204" x2="292" y1="104" y2="104"/>
<line marker-end="url(#triangle)" x1="204" x2="204" y1="168" y2="204"/>
<line x1="204" x2="292" y1="168" y2="168"/>
<line x1="292" x2="292" y1="104" y2="168"/>
</g>
<g>
<line x1="188" x2="188" y1="216" y2="280"/>
<line x1="188" x2="204" y1="216" y2="216"/>
<line x1="188" x2="356" y1="280" y2="280"/>
<line x1="204" x2="356" y1="216" y2="216"/>
<line x1="356" x2="356" y1="216" y2="280"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="204" x2="204" y1="40" y2="92"/>
<line x1="204" x2="276" y1="40" y2="40"/>
<line x1="276" x2="276" y1="8" y2="40"/>
<line x1="276" x2="444" y1="8" y2="8"/>
<line x1="276" x2="276" y1="40" y2="72"/>
<line x1="276" x2="444" y1="72" y2="72"/>
<line x1="444" x2="444" y1="8" y2="40"/>
<line x1="444" x2="444" y1="40" y2="72"/>
<line x1="444" x2="532" y1="40" y2="40"/>
<line marker-end="url(#triangle)" x1="532" x2="532" y1="40" y2="92"/>
</g>
<g>
<line x1="380" x2="380" y1="216" y2="280"/>
<line x1="380" x2="532" y1="216" y2="216"/>
<line x1="380" x2="548" y1="280" y2="280"/>
<line x1="532" x2="548" y1="216" y2="216"/>
<line x1="548" x2="548" y1="216" y2="280"/>
</g>
<g>
<line x1="444" x2="444" y1="104" y2="168"/>
<line x1="444" x2="532" y1="104" y2="104"/>
<line x1="444" x2="532" y1="168" y2="168"/>
<line x1="532" x2="612" y1="104" y2="104"/>
<line marker-end="url(#triangle)" x1="532" x2="532" y1="168" y2="204"/>
<line x1="532" x2="580" y1="168" y2="168"/>
<line marker-end="url(#triangle)" x1="580" x2="580" y1="168" y2="204"/>
<line x1="580" x2="612" y1="168" y2="168"/>
<line x1="612" x2="612" y1="104" y2="168"/>
</g>
<g>
<line x1="564" x2="564" y1="216" y2="280"/>
<line x1="564" x2="580" y1="216" y2="216"/>
<line x1="564" x2="732" y1="280" y2="280"/>
<line x1="580" x2="732" y1="216" y2="216"/>
<line x1="732" x2="732" y1="216" y2="280"/>
</g>
<g>
<text x="33" y="252">
Neighborhood
</text>
</g>
<g>
<text x="137" y="252">
3
</text>
</g>
<g>
<text x="153" y="140">
Neighborhood
</text>
</g>
<g>
<text x="217" y="252">
Neighborhood
</text>
</g>
<g>
<text x="257" y="140">
1
</text>
</g>
<g>
<text x="305" y="44">
Neighborhood
</text>
</g>
<g>
<text x="321" y="252">
4
</text>
</g>
<g>
<text x="409" y="44">
0
</text>
</g>
<g>
<text x="409" y="252">
Neighborhood
</text>
</g>
<g>
<text x="473" y="140">
Neighborhood
</text>
</g>
<g>
<text x="513" y="252">
5
</text>
</g>
<g>
<text x="577" y="140">
2
</text>
</g>
<g>
<text x="593" y="252">
Neighborhood
</text>
</g>
<g>
<text x="697" y="252">
6
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,330 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="208" width="768" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="208" width="768" x="0" y="0"/>
<g>
<line marker-end="url(#triangle)" x1="76" x2="76" y1="32" y2="172"/>
</g>
<g>
<line x1="124" x2="124" y1="24" y2="56"/>
<line x1="124" x2="164" y1="24" y2="24"/>
<line x1="124" x2="124" y1="56" y2="88"/>
<line x1="124" x2="164" y1="56" y2="56"/>
<line x1="124" x2="124" y1="88" y2="120"/>
<line x1="124" x2="164" y1="88" y2="88"/>
<line x1="124" x2="124" y1="120" y2="152"/>
<line x1="124" x2="164" y1="120" y2="120"/>
<line x1="124" x2="124" y1="152" y2="184"/>
<line x1="124" x2="164" y1="152" y2="152"/>
<line x1="124" x2="164" y1="184" y2="184"/>
<line x1="164" x2="164" y1="24" y2="56"/>
<line x1="164" x2="164" y1="56" y2="88"/>
<line x1="164" x2="164" y1="88" y2="120"/>
<line x1="164" x2="164" y1="120" y2="152"/>
<line x1="164" x2="164" y1="152" y2="184"/>
</g>
<g>
<line x1="188" x2="188" y1="144" y2="160"/>
</g>
<g>
<line x1="200" x2="208" y1="128" y2="112"/>
</g>
<g>
<line x1="224" x2="236" y1="112" y2="136"/>
</g>
<g>
<line x1="232" x2="240" y1="96" y2="80"/>
</g>
<g>
<line x1="236" x2="236" y1="144" y2="160"/>
</g>
<g>
<line x1="260" x2="260" y1="144" y2="160"/>
</g>
<g>
<line x1="264" x2="272" y1="64" y2="48"/>
</g>
<g>
<line x1="272" x2="280" y1="80" y2="96"/>
</g>
<g>
<line x1="272" x2="280" y1="128" y2="112"/>
</g>
<g>
<line x1="304" x2="316" y1="112" y2="136"/>
</g>
<g>
<line x1="316" x2="316" y1="144" y2="160"/>
</g>
<g>
<line x1="348" x2="360" y1="136" y2="112"/>
</g>
<g>
<line x1="348" x2="348" y1="144" y2="160"/>
</g>
<g>
<line x1="368" x2="376" y1="96" y2="80"/>
</g>
<g>
<line x1="376" x2="384" y1="48" y2="64"/>
</g>
<g>
<line x1="376" x2="388" y1="112" y2="136"/>
</g>
<g>
<line x1="388" x2="388" y1="144" y2="160"/>
</g>
<g>
<line x1="416" x2="424" y1="80" y2="96"/>
</g>
<g>
<line x1="420" x2="420" y1="144" y2="160"/>
</g>
<g>
<line x1="436" x2="420" y1="104" y2="136"/>
</g>
<g>
<line x1="448" x2="460" y1="112" y2="136"/>
</g>
<g>
<line x1="460" x2="460" y1="144" y2="160"/>
</g>
<g>
<line x1="512" x2="640" y1="24" y2="24"/>
</g>
<g>
<text x="17" y="108">
time
</text>
</g>
<g>
<text x="137" y="44">
L1
</text>
</g>
<g>
<text x="137" y="76">
L2
</text>
</g>
<g>
<text x="137" y="108">
L3
</text>
</g>
<g>
<text x="137" y="140">
L4
</text>
</g>
<g>
<text x="137" y="172">
L5
</text>
</g>
<g>
<text x="185" y="140">
x
</text>
</g>
<g>
<text x="185" y="172">
xx
</text>
</g>
<g>
<text x="209" y="108">
E3
</text>
</g>
<g>
<text x="225" y="172">
xx
</text>
</g>
<g>
<text x="249" y="76">
E2
</text>
</g>
<g>
<text x="257" y="140">
E4
</text>
</g>
<g>
<text x="257" y="172">
xx
</text>
</g>
<g>
<text x="289" y="108">
x
</text>
</g>
<g>
<text x="305" y="172">
E5
</text>
</g>
<g>
<text x="313" y="44">
E1
</text>
</g>
<g>
<text x="337" y="172">
xx
</text>
</g>
<g>
<text x="361" y="108">
E3&#39;
</text>
</g>
<g>
<text x="377" y="172">
xx
</text>
</g>
<g>
<text x="393" y="76">
x
</text>
</g>
<g>
<text x="409" y="172">
xx
</text>
</g>
<g>
<text x="449" y="172">
xx
</text>
</g>
<g>
<text x="513" y="12">
validator
</text>
</g>
<g>
<text x="513" y="60">
vote(E1)
</text>
</g>
<g>
<text x="513" y="92">
vote(E2)
</text>
</g>
<g>
<text x="513" y="124">
slash(E3)
</text>
</g>
<g>
<text x="513" y="156">
vote(E4)
</text>
</g>
<g>
<text x="513" y="188">
hang
</text>
</g>
<g>
<text x="553" y="188">
on
</text>
</g>
<g>
<text x="577" y="188">
to
</text>
</g>
<g>
<text x="593" y="12">
action
</text>
</g>
<g>
<text x="601" y="188">
E4
</text>
</g>
<g>
<text x="625" y="188">
and
</text>
</g>
<g>
<text x="657" y="188">
E5
</text>
</g>
<g>
<text x="681" y="188">
for
</text>
</g>
<g>
<text x="713" y="188">
more...
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,92 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="144" width="48" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="144" width="48" x="0" y="0"/>
<g>
<line x1="20" x2="24" y1="88" y2="80"/>
<line x1="20" x2="20" y1="96" y2="88"/>
<line x1="20" x2="20" y1="96" y2="128"/>
<line x1="24" x2="40" y1="80" y2="48"/>
</g>
<g>
<line x1="44" x2="44" y1="16" y2="32"/>
</g>
<g>
<line x1="44" x2="44" y1="48" y2="96"/>
</g>
<g>
<text x="17" y="140">
5
</text>
</g>
<g>
<text x="41" y="12">
1
</text>
</g>
<g>
<text x="41" y="44">
2
</text>
</g>
<g>
<text x="41" y="108">
4
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,92 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="176" width="40" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="176" width="40" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="16" y2="32"/>
</g>
<g>
<line x1="12" x2="12" y1="48" y2="128"/>
</g>
<g>
<line x1="16" x2="32" y1="48" y2="80"/>
<line x1="36" x2="32" y1="88" y2="80"/>
<line x1="36" x2="36" y1="96" y2="88"/>
<line x1="36" x2="36" y1="96" y2="160"/>
</g>
<g>
<text x="9" y="12">
1
</text>
</g>
<g>
<text x="9" y="44">
3
</text>
</g>
<g>
<text x="9" y="140">
6
</text>
</g>
<g>
<text x="33" y="172">
7
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,122 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="208" width="96" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="208" width="96" x="0" y="0"/>
<g>
<line x1="20" x2="24" y1="88" y2="80"/>
<line x1="20" x2="20" y1="96" y2="88"/>
<line x1="20" x2="20" y1="96" y2="128"/>
<line x1="24" x2="40" y1="80" y2="48"/>
</g>
<g>
<line x1="44" x2="44" y1="16" y2="32"/>
</g>
<g>
<line x1="44" x2="44" y1="48" y2="96"/>
</g>
<g>
<line x1="48" x2="64" y1="16" y2="48"/>
<line x1="68" x2="64" y1="56" y2="48"/>
<line x1="68" x2="68" y1="64" y2="56"/>
</g>
<g>
<line x1="68" x2="68" y1="80" y2="160"/>
</g>
<g>
<line x1="72" x2="80" y1="80" y2="96"/>
<line x1="80" x2="88" y1="96" y2="112"/>
<line x1="92" x2="88" y1="120" y2="112"/>
<line x1="92" x2="92" y1="128" y2="120"/>
<line x1="92" x2="92" y1="128" y2="192"/>
</g>
<g>
<text x="17" y="140">
5
</text>
</g>
<g>
<text x="41" y="12">
1
</text>
</g>
<g>
<text x="41" y="44">
2
</text>
</g>
<g>
<text x="41" y="108">
4
</text>
</g>
<g>
<text x="65" y="76">
3
</text>
</g>
<g>
<text x="65" y="172">
6
</text>
</g>
<g>
<text x="89" y="204">
7
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

View File

@@ -1,238 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
width="1320px" height="487px"
viewBox="0 0 1320 487"
xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges"
stroke-width="1" text-rendering="geometricPrecision">
<polygon fill="white" points="101,7 161,7 161,16 101,16"/>
<text x="132" y="16" textLength="59" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
VoteSigner
</text>
<polygon fill="white" points="371,7 419,7 419,16 371,16"/>
<text x="396" y="16" textLength="47" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
Validator
</text>
<polygon fill="white" points="639,7 679,7 679,16 639,16"/>
<text x="660" y="16" textLength="38" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
Cluster
</text>
<polygon fill="white" points="901,7 945,7 945,16 901,16"/>
<text x="924" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
StakerX
</text>
<polygon fill="white" points="1165,7 1209,7 1209,16 1165,16"/>
<text x="1188" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
StakerY
</text>
<line x1="132" y1="22" x2="132" y2="39" stroke="black"/>
<line x1="396" y1="22" x2="396" y2="39" stroke="black"/>
<line x1="660" y1="22" x2="660" y2="39" stroke="black"/>
<line x1="924" y1="22" x2="924" y2="39" stroke="black"/>
<line x1="1188" y1="22" x2="1188" y2="39" stroke="black"/>
<line x1="132" y1="39" x2="132" y2="67" stroke="black"/>
<line x1="396" y1="39" x2="396" y2="67" stroke="black"/>
<line x1="660" y1="39" x2="660" y2="67" stroke="black"/>
<line x1="924" y1="39" x2="924" y2="67" stroke="black"/>
<line x1="1188" y1="39" x2="1188" y2="67" stroke="black"/>
<polygon fill="white" points="272,39 520,39 520,61 272,61"/>
<line x1="272" y1="39" x2="520" y2="39" stroke="black"/>
<line x1="272" y1="61" x2="520" y2="61" stroke="black"/>
<line x1="272" y1="39" x2="272" y2="61" stroke="black"/>
<line x1="520" y1="39" x2="520" y2="61" stroke="black"/>
<polygon fill="white" points="379,46 411,46 411,55 379,55"/>
<text x="380" y="55" textLength="30" font-family="Helvetica" font-size="12" fill="black">
boot..
</text>
<line x1="132" y1="67" x2="132" y2="106" stroke="black"/>
<line x1="396" y1="67" x2="396" y2="106" stroke="black"/>
<line x1="660" y1="67" x2="660" y2="106" stroke="black"/>
<line x1="924" y1="67" x2="924" y2="106" stroke="black"/>
<line x1="1188" y1="67" x2="1188" y2="106" stroke="black"/>
<line x1="132" y1="82" x2="396" y2="82" stroke="black"/>
<line x1="132" y1="84" x2="396" y2="84" stroke="black"/>
<polygon fill="black" points="396,83 386,89 386,77"/>
<polygon fill="black" points="132,83 142,89 142,77"/>
<polygon fill="white" points="242,68 284,68 284,77 242,77"/>
<text x="243" y="77" textLength="40" font-family="Helvetica" font-size="12" fill="black">
register
</text>
<polygon fill="white" points="262,79 264,79 264,88 262,88"/>
<text x="263" y="88" textLength="0" font-family="Helvetica" font-size="12" fill="black">
</text>
<polygon fill="white" points="237,90 289,90 289,99 237,99"/>
<text x="238" y="99" textLength="50" font-family="Helvetica" font-size="12" fill="black">
(optional)
</text>
<line x1="132" y1="106" x2="132" y2="134" stroke="black"/>
<line x1="396" y1="106" x2="396" y2="134" stroke="black"/>
<line x1="660" y1="106" x2="660" y2="134" stroke="black"/>
<line x1="924" y1="106" x2="924" y2="134" stroke="black"/>
<line x1="1188" y1="106" x2="1188" y2="134" stroke="black"/>
<line x1="396" y1="117" x2="660" y2="117" stroke="black"/>
<polygon fill="black" points="660,117 650,123 650,111"/>
<polygon fill="white" points="441,107 613,107 613,116 441,116"/>
<text x="442" y="116" textLength="170" font-family="Helvetica" font-size="12" fill="black">
VoteState::Initialize(VoteSigner)
</text>
<line x1="132" y1="134" x2="132" y2="162" stroke="black"/>
<line x1="396" y1="134" x2="396" y2="162" stroke="black"/>
<line x1="660" y1="134" x2="660" y2="162" stroke="black"/>
<line x1="924" y1="134" x2="924" y2="162" stroke="black"/>
<line x1="1188" y1="134" x2="1188" y2="162" stroke="black"/>
<line x1="924" y1="145" x2="660" y2="145" stroke="black"/>
<polygon fill="black" points="660,145 670,151 670,139"/>
<polygon fill="white" points="706,135 877,135 877,144 706,144"/>
<text x="707" y="144" textLength="169" font-family="Helvetica" font-size="12" fill="black">
StakeState::Delegate(Validator)
</text>
<line x1="132" y1="162" x2="132" y2="190" stroke="black"/>
<line x1="396" y1="162" x2="396" y2="190" stroke="black"/>
<line x1="660" y1="162" x2="660" y2="190" stroke="black"/>
<line x1="924" y1="162" x2="924" y2="190" stroke="black"/>
<line x1="1188" y1="162" x2="1188" y2="190" stroke="black"/>
<line x1="1188" y1="173" x2="660" y2="173" stroke="black"/>
<polygon fill="black" points="660,173 670,179 670,167"/>
<polygon fill="white" points="838,163 1009,163 1009,172 838,172"/>
<text x="839" y="172" textLength="169" font-family="Helvetica" font-size="12" fill="black">
StakeState::Delegate(Validator)
</text>
<line x1="132" y1="190" x2="132" y2="207" stroke="black"/>
<line x1="396" y1="190" x2="396" y2="207" stroke="black"/>
<line x1="660" y1="190" x2="660" y2="207" stroke="black"/>
<line x1="924" y1="190" x2="924" y2="207" stroke="black"/>
<line x1="1188" y1="190" x2="1188" y2="207" stroke="black"/>
<line x1="132" y1="207" x2="132" y2="246" stroke="black"/>
<line x1="396" y1="207" x2="396" y2="246" stroke="black"/>
<line x1="660" y1="207" x2="660" y2="246" stroke="black"/>
<line x1="924" y1="207" x2="924" y2="246" stroke="black"/>
<line x1="1188" y1="207" x2="1188" y2="246" stroke="black"/>
<polygon fill="white" points="272,207 784,207 784,240 272,240"/>
<line x1="272" y1="207" x2="784" y2="207" stroke="black"/>
<line x1="272" y1="240" x2="784" y2="240" stroke="black"/>
<line x1="272" y1="207" x2="272" y2="240" stroke="black"/>
<line x1="784" y1="207" x2="784" y2="240" stroke="black"/>
<polygon fill="white" points="526,208 528,208 528,217 526,217"/>
<text x="527" y="217" textLength="0" font-family="Helvetica" font-size="12" fill="black">
</text>
<polygon fill="white" points="506,219 549,219 549,228 506,228"/>
<text x="507" y="228" textLength="41" font-family="Helvetica" font-size="12" fill="black">
validate
</text>
<polygon fill="white" points="526,230 528,230 528,239 526,239"/>
<text x="527" y="239" textLength="0" font-family="Helvetica" font-size="12" fill="black">
</text>
<line x1="132" y1="246" x2="132" y2="274" stroke="black"/>
<line x1="396" y1="246" x2="396" y2="274" stroke="black"/>
<line x1="660" y1="246" x2="660" y2="274" stroke="black"/>
<line x1="924" y1="246" x2="924" y2="274" stroke="black"/>
<line x1="1188" y1="246" x2="1188" y2="274" stroke="black"/>
<line x1="396" y1="257" x2="132" y2="257" stroke="black"/>
<polygon fill="black" points="132,257 142,263 142,251"/>
<polygon fill="white" points="236,247 291,247 291,256 236,256"/>
<text x="237" y="256" textLength="53" font-family="Helvetica" font-size="12" fill="black">
sign(vote)
</text>
<line x1="132" y1="274" x2="132" y2="302" stroke="black"/>
<line x1="396" y1="274" x2="396" y2="302" stroke="black"/>
<line x1="660" y1="274" x2="660" y2="302" stroke="black"/>
<line x1="924" y1="274" x2="924" y2="302" stroke="black"/>
<line x1="1188" y1="274" x2="1188" y2="302" stroke="black"/>
<line x1="132" y1="285" x2="396" y2="285" stroke="black" stroke-dasharray="2,2"/>
<polygon fill="black" points="396,285 386,291 386,279"/>
<polygon fill="white" points="232,275 295,275 295,284 232,284"/>
<text x="233" y="284" textLength="61" font-family="Helvetica" font-size="12" fill="black">
signed vote
</text>
<line x1="132" y1="302" x2="132" y2="330" stroke="black"/>
<line x1="396" y1="302" x2="396" y2="330" stroke="black"/>
<line x1="660" y1="302" x2="660" y2="330" stroke="black"/>
<line x1="924" y1="302" x2="924" y2="330" stroke="black"/>
<line x1="1188" y1="302" x2="1188" y2="330" stroke="black"/>
<line x1="396" y1="313" x2="660" y2="313" stroke="black"/>
<polygon fill="black" points="660,313 650,319 650,307"/>
<polygon fill="white" points="494,303 561,303 561,312 494,312"/>
<text x="495" y="312" textLength="65" font-family="Helvetica" font-size="12" fill="black">
gossip(vote)
</text>
<line x1="132" y1="330" x2="132" y2="347" stroke="black" stroke-dasharray="2,2"/>
<line x1="396" y1="330" x2="396" y2="347" stroke="black" stroke-dasharray="2,2"/>
<line x1="660" y1="330" x2="660" y2="347" stroke="black" stroke-dasharray="2,2"/>
<line x1="924" y1="330" x2="924" y2="347" stroke="black" stroke-dasharray="2,2"/>
<line x1="1188" y1="330" x2="1188" y2="347" stroke="black" stroke-dasharray="2,2"/>
<line x1="132" y1="347" x2="132" y2="364" stroke="black" stroke-dasharray="2,2"/>
<line x1="396" y1="347" x2="396" y2="364" stroke="black" stroke-dasharray="2,2"/>
<line x1="660" y1="347" x2="660" y2="364" stroke="black" stroke-dasharray="2,2"/>
<line x1="924" y1="347" x2="924" y2="364" stroke="black" stroke-dasharray="2,2"/>
<line x1="1188" y1="347" x2="1188" y2="364" stroke="black" stroke-dasharray="2,2"/>
<line x1="132" y1="364" x2="132" y2="414" stroke="black"/>
<line x1="396" y1="364" x2="396" y2="414" stroke="black"/>
<line x1="660" y1="364" x2="660" y2="414" stroke="black"/>
<line x1="924" y1="364" x2="924" y2="414" stroke="black"/>
<line x1="1188" y1="364" x2="1188" y2="414" stroke="black"/>
<polygon fill="white" points="278,364 514,364 514,408 278,408"/>
<polygon fill="white" points="278,364 278,408 272,386"/>
<polygon fill="white" points="514,364 514,408 520,386"/>
<line x1="278" y1="364" x2="514" y2="364" stroke="black"/>
<line x1="278" y1="408" x2="514" y2="408" stroke="black"/>
<line x1="278" y1="364" x2="272" y2="386" stroke="black"/>
<line x1="272" y1="386" x2="278" y2="408" stroke="black"/>
<line x1="514" y1="364" x2="520" y2="386" stroke="black"/>
<line x1="520" y1="386" x2="514" y2="408" stroke="black"/>
<polygon fill="white" points="394,365 396,365 396,374 394,374"/>
<text x="395" y="374" textLength="0" font-family="Helvetica" font-size="12" fill="black">
</text>
<polygon fill="white" points="383,376 408,376 408,385 383,385"/>
<text x="384" y="385" textLength="23" font-family="Helvetica" font-size="12" fill="black">
max
</text>
<polygon fill="white" points="375,387 415,387 415,396 375,396"/>
<text x="376" y="396" textLength="38" font-family="Helvetica" font-size="12" fill="black">
lockout
</text>
<polygon fill="white" points="394,398 396,398 396,407 394,407"/>
<text x="395" y="407" textLength="0" font-family="Helvetica" font-size="12" fill="black">
</text>
<line x1="132" y1="414" x2="132" y2="431" stroke="black"/>
<line x1="396" y1="414" x2="396" y2="431" stroke="black"/>
<line x1="660" y1="414" x2="660" y2="431" stroke="black"/>
<line x1="924" y1="414" x2="924" y2="431" stroke="black"/>
<line x1="1188" y1="414" x2="1188" y2="431" stroke="black"/>
<line x1="132" y1="431" x2="132" y2="459" stroke="black"/>
<line x1="396" y1="431" x2="396" y2="459" stroke="black"/>
<line x1="660" y1="431" x2="660" y2="459" stroke="black"/>
<line x1="924" y1="431" x2="924" y2="459" stroke="black"/>
<line x1="1188" y1="431" x2="1188" y2="459" stroke="black"/>
<line x1="924" y1="442" x2="660" y2="442" stroke="black"/>
<polygon fill="black" points="660,442 670,448 670,436"/>
<polygon fill="white" points="712,432 871,432 871,441 712,441"/>
<text x="713" y="441" textLength="157" font-family="Helvetica" font-size="12" fill="black">
StakeState::RedeemCredits()
</text>
<line x1="132" y1="459" x2="132" y2="487" stroke="black"/>
<line x1="396" y1="459" x2="396" y2="487" stroke="black"/>
<line x1="660" y1="459" x2="660" y2="487" stroke="black"/>
<line x1="924" y1="459" x2="924" y2="487" stroke="black"/>
<line x1="1188" y1="459" x2="1188" y2="487" stroke="black"/>
<line x1="1188" y1="470" x2="660" y2="470" stroke="black"/>
<polygon fill="black" points="660,470 670,476 670,464"/>
<polygon fill="white" points="844,460 1003,460 1003,469 844,469"/>
<text x="845" y="469" textLength="157" font-family="Helvetica" font-size="12" fill="black">
StakeState::RedeemCredits()
</text>
<line x1="132" y1="481" x2="132" y2="487" stroke="black"/>
<line x1="396" y1="481" x2="396" y2="487" stroke="black"/>
<line x1="660" y1="481" x2="660" y2="487" stroke="black"/>
<line x1="924" y1="481" x2="924" y2="487" stroke="black"/>
<line x1="1188" y1="481" x2="1188" y2="487" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,346 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="160" width="848" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="160" width="848" x="0" y="0"/>
<g>
<line marker-end="url(#triangle)" x1="0" x2="28" y1="104" y2="104"/>
</g>
<g>
<line x1="4" x2="4" y1="12" y2="52"/>
<path d="M 4 52 A 4 4 0 0 0 8 56" fill="none"/>
<path d="M 8 8 A 4 4 0 0 0 4 12" fill="none"/>
</g>
<g>
<line x1="8" x2="104" y1="8" y2="8"/>
<path d="M 108 12 A 4 4 0 0 0 104 8" fill="none"/>
</g>
<g>
<line x1="8" x2="104" y1="56" y2="56"/>
<path d="M 104 56 A 4 4 0 0 0 108 52" fill="none"/>
</g>
<g>
<line x1="36" x2="36" y1="92" y2="116"/>
<path d="M 36 116 A 4 4 0 0 0 40 120" fill="none"/>
<path d="M 40 88 A 4 4 0 0 0 36 92" fill="none"/>
</g>
<g>
<line x1="40" x2="160" y1="88" y2="88"/>
<path d="M 164 92 A 4 4 0 0 0 160 88" fill="none"/>
</g>
<g>
<line x1="40" x2="160" y1="120" y2="120"/>
<path d="M 160 120 A 4 4 0 0 0 164 116" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="12" y2="24"/>
<line x1="108" x2="108" y1="24" y2="52"/>
<line marker-end="url(#triangle)" x1="108" x2="140" y1="24" y2="24"/>
</g>
<g>
<line x1="156" x2="156" y1="12" y2="36"/>
<path d="M 156 36 A 4 4 0 0 0 160 40" fill="none"/>
<path d="M 160 8 A 4 4 0 0 0 156 12" fill="none"/>
</g>
<g>
<line x1="160" x2="248" y1="8" y2="8"/>
<path d="M 252 12 A 4 4 0 0 0 248 8" fill="none"/>
</g>
<g>
<line x1="160" x2="248" y1="40" y2="40"/>
<path d="M 248 40 A 4 4 0 0 0 252 36" fill="none"/>
</g>
<g>
<line x1="164" x2="164" y1="92" y2="104"/>
<line x1="164" x2="164" y1="104" y2="116"/>
<line marker-end="url(#triangle)" x1="164" x2="196" y1="104" y2="104"/>
</g>
<g>
<line x1="204" x2="204" y1="92" y2="116"/>
<path d="M 204 116 A 4 4 0 0 0 208 120" fill="none"/>
<path d="M 208 88 A 4 4 0 0 0 204 92" fill="none"/>
</g>
<g>
<line x1="208" x2="280" y1="88" y2="88"/>
<path d="M 284 92 A 4 4 0 0 0 280 88" fill="none"/>
</g>
<g>
<line x1="208" x2="280" y1="120" y2="120"/>
<path d="M 280 120 A 4 4 0 0 0 284 116" fill="none"/>
</g>
<g>
<line x1="252" x2="252" y1="12" y2="24"/>
<line x1="252" x2="252" y1="24" y2="36"/>
<line marker-end="url(#triangle)" x1="252" x2="284" y1="24" y2="24"/>
</g>
<g>
<line x1="284" x2="284" y1="92" y2="104"/>
<line x1="284" x2="284" y1="104" y2="116"/>
<line marker-end="url(#triangle)" x1="284" x2="316" y1="104" y2="104"/>
</g>
<g>
<line x1="292" x2="292" y1="12" y2="36"/>
<path d="M 292 36 A 4 4 0 0 0 296 40" fill="none"/>
<path d="M 296 8 A 4 4 0 0 0 292 12" fill="none"/>
</g>
<g>
<line x1="296" x2="416" y1="8" y2="8"/>
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
</g>
<g>
<line x1="296" x2="416" y1="40" y2="40"/>
<path d="M 416 40 A 4 4 0 0 0 420 36" fill="none"/>
</g>
<g>
<line x1="324" x2="324" y1="92" y2="132"/>
<path d="M 324 132 A 4 4 0 0 0 328 136" fill="none"/>
<path d="M 328 88 A 4 4 0 0 0 324 92" fill="none"/>
</g>
<g>
<line x1="328" x2="424" y1="88" y2="88"/>
<path d="M 428 92 A 4 4 0 0 0 424 88" fill="none"/>
</g>
<g>
<line x1="328" x2="424" y1="136" y2="136"/>
<path d="M 424 136 A 4 4 0 0 0 428 132" fill="none"/>
</g>
<g>
<line x1="420" x2="420" y1="12" y2="24"/>
<line x1="420" x2="420" y1="24" y2="36"/>
<line marker-end="url(#triangle)" x1="420" x2="452" y1="24" y2="24"/>
</g>
<g>
<line x1="428" x2="428" y1="92" y2="104"/>
<line x1="428" x2="428" y1="104" y2="132"/>
<line marker-end="url(#triangle)" x1="428" x2="460" y1="104" y2="104"/>
</g>
<g>
<line x1="460" x2="460" y1="12" y2="36"/>
<path d="M 460 36 A 4 4 0 0 0 464 40" fill="none"/>
<path d="M 464 8 A 4 4 0 0 0 460 12" fill="none"/>
</g>
<g>
<line x1="464" x2="576" y1="8" y2="8"/>
<path d="M 580 12 A 4 4 0 0 0 576 8" fill="none"/>
</g>
<g>
<line x1="464" x2="576" y1="40" y2="40"/>
<path d="M 576 40 A 4 4 0 0 0 580 36" fill="none"/>
</g>
<g>
<line x1="468" x2="468" y1="92" y2="116"/>
<path d="M 468 116 A 4 4 0 0 0 472 120" fill="none"/>
<path d="M 472 88 A 4 4 0 0 0 468 92" fill="none"/>
</g>
<g>
<line x1="472" x2="608" y1="88" y2="88"/>
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
</g>
<g>
<line x1="472" x2="608" y1="120" y2="120"/>
<path d="M 608 120 A 4 4 0 0 0 612 116" fill="none"/>
</g>
<g>
<line x1="580" x2="580" y1="12" y2="24"/>
<line x1="580" x2="580" y1="24" y2="36"/>
<line marker-end="url(#triangle)" x1="580" x2="612" y1="24" y2="24"/>
</g>
<g>
<line x1="612" x2="612" y1="92" y2="104"/>
<line x1="612" x2="612" y1="104" y2="116"/>
<line marker-end="url(#triangle)" x1="612" x2="636" y1="104" y2="104"/>
</g>
<g>
<line x1="620" x2="620" y1="12" y2="36"/>
<path d="M 620 36 A 4 4 0 0 0 624 40" fill="none"/>
<path d="M 624 8 A 4 4 0 0 0 620 12" fill="none"/>
</g>
<g>
<line x1="624" x2="808" y1="8" y2="8"/>
<path d="M 812 12 A 4 4 0 0 0 808 8" fill="none"/>
</g>
<g>
<line x1="624" x2="808" y1="40" y2="40"/>
<path d="M 808 40 A 4 4 0 0 0 812 36" fill="none"/>
</g>
<g>
<line x1="644" x2="644" y1="92" y2="116"/>
<path d="M 644 116 A 4 4 0 0 0 648 120" fill="none"/>
<path d="M 648 88 A 4 4 0 0 0 644 92" fill="none"/>
</g>
<g>
<line x1="648" x2="784" y1="88" y2="88"/>
<path d="M 788 92 A 4 4 0 0 0 784 88" fill="none"/>
</g>
<g>
<line x1="648" x2="784" y1="120" y2="120"/>
<path d="M 784 120 A 4 4 0 0 0 788 116" fill="none"/>
</g>
<g>
<line x1="788" x2="788" y1="92" y2="116"/>
</g>
<g>
<line x1="812" x2="812" y1="12" y2="24"/>
<line x1="812" x2="812" y1="24" y2="36"/>
<line marker-end="url(#triangle)" x1="812" x2="844" y1="24" y2="24"/>
</g>
<g>
<text x="17" y="28">
PoH
</text>
</g>
<g>
<text x="49" y="28">
verify
</text>
</g>
<g>
<text x="49" y="44">
TVU
</text>
</g>
<g>
<text x="49" y="108">
load
</text>
</g>
<g>
<text x="89" y="108">
accounts
</text>
</g>
<g>
<text x="169" y="28">
sigverify
</text>
</g>
<g>
<text x="217" y="108">
execute
</text>
</g>
<g>
<text x="305" y="28">
lock
</text>
</g>
<g>
<text x="337" y="108">
PoH
</text>
</g>
<g>
<text x="345" y="28">
accounts
</text>
</g>
<g>
<text x="361" y="124">
TPU
</text>
</g>
<g>
<text x="369" y="108">
record
</text>
</g>
<g>
<text x="473" y="28">
validate
</text>
</g>
<g>
<text x="481" y="108">
commit
</text>
</g>
<g>
<text x="537" y="108">
accounts
</text>
</g>
<g>
<text x="545" y="28">
fee
</text>
</g>
<g>
<text x="633" y="28">
allocate
</text>
</g>
<g>
<text x="657" y="108">
unlock
</text>
</g>
<g>
<text x="705" y="28">
new
</text>
</g>
<g>
<text x="713" y="108">
accounts
</text>
</g>
<g>
<text x="737" y="28">
accounts
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,237 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="320" width="560" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="320" width="560" x="0" y="0"/>
<g>
<line x1="20" x2="20" y1="140" y2="196"/>
<path d="M 20 196 A 4 4 0 0 0 24 200" fill="none"/>
<path d="M 24 136 A 4 4 0 0 0 20 140" fill="none"/>
</g>
<g>
<line x1="24" x2="104" y1="136" y2="136"/>
<path d="M 108 140 A 4 4 0 0 0 104 136" fill="none"/>
</g>
<g>
<line x1="24" x2="104" y1="200" y2="200"/>
<path d="M 104 200 A 4 4 0 0 0 108 196" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="140" y2="152"/>
<line x1="108" x2="108" y1="152" y2="184"/>
<line x1="108" x2="176" y1="152" y2="152"/>
<line x1="108" x2="108" y1="184" y2="196"/>
<line x1="108" x2="176" y1="184" y2="184"/>
<path d="M 176 152 A 4 4 0 0 0 180 148" fill="none"/>
<path d="M 180 188 A 4 4 0 0 0 176 184" fill="none"/>
</g>
<g>
<line x1="180" x2="180" y1="108" y2="148"/>
<path d="M 184 104 A 4 4 0 0 0 180 108" fill="none"/>
</g>
<g>
<line x1="180" x2="180" y1="188" y2="244"/>
<path d="M 180 244 A 4 4 0 0 0 184 248" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="184" x2="252" y1="104" y2="104"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="184" x2="252" y1="248" y2="248"/>
</g>
<g>
<line x1="228" x2="228" y1="28" y2="96"/>
<path d="M 232 24 A 4 4 0 0 0 228 28" fill="none"/>
</g>
<g>
<line x1="228" x2="228" y1="112" y2="240"/>
</g>
<g>
<line x1="228" x2="228" y1="256" y2="308"/>
<path d="M 228 308 A 4 4 0 0 0 232 312" fill="none"/>
</g>
<g>
<line x1="232" x2="552" y1="24" y2="24"/>
<path d="M 556 28 A 4 4 0 0 0 552 24" fill="none"/>
</g>
<g>
<line x1="232" x2="552" y1="312" y2="312"/>
<path d="M 552 312 A 4 4 0 0 0 556 308" fill="none"/>
</g>
<g>
<line x1="260" x2="260" y1="76" y2="132"/>
<path d="M 260 132 A 4 4 0 0 0 264 136" fill="none"/>
<path d="M 264 72 A 4 4 0 0 0 260 76" fill="none"/>
</g>
<g>
<line x1="260" x2="260" y1="220" y2="276"/>
<path d="M 260 276 A 4 4 0 0 0 264 280" fill="none"/>
<path d="M 264 216 A 4 4 0 0 0 260 220" fill="none"/>
</g>
<g>
<line x1="264" x2="360" y1="72" y2="72"/>
<path d="M 364 76 A 4 4 0 0 0 360 72" fill="none"/>
</g>
<g>
<line x1="264" x2="360" y1="136" y2="136"/>
<path d="M 360 136 A 4 4 0 0 0 364 132" fill="none"/>
</g>
<g>
<line x1="264" x2="316" y1="216" y2="216"/>
<line x1="316" x2="316" y1="188" y2="216"/>
<line x1="316" x2="360" y1="216" y2="216"/>
<path d="M 320 184 A 4 4 0 0 0 316 188" fill="none"/>
<path d="M 364 220 A 4 4 0 0 0 360 216" fill="none"/>
</g>
<g>
<line x1="264" x2="360" y1="280" y2="280"/>
<path d="M 360 280 A 4 4 0 0 0 364 276" fill="none"/>
</g>
<g>
<line x1="320" x2="448" y1="184" y2="184"/>
<path d="M 448 184 A 4 4 0 0 0 452 180" fill="none"/>
</g>
<g>
<line x1="364" x2="364" y1="76" y2="104"/>
<line x1="364" x2="364" y1="104" y2="132"/>
<line marker-end="url(#triangle)" x1="364" x2="388" y1="104" y2="104"/>
</g>
<g>
<line x1="364" x2="364" y1="220" y2="248"/>
<line x1="364" x2="364" y1="248" y2="276"/>
<line marker-end="url(#triangle)" x1="364" x2="388" y1="248" y2="248"/>
</g>
<g>
<line x1="396" x2="396" y1="76" y2="132"/>
<path d="M 396 132 A 4 4 0 0 0 400 136" fill="none"/>
<path d="M 400 72 A 4 4 0 0 0 396 76" fill="none"/>
</g>
<g>
<line x1="396" x2="396" y1="220" y2="276"/>
<path d="M 396 276 A 4 4 0 0 0 400 280" fill="none"/>
<path d="M 400 216 A 4 4 0 0 0 396 220" fill="none"/>
</g>
<g>
<line x1="400" x2="496" y1="72" y2="72"/>
<path d="M 500 76 A 4 4 0 0 0 496 72" fill="none"/>
</g>
<g>
<line x1="400" x2="496" y1="136" y2="136"/>
<path d="M 496 136 A 4 4 0 0 0 500 132" fill="none"/>
</g>
<g>
<line x1="400" x2="504" y1="216" y2="216"/>
<path d="M 508 220 A 4 4 0 0 0 504 216" fill="none"/>
</g>
<g>
<line x1="400" x2="504" y1="280" y2="280"/>
<path d="M 504 280 A 4 4 0 0 0 508 276" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="452" x2="452" y1="160" y2="148"/>
<line x1="452" x2="452" y1="160" y2="180"/>
</g>
<g>
<line x1="500" x2="500" y1="76" y2="132"/>
</g>
<g>
<line x1="508" x2="508" y1="220" y2="276"/>
</g>
<g>
<line x1="556" x2="556" y1="28" y2="308"/>
</g>
<g>
<text x="41" y="172">
Client
</text>
</g>
<g>
<text x="281" y="108">
Verifier
</text>
</g>
<g>
<text x="281" y="252">
Loader
</text>
</g>
<g>
<text x="329" y="44">
Solana
</text>
</g>
<g>
<text x="337" y="172">
LoadAccounts
</text>
</g>
<g>
<text x="385" y="44">
Runtime
</text>
</g>
<g>
<text x="409" y="252">
Interpreter
</text>
</g>
<g>
<text x="417" y="108">
Accounts
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,163 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="288" width="432" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="288" width="432" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="248" y2="280"/>
<line x1="12" x2="140" y1="248" y2="248"/>
<line x1="12" x2="140" y1="280" y2="280"/>
<line x1="140" x2="140" y1="248" y2="280"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="112" x2="126" y1="240" y2="212"/>
</g>
<g>
<line x1="124" x2="124" y1="88" y2="120"/>
<line x1="124" x2="268" y1="88" y2="88"/>
<line x1="124" x2="268" y1="120" y2="120"/>
<line x1="268" x2="268" y1="88" y2="120"/>
</g>
<g>
<line x1="124" x2="124" y1="168" y2="200"/>
<line x1="124" x2="180" y1="168" y2="168"/>
<line x1="124" x2="180" y1="200" y2="200"/>
<line x1="180" x2="180" y1="168" y2="200"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="160" x2="174" y1="160" y2="132"/>
</g>
<g>
<line x1="172" x2="172" y1="248" y2="280"/>
<line x1="172" x2="300" y1="248" y2="248"/>
<line x1="172" x2="300" y1="280" y2="280"/>
<line x1="300" x2="300" y1="248" y2="280"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="184" x2="178" y1="224" y2="212"/>
<line x1="184" x2="192" y1="224" y2="240"/>
</g>
<g>
<line x1="212" x2="212" y1="168" y2="200"/>
<line x1="212" x2="428" y1="168" y2="168"/>
<line x1="212" x2="428" y1="200" y2="200"/>
<line x1="428" x2="428" y1="168" y2="200"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="224" x2="218" y1="144" y2="132"/>
<line x1="224" x2="232" y1="144" y2="160"/>
</g>
<g>
<line x1="236" x2="236" y1="8" y2="40"/>
<line x1="236" x2="340" y1="8" y2="8"/>
<line x1="236" x2="340" y1="40" y2="40"/>
<line x1="340" x2="340" y1="8" y2="40"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="248" x2="262" y1="80" y2="52"/>
</g>
<g>
<line x1="308" x2="308" y1="88" y2="120"/>
<line x1="308" x2="420" y1="88" y2="88"/>
<line x1="308" x2="420" y1="120" y2="120"/>
<line x1="420" x2="420" y1="88" y2="120"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="320" x2="314" y1="64" y2="52"/>
<line x1="320" x2="328" y1="64" y2="80"/>
</g>
<g>
<text x="25" y="268">
Hash(Account1)
</text>
</g>
<g>
<text x="137" y="108">
Bank-Diff-Merkle
</text>
</g>
<g>
<text x="137" y="188">
Hash
</text>
</g>
<g>
<text x="185" y="268">
Hash(Account2)
</text>
</g>
<g>
<text x="225" y="188">
Previous
</text>
</g>
<g>
<text x="249" y="28">
Bank-Merkle
</text>
</g>
<g>
<text x="297" y="188">
Bank-Diff-Merkle
</text>
</g>
<g>
<text x="321" y="108">
Block-Merkle
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,203 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="304" width="584" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="304" width="584" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="248" y2="280"/>
<line x1="12" x2="156" y1="248" y2="248"/>
<line x1="12" x2="156" y1="280" y2="280"/>
<line x1="156" x2="156" y1="248" y2="280"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="128" x2="142" y1="240" y2="212"/>
</g>
<g>
<line x1="148" x2="148" y1="168" y2="200"/>
<line x1="148" x2="212" y1="168" y2="168"/>
<line x1="148" x2="212" y1="200" y2="200"/>
<line x1="212" x2="212" y1="168" y2="200"/>
</g>
<g>
<line x1="180" x2="180" y1="88" y2="120"/>
<line x1="180" x2="292" y1="88" y2="88"/>
<line x1="180" x2="292" y1="120" y2="120"/>
<line x1="292" x2="292" y1="88" y2="120"/>
</g>
<g>
<line x1="188" x2="188" y1="248" y2="280"/>
<line x1="188" x2="332" y1="248" y2="248"/>
<line x1="188" x2="332" y1="280" y2="280"/>
<line x1="332" x2="332" y1="248" y2="280"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="192" x2="206" y1="160" y2="132"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="212" x2="212" y1="224" y2="212"/>
<line x1="212" x2="212" y1="224" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="264" x2="278" y1="80" y2="52"/>
</g>
<g>
<line x1="284" x2="284" y1="8" y2="40"/>
<line x1="284" x2="412" y1="8" y2="8"/>
<line x1="284" x2="412" y1="40" y2="40"/>
<line x1="412" x2="412" y1="8" y2="40"/>
</g>
<g>
<line x1="364" x2="364" y1="248" y2="280"/>
<line x1="364" x2="508" y1="248" y2="248"/>
<line x1="364" x2="508" y1="280" y2="280"/>
<line x1="508" x2="508" y1="248" y2="280"/>
</g>
<g>
<line x1="396" x2="396" y1="88" y2="120"/>
<line x1="396" x2="508" y1="88" y2="88"/>
<line x1="396" x2="508" y1="120" y2="120"/>
<line x1="508" x2="508" y1="88" y2="120"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="416" x2="410" y1="64" y2="52"/>
<line x1="416" x2="424" y1="64" y2="80"/>
</g>
<g>
<line x1="468" x2="468" y1="168" y2="200"/>
<line x1="468" x2="532" y1="168" y2="168"/>
<line x1="468" x2="532" y1="200" y2="200"/>
<line x1="532" x2="532" y1="168" y2="200"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="476" x2="476" y1="224" y2="212"/>
<line x1="476" x2="476" y1="224" y2="240"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="480" x2="474" y1="144" y2="132"/>
<line x1="480" x2="488" y1="144" y2="160"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="536" x2="530" y1="224" y2="212"/>
<line x1="536" x2="544" y1="224" y2="240"/>
</g>
<g>
<line x1="540" x2="540" y1="248" y2="280"/>
<line x1="540" x2="572" y1="248" y2="248"/>
<line x1="540" x2="572" y1="280" y2="280"/>
<line x1="572" x2="572" y1="248" y2="280"/>
</g>
<g>
<text x="25" y="268">
Hash(T1,
</text>
</g>
<g>
<text x="97" y="268">
status)
</text>
</g>
<g>
<text x="169" y="188">
Hash
</text>
</g>
<g>
<text x="193" y="108">
Entry-Merkle
</text>
</g>
<g>
<text x="201" y="268">
Hash(T2,
</text>
</g>
<g>
<text x="273" y="268">
status)
</text>
</g>
<g>
<text x="305" y="28">
Block-Merkle
</text>
</g>
<g>
<text x="377" y="268">
Hash(T3,
</text>
</g>
<g>
<text x="409" y="108">
Entry-Merkle
</text>
</g>
<g>
<text x="449" y="268">
status)
</text>
</g>
<g>
<text x="489" y="188">
Hash
</text>
</g>
<g>
<text x="553" y="268">
0
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,312 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="304" width="696" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="304" width="696" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="140" y2="164"/>
<path d="M 12 164 A 4 4 0 0 0 16 168" fill="none"/>
<path d="M 16 136 A 4 4 0 0 0 12 140" fill="none"/>
</g>
<g>
<line x1="16" x2="88" y1="136" y2="136"/>
<path d="M 92 140 A 4 4 0 0 0 88 136" fill="none"/>
</g>
<g>
<line x1="16" x2="88" y1="168" y2="168"/>
<path d="M 88 168 A 4 4 0 0 0 92 164" fill="none"/>
</g>
<g>
<line x1="92" x2="92" y1="140" y2="164"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="92" x2="124" y1="152" y2="152"/>
</g>
<g>
<line x1="108" x2="108" y1="92" y2="144"/>
<path d="M 112 88 A 4 4 0 0 0 108 92" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="160" y2="212"/>
<path d="M 108 212 A 4 4 0 0 0 112 216" fill="none"/>
</g>
<g>
<line x1="112" x2="356" y1="88" y2="88"/>
<line x1="356" x2="396" y1="88" y2="88"/>
<line x1="396" x2="560" y1="88" y2="88"/>
<path d="M 564 92 A 4 4 0 0 0 560 88" fill="none"/>
</g>
<g>
<line x1="112" x2="380" y1="216" y2="216"/>
<line x1="380" x2="560" y1="216" y2="216"/>
<path d="M 560 216 A 4 4 0 0 0 564 212" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="124" y2="180"/>
<path d="M 132 180 A 4 4 0 0 0 136 184" fill="none"/>
<path d="M 136 120 A 4 4 0 0 0 132 124" fill="none"/>
</g>
<g>
<line x1="136" x2="192" y1="120" y2="120"/>
<path d="M 196 124 A 4 4 0 0 0 192 120" fill="none"/>
</g>
<g>
<line x1="136" x2="192" y1="184" y2="184"/>
<path d="M 192 184 A 4 4 0 0 0 196 180" fill="none"/>
</g>
<g>
<line x1="196" x2="196" y1="124" y2="180"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="196" x2="212" y1="152" y2="152"/>
</g>
<g>
<line x1="220" x2="220" y1="124" y2="180"/>
<path d="M 220 180 A 4 4 0 0 0 224 184" fill="none"/>
<path d="M 224 120 A 4 4 0 0 0 220 124" fill="none"/>
</g>
<g>
<line x1="224" x2="312" y1="120" y2="120"/>
<path d="M 316 124 A 4 4 0 0 0 312 120" fill="none"/>
</g>
<g>
<line x1="224" x2="312" y1="184" y2="184"/>
<path d="M 312 184 A 4 4 0 0 0 316 180" fill="none"/>
</g>
<g>
<line x1="316" x2="316" y1="124" y2="180"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="316" x2="332" y1="152" y2="152"/>
</g>
<g>
<line x1="324" x2="324" y1="28" y2="52"/>
<path d="M 324 52 A 4 4 0 0 0 328 56" fill="none"/>
<path d="M 328 24 A 4 4 0 0 0 324 28" fill="none"/>
</g>
<g>
<line x1="328" x2="432" y1="24" y2="24"/>
<path d="M 436 28 A 4 4 0 0 0 432 24" fill="none"/>
</g>
<g>
<line x1="328" x2="396" y1="56" y2="56"/>
<line marker-end="url(#triangle)" x1="396" x2="396" y1="56" y2="108"/>
<line x1="396" x2="432" y1="56" y2="56"/>
<path d="M 432 56 A 4 4 0 0 0 436 52" fill="none"/>
</g>
<g>
<line x1="340" x2="340" y1="124" y2="180"/>
<path d="M 340 180 A 4 4 0 0 0 344 184" fill="none"/>
<path d="M 344 120 A 4 4 0 0 0 340 124" fill="none"/>
</g>
<g>
<line x1="344" x2="356" y1="120" y2="120"/>
<line x1="356" x2="356" y1="80" y2="120"/>
<line x1="356" x2="416" y1="120" y2="120"/>
<path d="M 420 124 A 4 4 0 0 0 416 120" fill="none"/>
</g>
<g>
<line x1="344" x2="380" y1="184" y2="184"/>
<line marker-end="url(#triangle)" x1="380" x2="380" y1="184" y2="252"/>
<line x1="380" x2="416" y1="184" y2="184"/>
<path d="M 416 184 A 4 4 0 0 0 420 180" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="356" x2="356" y1="80" y2="68"/>
</g>
<g>
<line x1="356" x2="356" y1="268" y2="292"/>
<path d="M 356 292 A 4 4 0 0 0 360 296" fill="none"/>
<path d="M 360 264 A 4 4 0 0 0 356 268" fill="none"/>
</g>
<g>
<line x1="360" x2="408" y1="264" y2="264"/>
<path d="M 412 268 A 4 4 0 0 0 408 264" fill="none"/>
</g>
<g>
<line x1="360" x2="408" y1="296" y2="296"/>
<path d="M 408 296 A 4 4 0 0 0 412 292" fill="none"/>
</g>
<g>
<line x1="412" x2="412" y1="268" y2="292"/>
</g>
<g>
<line x1="420" x2="420" y1="124" y2="180"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="420" x2="436" y1="152" y2="152"/>
</g>
<g>
<line x1="436" x2="436" y1="28" y2="52"/>
</g>
<g>
<line x1="444" x2="444" y1="124" y2="180"/>
<path d="M 444 180 A 4 4 0 0 0 448 184" fill="none"/>
<path d="M 448 120 A 4 4 0 0 0 444 124" fill="none"/>
</g>
<g>
<line x1="448" x2="536" y1="120" y2="120"/>
<path d="M 540 124 A 4 4 0 0 0 536 120" fill="none"/>
</g>
<g>
<line x1="448" x2="536" y1="184" y2="184"/>
<path d="M 536 184 A 4 4 0 0 0 540 180" fill="none"/>
</g>
<g>
<line x1="540" x2="540" y1="124" y2="180"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="540" x2="580" y1="152" y2="152"/>
</g>
<g>
<line x1="564" x2="564" y1="92" y2="144"/>
</g>
<g>
<line x1="564" x2="564" y1="160" y2="212"/>
</g>
<g>
<line x1="588" x2="588" y1="124" y2="180"/>
<path d="M 588 180 A 4 4 0 0 0 592 184" fill="none"/>
<path d="M 592 120 A 4 4 0 0 0 588 124" fill="none"/>
</g>
<g>
<line x1="592" x2="688" y1="120" y2="120"/>
<path d="M 692 124 A 4 4 0 0 0 688 120" fill="none"/>
</g>
<g>
<line x1="592" x2="688" y1="184" y2="184"/>
<path d="M 688 184 A 4 4 0 0 0 692 180" fill="none"/>
</g>
<g>
<line x1="692" x2="692" y1="124" y2="180"/>
</g>
<g>
<text x="25" y="156">
Clients
</text>
</g>
<g>
<text x="121" y="108">
TPU
</text>
</g>
<g>
<text x="145" y="140">
Fetch
</text>
</g>
<g>
<text x="145" y="156">
Stage
</text>
</g>
<g>
<text x="233" y="140">
SigVerify
</text>
</g>
<g>
<text x="233" y="156">
Stage
</text>
</g>
<g>
<text x="337" y="44">
PoH
</text>
</g>
<g>
<text x="353" y="140">
Banking
</text>
</g>
<g>
<text x="353" y="156">
Stage
</text>
</g>
<g>
<text x="369" y="44">
Service
</text>
</g>
<g>
<text x="369" y="284">
Bank
</text>
</g>
<g>
<text x="457" y="140">
Broadcast
</text>
</g>
<g>
<text x="457" y="156">
Stage
</text>
</g>
<g>
<text x="601" y="140">
Downstream
</text>
</g>
<g>
<text x="601" y="156">
Validators
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,311 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="352" width="616" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="352" width="616" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="156" y2="196"/>
<path d="M 12 196 A 4 4 0 0 0 16 200" fill="none"/>
<path d="M 16 152 A 4 4 0 0 0 12 156" fill="none"/>
</g>
<g>
<line x1="16" x2="112" y1="152" y2="152"/>
<path d="M 116 156 A 4 4 0 0 0 112 152" fill="none"/>
</g>
<g>
<line x1="16" x2="112" y1="200" y2="200"/>
<path d="M 112 200 A 4 4 0 0 0 116 196" fill="none"/>
</g>
<g>
<line x1="116" x2="116" y1="156" y2="168"/>
<line x1="116" x2="116" y1="168" y2="196"/>
<line marker-end="url(#triangle)" x1="116" x2="164" y1="168" y2="168"/>
</g>
<g>
<line x1="148" x2="148" y1="92" y2="160"/>
<path d="M 152 88 A 4 4 0 0 0 148 92" fill="none"/>
</g>
<g>
<line x1="148" x2="148" y1="176" y2="244"/>
<path d="M 148 244 A 4 4 0 0 0 152 248" fill="none"/>
</g>
<g>
<line x1="152" x2="444" y1="88" y2="88"/>
<line x1="444" x2="608" y1="88" y2="88"/>
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
</g>
<g>
<line x1="152" x2="220" y1="248" y2="248"/>
<line x1="220" x2="308" y1="248" y2="248"/>
<line x1="308" x2="444" y1="248" y2="248"/>
<line x1="444" x2="608" y1="248" y2="248"/>
<path d="M 608 248 A 4 4 0 0 0 612 244" fill="none"/>
</g>
<g>
<line x1="172" x2="172" y1="140" y2="196"/>
<path d="M 172 196 A 4 4 0 0 0 176 200" fill="none"/>
<path d="M 176 136 A 4 4 0 0 0 172 140" fill="none"/>
</g>
<g>
<line x1="176" x2="232" y1="136" y2="136"/>
<path d="M 236 140 A 4 4 0 0 0 232 136" fill="none"/>
</g>
<g>
<line x1="176" x2="232" y1="200" y2="200"/>
<path d="M 232 200 A 4 4 0 0 0 236 196" fill="none"/>
</g>
<g>
<line x1="212" x2="212" y1="300" y2="340"/>
<path d="M 212 340 A 4 4 0 0 0 216 344" fill="none"/>
<path d="M 216 296 A 4 4 0 0 0 212 300" fill="none"/>
</g>
<g>
<line x1="216" x2="312" y1="344" y2="344"/>
<path d="M 312 344 A 4 4 0 0 0 316 340" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="220" x2="220" y1="224" y2="212"/>
<line x1="220" x2="220" y1="224" y2="296"/>
<line x1="220" x2="216" y1="296" y2="296"/>
<line x1="220" x2="312" y1="296" y2="296"/>
<path d="M 316 300 A 4 4 0 0 0 312 296" fill="none"/>
</g>
<g>
<line x1="236" x2="236" y1="140" y2="168"/>
<line x1="236" x2="236" y1="168" y2="196"/>
<line marker-end="url(#triangle)" x1="236" x2="260" y1="168" y2="168"/>
</g>
<g>
<line x1="268" x2="268" y1="140" y2="196"/>
<path d="M 268 196 A 4 4 0 0 0 272 200" fill="none"/>
<path d="M 272 136 A 4 4 0 0 0 268 140" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="136" y2="136"/>
<path d="M 372 140 A 4 4 0 0 0 368 136" fill="none"/>
</g>
<g>
<line x1="272" x2="308" y1="200" y2="200"/>
<line marker-end="url(#triangle)" x1="308" x2="308" y1="200" y2="284"/>
<line x1="308" x2="368" y1="200" y2="200"/>
<path d="M 368 200 A 4 4 0 0 0 372 196" fill="none"/>
</g>
<g>
<line x1="316" x2="316" y1="300" y2="340"/>
</g>
<g>
<line x1="372" x2="372" y1="140" y2="168"/>
<line x1="372" x2="372" y1="168" y2="196"/>
<line marker-end="url(#triangle)" x1="372" x2="396" y1="168" y2="168"/>
</g>
<g>
<line x1="404" x2="404" y1="12" y2="36"/>
<path d="M 404 36 A 4 4 0 0 0 408 40" fill="none"/>
<path d="M 408 8 A 4 4 0 0 0 404 12" fill="none"/>
</g>
<g>
<line x1="404" x2="404" y1="140" y2="196"/>
<path d="M 404 196 A 4 4 0 0 0 408 200" fill="none"/>
<path d="M 408 136 A 4 4 0 0 0 404 140" fill="none"/>
</g>
<g>
<line x1="408" x2="472" y1="8" y2="8"/>
<path d="M 476 12 A 4 4 0 0 0 472 8" fill="none"/>
</g>
<g>
<line x1="408" x2="472" y1="40" y2="40"/>
<path d="M 472 40 A 4 4 0 0 0 476 36" fill="none"/>
</g>
<g>
<line x1="408" x2="444" y1="136" y2="136"/>
<line x1="444" x2="444" y1="64" y2="136"/>
<line x1="444" x2="472" y1="136" y2="136"/>
<path d="M 476 140 A 4 4 0 0 0 472 136" fill="none"/>
</g>
<g>
<line x1="408" x2="444" y1="200" y2="200"/>
<line marker-end="url(#triangle)" x1="444" x2="444" y1="200" y2="284"/>
<line x1="444" x2="472" y1="200" y2="200"/>
<path d="M 472 200 A 4 4 0 0 0 476 196" fill="none"/>
</g>
<g>
<line x1="420" x2="420" y1="300" y2="324"/>
<path d="M 420 324 A 4 4 0 0 0 424 328" fill="none"/>
<path d="M 424 296 A 4 4 0 0 0 420 300" fill="none"/>
</g>
<g>
<line x1="424" x2="472" y1="296" y2="296"/>
<path d="M 476 300 A 4 4 0 0 0 472 296" fill="none"/>
</g>
<g>
<line x1="424" x2="472" y1="328" y2="328"/>
<path d="M 472 328 A 4 4 0 0 0 476 324" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="444" x2="444" y1="64" y2="52"/>
</g>
<g>
<line x1="476" x2="476" y1="12" y2="36"/>
</g>
<g>
<line x1="476" x2="476" y1="140" y2="168"/>
<line x1="476" x2="476" y1="168" y2="196"/>
<line marker-end="url(#triangle)" x1="476" x2="500" y1="168" y2="168"/>
</g>
<g>
<line x1="476" x2="476" y1="300" y2="324"/>
</g>
<g>
<line x1="508" x2="508" y1="140" y2="196"/>
<path d="M 508 196 A 4 4 0 0 0 512 200" fill="none"/>
<path d="M 512 136 A 4 4 0 0 0 508 140" fill="none"/>
</g>
<g>
<line x1="512" x2="584" y1="136" y2="136"/>
<path d="M 588 140 A 4 4 0 0 0 584 136" fill="none"/>
</g>
<g>
<line x1="512" x2="584" y1="200" y2="200"/>
<path d="M 584 200 A 4 4 0 0 0 588 196" fill="none"/>
</g>
<g>
<line x1="588" x2="588" y1="140" y2="196"/>
</g>
<g>
<line x1="612" x2="612" y1="92" y2="244"/>
</g>
<g>
<text x="25" y="172">
Upstream
</text>
</g>
<g>
<text x="25" y="188">
Validators
</text>
</g>
<g>
<text x="169" y="108">
TVU
</text>
</g>
<g>
<text x="185" y="156">
Blob
</text>
</g>
<g>
<text x="185" y="172">
Fetch
</text>
</g>
<g>
<text x="185" y="188">
Stage
</text>
</g>
<g>
<text x="225" y="316">
Gossip
</text>
</g>
<g>
<text x="225" y="332">
Service
</text>
</g>
<g>
<text x="281" y="156">
Retransmit
</text>
</g>
<g>
<text x="281" y="172">
Stage
</text>
</g>
<g>
<text x="417" y="28">
Leader
</text>
</g>
<g>
<text x="417" y="156">
Replay
</text>
</g>
<g>
<text x="417" y="172">
Stage
</text>
</g>
<g>
<text x="433" y="316">
Bank
</text>
</g>
<g>
<text x="521" y="156">
Storage
</text>
</g>
<g>
<text x="521" y="172">
Stage
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -1,496 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="960" width="528" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="960" width="528" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="188" y2="212"/>
<path d="M 12 212 A 4 4 0 0 0 16 216" fill="none"/>
<path d="M 16 184 A 4 4 0 0 0 12 188" fill="none"/>
</g>
<g>
<line x1="16" x2="80" y1="184" y2="184"/>
<path d="M 84 188 A 4 4 0 0 0 80 184" fill="none"/>
</g>
<g>
<line x1="16" x2="80" y1="216" y2="216"/>
<path d="M 80 216 A 4 4 0 0 0 84 212" fill="none"/>
</g>
<g>
<line x1="20" x2="20" y1="748" y2="788"/>
<path d="M 20 788 A 4 4 0 0 0 24 792" fill="none"/>
<path d="M 24 744 A 4 4 0 0 0 20 748" fill="none"/>
</g>
<g>
<line x1="24" x2="80" y1="744" y2="744"/>
<path d="M 84 748 A 4 4 0 0 0 80 744" fill="none"/>
</g>
<g>
<line x1="24" x2="80" y1="792" y2="792"/>
<path d="M 80 792 A 4 4 0 0 0 84 788" fill="none"/>
</g>
<g>
<line x1="84" x2="84" y1="188" y2="200"/>
<line x1="84" x2="84" y1="200" y2="212"/>
<line marker-end="url(#triangle)" x1="84" x2="124" y1="200" y2="200"/>
</g>
<g>
<line x1="84" x2="84" y1="748" y2="760"/>
<line x1="84" x2="84" y1="760" y2="788"/>
<line marker-end="url(#triangle)" x1="84" x2="124" y1="760" y2="760"/>
</g>
<g>
<line x1="108" x2="108" y1="124" y2="192"/>
<path d="M 112 120 A 4 4 0 0 0 108 124" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="208" y2="436"/>
<path d="M 108 436 A 4 4 0 0 0 112 440" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="700" y2="752"/>
<path d="M 112 696 A 4 4 0 0 0 108 700" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="768" y2="836"/>
<path d="M 108 836 A 4 4 0 0 0 112 840" fill="none"/>
</g>
<g>
<line x1="112" x2="392" y1="120" y2="120"/>
<path d="M 396 124 A 4 4 0 0 0 392 120" fill="none"/>
</g>
<g>
<line x1="112" x2="392" y1="440" y2="440"/>
<path d="M 392 440 A 4 4 0 0 0 396 436" fill="none"/>
</g>
<g>
<line x1="112" x2="392" y1="696" y2="696"/>
<path d="M 396 700 A 4 4 0 0 0 392 696" fill="none"/>
</g>
<g>
<line x1="112" x2="392" y1="840" y2="840"/>
<path d="M 392 840 A 4 4 0 0 0 396 836" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="172" y2="212"/>
<path d="M 132 212 A 4 4 0 0 0 136 216" fill="none"/>
<path d="M 136 168 A 4 4 0 0 0 132 172" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="268" y2="308"/>
<path d="M 132 308 A 4 4 0 0 0 136 312" fill="none"/>
<path d="M 136 264 A 4 4 0 0 0 132 268" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="748" y2="788"/>
<path d="M 132 788 A 4 4 0 0 0 136 792" fill="none"/>
<path d="M 136 744 A 4 4 0 0 0 132 748" fill="none"/>
</g>
<g>
<line x1="136" x2="224" y1="168" y2="168"/>
<path d="M 228 172 A 4 4 0 0 0 224 168" fill="none"/>
</g>
<g>
<line x1="136" x2="164" y1="216" y2="216"/>
<line marker-end="url(#triangle)" x1="164" x2="164" y1="216" y2="252"/>
<line x1="164" x2="224" y1="216" y2="216"/>
<path d="M 224 216 A 4 4 0 0 0 228 212" fill="none"/>
</g>
<g>
<line x1="136" x2="224" y1="264" y2="264"/>
<path d="M 228 268 A 4 4 0 0 0 224 264" fill="none"/>
</g>
<g>
<line x1="136" x2="224" y1="312" y2="312"/>
<path d="M 224 312 A 4 4 0 0 0 228 308" fill="none"/>
</g>
<g>
<line x1="136" x2="224" y1="744" y2="744"/>
<path d="M 228 748 A 4 4 0 0 0 224 744" fill="none"/>
</g>
<g>
<line x1="136" x2="224" y1="792" y2="792"/>
<path d="M 224 792 A 4 4 0 0 0 228 788" fill="none"/>
</g>
<g>
<line x1="228" x2="228" y1="172" y2="212"/>
</g>
<g>
<line x1="228" x2="228" y1="268" y2="308"/>
</g>
<g>
<line x1="228" x2="228" y1="748" y2="760"/>
<line x1="228" x2="228" y1="760" y2="788"/>
<line marker-end="url(#triangle)" x1="228" x2="260" y1="760" y2="760"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="240" x2="236" y1="280" y2="280"/>
<line marker-end="url(#triangle)" x1="240" x2="260" y1="280" y2="280"/>
</g>
<g>
<line x1="268" x2="268" y1="28" y2="68"/>
<path d="M 268 68 A 4 4 0 0 0 272 72" fill="none"/>
<path d="M 272 24 A 4 4 0 0 0 268 28" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="172" y2="212"/>
<path d="M 268 212 A 4 4 0 0 0 272 216" fill="none"/>
<path d="M 272 168 A 4 4 0 0 0 268 172" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="268" y2="308"/>
<path d="M 268 308 A 4 4 0 0 0 272 312" fill="none"/>
<path d="M 272 264 A 4 4 0 0 0 268 268" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="364" y2="404"/>
<path d="M 268 404 A 4 4 0 0 0 272 408" fill="none"/>
<path d="M 272 360 A 4 4 0 0 0 268 364" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="492" y2="532"/>
<path d="M 268 532 A 4 4 0 0 0 272 536" fill="none"/>
<path d="M 272 488 A 4 4 0 0 0 268 492" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="604" y2="644"/>
<path d="M 268 644 A 4 4 0 0 0 272 648" fill="none"/>
<path d="M 272 600 A 4 4 0 0 0 268 604" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="748" y2="788"/>
<path d="M 268 788 A 4 4 0 0 0 272 792" fill="none"/>
<path d="M 272 744 A 4 4 0 0 0 268 748" fill="none"/>
</g>
<g>
<line x1="268" x2="268" y1="892" y2="932"/>
<path d="M 268 932 A 4 4 0 0 0 272 936" fill="none"/>
<path d="M 272 888 A 4 4 0 0 0 268 892" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="24" y2="24"/>
<path d="M 372 28 A 4 4 0 0 0 368 24" fill="none"/>
</g>
<g>
<line x1="272" x2="308" y1="72" y2="72"/>
<line x1="308" x2="308" y1="72" y2="112"/>
<line x1="308" x2="368" y1="72" y2="72"/>
<path d="M 368 72 A 4 4 0 0 0 372 68" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="168" y2="168"/>
<path d="M 372 172 A 4 4 0 0 0 368 168" fill="none"/>
</g>
<g>
<line x1="272" x2="308" y1="216" y2="216"/>
<line marker-end="url(#triangle)" x1="308" x2="308" y1="216" y2="252"/>
<line x1="308" x2="368" y1="216" y2="216"/>
<path d="M 368 216 A 4 4 0 0 0 372 212" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="264" y2="264"/>
<path d="M 372 268 A 4 4 0 0 0 368 264" fill="none"/>
</g>
<g>
<line x1="272" x2="308" y1="312" y2="312"/>
<line marker-end="url(#triangle)" x1="308" x2="308" y1="312" y2="348"/>
<line x1="308" x2="368" y1="312" y2="312"/>
<path d="M 368 312 A 4 4 0 0 0 372 308" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="360" y2="360"/>
<path d="M 372 364 A 4 4 0 0 0 368 360" fill="none"/>
</g>
<g>
<line x1="272" x2="308" y1="408" y2="408"/>
<line x1="308" x2="308" y1="408" y2="432"/>
<line x1="308" x2="368" y1="408" y2="408"/>
<path d="M 368 408 A 4 4 0 0 0 372 404" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="488" y2="488"/>
<path d="M 372 492 A 4 4 0 0 0 368 488" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="536" y2="536"/>
<path d="M 368 536 A 4 4 0 0 0 372 532" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="600" y2="600"/>
<path d="M 372 604 A 4 4 0 0 0 368 600" fill="none"/>
</g>
<g>
<line x1="272" x2="332" y1="648" y2="648"/>
<line x1="332" x2="332" y1="648" y2="688"/>
<line x1="332" x2="368" y1="648" y2="648"/>
<path d="M 368 648 A 4 4 0 0 0 372 644" fill="none"/>
</g>
<g>
<line x1="272" x2="300" y1="744" y2="744"/>
<line x1="300" x2="300" y1="704" y2="744"/>
<line x1="300" x2="368" y1="744" y2="744"/>
<path d="M 372 748 A 4 4 0 0 0 368 744" fill="none"/>
</g>
<g>
<line x1="272" x2="316" y1="792" y2="792"/>
<line x1="316" x2="316" y1="792" y2="832"/>
<line x1="316" x2="368" y1="792" y2="792"/>
<path d="M 368 792 A 4 4 0 0 0 372 788" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="888" y2="888"/>
<path d="M 372 892 A 4 4 0 0 0 368 888" fill="none"/>
</g>
<g>
<line x1="272" x2="368" y1="936" y2="936"/>
<path d="M 368 936 A 4 4 0 0 0 372 932" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="300" x2="300" y1="672" y2="660"/>
<line x1="300" x2="300" y1="672" y2="688"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="308" x2="308" y1="128" y2="156"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="308" x2="308" y1="448" y2="476"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="316" x2="316" y1="848" y2="876"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="332" x2="332" y1="704" y2="732"/>
</g>
<g>
<line x1="372" x2="372" y1="28" y2="68"/>
</g>
<g>
<line x1="372" x2="372" y1="172" y2="212"/>
</g>
<g>
<line x1="372" x2="372" y1="268" y2="308"/>
</g>
<g>
<line x1="372" x2="372" y1="364" y2="404"/>
</g>
<g>
<line x1="372" x2="372" y1="492" y2="532"/>
</g>
<g>
<line x1="372" x2="372" y1="604" y2="644"/>
</g>
<g>
<line x1="372" x2="372" y1="748" y2="788"/>
</g>
<g>
<line x1="372" x2="372" y1="892" y2="932"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="384" x2="380" y1="760" y2="760"/>
<line marker-end="url(#triangle)" x1="384" x2="412" y1="760" y2="760"/>
</g>
<g>
<line x1="396" x2="396" y1="124" y2="436"/>
</g>
<g>
<line x1="396" x2="396" y1="700" y2="752"/>
</g>
<g>
<line x1="396" x2="396" y1="768" y2="836"/>
</g>
<g>
<line x1="420" x2="420" y1="748" y2="788"/>
<path d="M 420 788 A 4 4 0 0 0 424 792" fill="none"/>
<path d="M 424 744 A 4 4 0 0 0 420 748" fill="none"/>
</g>
<g>
<line x1="424" x2="520" y1="744" y2="744"/>
<path d="M 524 748 A 4 4 0 0 0 520 744" fill="none"/>
</g>
<g>
<line x1="424" x2="520" y1="792" y2="792"/>
<path d="M 520 792 A 4 4 0 0 0 524 788" fill="none"/>
</g>
<g>
<line x1="524" x2="524" y1="748" y2="788"/>
</g>
<g>
<text x="25" y="204">
Client
</text>
</g>
<g>
<text x="33" y="764">
Fetch
</text>
</g>
<g>
<text x="33" y="780">
Stage
</text>
</g>
<g>
<text x="121" y="140">
Validator
</text>
</g>
<g>
<text x="121" y="716">
TPU
</text>
</g>
<g>
<text x="145" y="188">
Fetch
</text>
</g>
<g>
<text x="145" y="204">
Stage
</text>
</g>
<g>
<text x="145" y="284">
TPU
</text>
</g>
<g>
<text x="145" y="764">
SigVerify
</text>
</g>
<g>
<text x="145" y="780">
Stage
</text>
</g>
<g>
<text x="281" y="44">
Upstream
</text>
</g>
<g>
<text x="281" y="60">
Validators
</text>
</g>
<g>
<text x="281" y="188">
Repair
</text>
</g>
<g>
<text x="281" y="204">
Stage
</text>
</g>
<g>
<text x="281" y="284">
Blockstore
</text>
</g>
<g>
<text x="281" y="380">
Multicast
</text>
</g>
<g>
<text x="281" y="396">
Stage
</text>
</g>
<g>
<text x="281" y="508">
Downstream
</text>
</g>
<g>
<text x="281" y="524">
Validators
</text>
</g>
<g>
<text x="281" y="620">
PoH
</text>
</g>
<g>
<text x="281" y="636">
Service
</text>
</g>
<g>
<text x="281" y="764">
Banking
</text>
</g>
<g>
<text x="281" y="780">
Stage
</text>
</g>
<g>
<text x="281" y="908">
Banktree
</text>
</g>
<g>
<text x="433" y="764">
Blockstore
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,456 +0,0 @@
<svg class="bob" font-family="arial" font-size="14" height="480" width="592" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
</marker>
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
</marker>
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="black" r="8"/>
</marker>
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<rect fill="black" height="20" width="20" x="0" y="0"/>
</marker>
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
</marker>
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
</marker>
</defs>
<style type="text/css">
line,path {
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
line.dashed {
stroke-dasharray: 5;
}
circle.solid {
fill:black;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
circle.open {
fill:none;
stroke: black;
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
stroke-linecap: round;
stroke-linejoin: miter;
}
tspan.head{
fill: none;
stroke: none;
}
</style>
<rect fill="white" height="480" width="592" x="0" y="0"/>
<g>
<line x1="12" x2="12" y1="60" y2="116"/>
<path d="M 12 116 A 4 4 0 0 0 16 120" fill="none"/>
<path d="M 16 56 A 4 4 0 0 0 12 60" fill="none"/>
</g>
<g>
<line x1="16" x2="80" y1="56" y2="56"/>
<path d="M 84 60 A 4 4 0 0 0 80 56" fill="none"/>
</g>
<g>
<line x1="16" x2="52" y1="120" y2="120"/>
<line x1="52" x2="52" y1="120" y2="420"/>
<line x1="52" x2="80" y1="120" y2="120"/>
<path d="M 52 420 A 4 4 0 0 0 56 424" fill="none"/>
<path d="M 80 120 A 4 4 0 0 0 84 116" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="56" x2="124" y1="424" y2="424"/>
</g>
<g>
<line x1="84" x2="84" y1="60" y2="116"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="84" x2="124" y1="72" y2="72"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="96" x2="92" y1="104" y2="104"/>
<line x1="96" x2="132" y1="104" y2="104"/>
</g>
<g>
<line x1="108" x2="108" y1="12" y2="64"/>
<path d="M 112 8 A 4 4 0 0 0 108 12" fill="none"/>
</g>
<g>
<line x1="108" x2="108" y1="80" y2="96"/>
</g>
<g>
<line x1="108" x2="108" y1="112" y2="416"/>
</g>
<g>
<line x1="108" x2="108" y1="432" y2="468"/>
<path d="M 108 468 A 4 4 0 0 0 112 472" fill="none"/>
</g>
<g>
<line x1="112" x2="416" y1="8" y2="8"/>
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
</g>
<g>
<line x1="112" x2="416" y1="472" y2="472"/>
<path d="M 416 472 A 4 4 0 0 0 420 468" fill="none"/>
</g>
<g>
<line x1="124" x2="124" y1="236" y2="276"/>
<path d="M 124 276 A 4 4 0 0 0 128 280" fill="none"/>
<path d="M 128 232 A 4 4 0 0 0 124 236" fill="none"/>
</g>
<g>
<line x1="128" x2="156" y1="232" y2="232"/>
<line x1="156" x2="156" y1="144" y2="232"/>
<line x1="156" x2="184" y1="232" y2="232"/>
<path d="M 188 236 A 4 4 0 0 0 184 232" fill="none"/>
</g>
<g>
<line x1="128" x2="184" y1="280" y2="280"/>
<path d="M 184 280 A 4 4 0 0 0 188 276" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="60" y2="116"/>
<path d="M 132 116 A 4 4 0 0 0 136 120" fill="none"/>
<path d="M 136 56 A 4 4 0 0 0 132 60" fill="none"/>
</g>
<g>
<line x1="132" x2="132" y1="412" y2="436"/>
<path d="M 132 436 A 4 4 0 0 0 136 440" fill="none"/>
<path d="M 136 408 A 4 4 0 0 0 132 412" fill="none"/>
</g>
<g>
<line x1="136" x2="288" y1="56" y2="56"/>
<path d="M 292 60 A 4 4 0 0 0 288 56" fill="none"/>
</g>
<g>
<line x1="136" x2="288" y1="120" y2="120"/>
<path d="M 288 120 A 4 4 0 0 0 292 116" fill="none"/>
</g>
<g>
<line x1="136" x2="156" y1="408" y2="408"/>
<line x1="156" x2="156" y1="304" y2="408"/>
<line x1="156" x2="176" y1="408" y2="408"/>
<path d="M 180 412 A 4 4 0 0 0 176 408" fill="none"/>
</g>
<g>
<line x1="136" x2="176" y1="440" y2="440"/>
<path d="M 176 440 A 4 4 0 0 0 180 436" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="156" x2="156" y1="144" y2="132"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="156" x2="156" y1="304" y2="292"/>
</g>
<g>
<line x1="180" x2="180" y1="412" y2="424"/>
<line x1="180" x2="180" y1="424" y2="436"/>
<line marker-end="url(#triangle)" x1="180" x2="220" y1="424" y2="424"/>
</g>
<g>
<line x1="188" x2="188" y1="236" y2="276"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="200" x2="196" y1="248" y2="248"/>
<line x1="200" x2="212" y1="248" y2="248"/>
<line x1="212" x2="212" y1="236" y2="248"/>
<line x1="212" x2="212" y1="248" y2="276"/>
<path d="M 212 276 A 4 4 0 0 0 216 280" fill="none"/>
<path d="M 216 232 A 4 4 0 0 0 212 236" fill="none"/>
</g>
<g>
<line x1="204" x2="204" y1="156" y2="180"/>
<path d="M 204 180 A 4 4 0 0 0 208 184" fill="none"/>
<path d="M 208 152 A 4 4 0 0 0 204 156" fill="none"/>
</g>
<g>
<line x1="208" x2="336" y1="152" y2="152"/>
<path d="M 340 156 A 4 4 0 0 0 336 152" fill="none"/>
</g>
<g>
<line x1="208" x2="336" y1="184" y2="184"/>
<path d="M 336 184 A 4 4 0 0 0 340 180" fill="none"/>
</g>
<g>
<line x1="216" x2="252" y1="232" y2="232"/>
<line x1="252" x2="252" y1="208" y2="232"/>
<line x1="252" x2="280" y1="232" y2="232"/>
<path d="M 284 236 A 4 4 0 0 0 280 232" fill="none"/>
</g>
<g>
<line x1="216" x2="280" y1="280" y2="280"/>
<path d="M 280 280 A 4 4 0 0 0 284 276" fill="none"/>
</g>
<g>
<line x1="228" x2="228" y1="412" y2="452"/>
<path d="M 228 452 A 4 4 0 0 0 232 456" fill="none"/>
<path d="M 232 408 A 4 4 0 0 0 228 412" fill="none"/>
</g>
<g>
<line x1="232" x2="292" y1="408" y2="408"/>
<line x1="292" x2="292" y1="384" y2="408"/>
<line x1="292" x2="320" y1="408" y2="408"/>
<path d="M 324 412 A 4 4 0 0 0 320 408" fill="none"/>
</g>
<g>
<line x1="232" x2="320" y1="456" y2="456"/>
<path d="M 320 456 A 4 4 0 0 0 324 452" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="252" x2="252" y1="208" y2="196"/>
</g>
<g>
<line x1="252" x2="252" y1="332" y2="356"/>
<path d="M 252 356 A 4 4 0 0 0 256 360" fill="none"/>
<path d="M 256 328 A 4 4 0 0 0 252 332" fill="none"/>
</g>
<g>
<line x1="256" x2="276" y1="328" y2="328"/>
<line x1="276" x2="276" y1="304" y2="328"/>
<line x1="276" x2="344" y1="328" y2="328"/>
<path d="M 348 332 A 4 4 0 0 0 344 328" fill="none"/>
</g>
<g>
<line x1="256" x2="344" y1="360" y2="360"/>
<path d="M 344 360 A 4 4 0 0 0 348 356" fill="none"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="276" x2="276" y1="304" y2="292"/>
</g>
<g>
<line x1="284" x2="284" y1="236" y2="276"/>
</g>
<g>
<line x1="292" x2="292" y1="60" y2="116"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="292" x2="292" y1="384" y2="372"/>
</g>
<g>
<line x1="300" x2="300" y1="236" y2="276"/>
<path d="M 300 276 A 4 4 0 0 0 304 280" fill="none"/>
<path d="M 304 232 A 4 4 0 0 0 300 236" fill="none"/>
</g>
<g>
<line x1="304" x2="392" y1="232" y2="232"/>
<path d="M 396 236 A 4 4 0 0 0 392 232" fill="none"/>
</g>
<g>
<line x1="304" x2="324" y1="280" y2="280"/>
<line marker-end="url(#triangle)" x1="324" x2="324" y1="280" y2="316"/>
<line x1="324" x2="392" y1="280" y2="280"/>
<path d="M 392 280 A 4 4 0 0 0 396 276" fill="none"/>
</g>
<g>
<line x1="324" x2="324" y1="412" y2="424"/>
<line x1="324" x2="324" y1="424" y2="452"/>
<line marker-end="url(#triangle)" x1="324" x2="452" y1="424" y2="424"/>
</g>
<g>
<line x1="340" x2="340" y1="156" y2="180"/>
</g>
<g>
<line x1="348" x2="348" y1="332" y2="356"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="352" x2="348" y1="168" y2="168"/>
<line x1="352" x2="436" y1="168" y2="168"/>
</g>
<g>
<line x1="396" x2="396" y1="236" y2="276"/>
</g>
<g>
<line marker-end="url(#triangle)" x1="408" x2="404" y1="248" y2="248"/>
<line x1="408" x2="460" y1="248" y2="248"/>
<line x1="460" x2="460" y1="220" y2="248"/>
<line x1="460" x2="460" y1="248" y2="292"/>
<path d="M 460 292 A 4 4 0 0 0 464 296" fill="none"/>
<path d="M 464 216 A 4 4 0 0 0 460 220" fill="none"/>
</g>
<g>
<line x1="420" x2="420" y1="12" y2="160"/>
</g>
<g>
<line x1="420" x2="420" y1="176" y2="240"/>
</g>
<g>
<line x1="420" x2="420" y1="256" y2="416"/>
</g>
<g>
<line x1="420" x2="420" y1="432" y2="468"/>
</g>
<g>
<line x1="436" x2="436" y1="156" y2="240"/>
<path d="M 440 152 A 4 4 0 0 0 436 156" fill="none"/>
</g>
<g>
<line x1="436" x2="436" y1="256" y2="416"/>
</g>
<g>
<line x1="436" x2="436" y1="432" y2="452"/>
<path d="M 436 452 A 4 4 0 0 0 440 456" fill="none"/>
</g>
<g>
<line x1="440" x2="584" y1="152" y2="152"/>
<path d="M 588 156 A 4 4 0 0 0 584 152" fill="none"/>
</g>
<g>
<line x1="440" x2="584" y1="456" y2="456"/>
<path d="M 584 456 A 4 4 0 0 0 588 452" fill="none"/>
</g>
<g>
<line x1="460" x2="460" y1="364" y2="436"/>
<path d="M 460 436 A 4 4 0 0 0 464 440" fill="none"/>
<path d="M 464 360 A 4 4 0 0 0 460 364" fill="none"/>
</g>
<g>
<line x1="464" x2="560" y1="216" y2="216"/>
<path d="M 564 220 A 4 4 0 0 0 560 216" fill="none"/>
</g>
<g>
<line x1="464" x2="560" y1="296" y2="296"/>
<path d="M 560 296 A 4 4 0 0 0 564 292" fill="none"/>
</g>
<g>
<line x1="464" x2="560" y1="360" y2="360"/>
<path d="M 564 364 A 4 4 0 0 0 560 360" fill="none"/>
</g>
<g>
<line x1="464" x2="560" y1="440" y2="440"/>
<path d="M 560 440 A 4 4 0 0 0 564 436" fill="none"/>
</g>
<g>
<line x1="564" x2="564" y1="220" y2="292"/>
</g>
<g>
<line x1="564" x2="564" y1="364" y2="436"/>
</g>
<g>
<line x1="588" x2="588" y1="156" y2="452"/>
</g>
<g>
<text x="25" y="92">
Client
</text>
</g>
<g>
<text x="129" y="28">
Validator
</text>
</g>
<g>
<text x="137" y="252">
Bank
</text>
</g>
<g>
<text x="137" y="268">
Forks
</text>
</g>
<g>
<text x="145" y="92">
JSON
</text>
</g>
<g>
<text x="145" y="428">
TPU
</text>
</g>
<g>
<text x="185" y="92">
RPC
</text>
</g>
<g>
<text x="217" y="92">
Service
</text>
</g>
<g>
<text x="217" y="172">
Gossip
</text>
</g>
<g>
<text x="225" y="252">
Replay
</text>
</g>
<g>
<text x="225" y="268">
Stage
</text>
</g>
<g>
<text x="241" y="428">
Broadcast
</text>
</g>
<g>
<text x="241" y="444">
Stage
</text>
</g>
<g>
<text x="265" y="348">
Blocktree
</text>
</g>
<g>
<text x="273" y="172">
Service
</text>
</g>
<g>
<text x="313" y="252">
BlobFetch
</text>
</g>
<g>
<text x="321" y="268">
Stage
</text>
</g>
<g>
<text x="449" y="172">
Validators
</text>
</g>
<g>
<text x="473" y="252">
Upstream
</text>
</g>
<g>
<text x="473" y="268">
Validators
</text>
</g>
<g>
<text x="473" y="396">
Downstream
</text>
</g>
<g>
<text x="473" y="412">
Validators
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,81 +1,59 @@
# Table of contents
# Solana Architecture
* [Introduction](introduction.md)
* [Terminology](terminology.md)
* [Getting Started](getting-started/README.md)
* [Testnet Participation](getting-started/testnet-participation.md)
* [Example Client: Web Wallet](getting-started/webwallet.md)
* [Programming Model](programs/README.md)
* [Example: Tic-Tac-Toe](programs/tictactoe.md)
* [Drones](programs/drones.md)
* [A Solana Cluster](cluster/README.md)
* [Synchronization](cluster/synchronization.md)
* [Leader Rotation](cluster/leader-rotation.md)
* [Fork Generation](cluster/fork-generation.md)
* [Managing Forks](cluster/managing-forks.md)
* [Turbine Block Propagation](cluster/turbine-block-propagation.md)
* [Ledger Replication](cluster/ledger-replication.md)
* [Secure Vote Signing](cluster/vote-signing.md)
* [Stake Delegation and Rewards](cluster/stake-delegation-and-rewards.md)
* [Performance Metrics](cluster/performance-metrics.md)
* [Anatomy of a Validator](validator/README.md)
* [TPU](validator/tpu.md)
* [TVU](validator/tvu/README.md)
* [Blocktree](validator/tvu/blocktree.md)
* [Gossip Service](validator/gossip.md)
* [The Runtime](validator/runtime.md)
* [Anatomy of a Transaction](transaction.md)
* [Running a Validator](running-validator/README.md)
* [Hardware Requirements](running-validator/validator-hardware.md)
* [Choosing a Testnet](running-validator/validator-testnet.md)
* [Installing the Validator Software](running-validator/validator-software.md)
* [Starting a Validator](running-validator/validator-start.md)
* [Staking](running-validator/validator-stake.md)
* [Monitoring a Validator](running-validator/validator-monitor.md)
* [Publishing Validator Info](running-validator/validator-info.md)
* [Troubleshooting](running-validator/validator-troubleshoot.md)
* [FAQ](running-validator/validator-faq.md)
* [Running a Replicator](running-replicator.md)
* [API Reference](api-reference/README.md)
* [Transaction](api-reference/transaction-api.md)
* [Instruction](api-reference/instruction-api.md)
* [Blockstreamer](api-reference/blockstreamer.md)
* [JSON RPC API](api-reference/jsonrpc-api.md)
* [JavaScript API](api-reference/javascript-api.md)
* [solana CLI](api-reference/cli.md)
* [Accepted Design Proposals](proposals/README.md)
* [Ledger Replication](proposals/ledger-replication-to-implement.md)
* [Secure Vote Signing](proposals/vote-signing-to-implement.md)
* [Staking Rewards](proposals/staking-rewards.md)
* [Cluster Economics](proposals/ed_overview/README.md)
* [Validation-client Economics](proposals/ed_overview/ed_validation_client_economics/README.md)
* [State-validation Protocol-based Rewards](proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md)
* [State-validation Transaction Fees](proposals/ed_overview/ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md)
* [Replication-validation Transaction Fees](proposals/ed_overview/ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md)
* [Validation Stake Delegation](proposals/ed_overview/ed_validation_client_economics/ed_vce_validation_stake_delegation.md)
* [Replication-client Economics](proposals/ed_overview/ed_replication_client_economics/README.md)
* [Storage-replication Rewards](proposals/ed_overview/ed_replication_client_economics/ed_rce_storage_replication_rewards.md)
* [Replication-client Reward Auto-delegation](proposals/ed_overview/ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md)
* [Economic Sustainability](proposals/ed_overview/ed_economic_sustainability.md)
* [Attack Vectors](proposals/ed_overview/ed_attack_vectors.md)
* [Economic Design MVP](proposals/ed_overview/ed_mvp.md)
* [References](proposals/ed_overview/ed_references.md)
* [Cluster Test Framework](proposals/cluster-test-framework.md)
* [Validator](proposals/validator-proposal.md)
* [Simple Payment and State Verification](proposals/simple-payment-and-state-verification.md)
* [Cross-Program Invocation](proposals/cross-program-invocation.md)
* [Implemented Design Proposals](implemented-proposals/README.md)
* [Blocktree](implemented-proposals/blocktree.md)
* [Cluster Software Installation and Updates](implemented-proposals/installer.md)
* [Deterministic Transaction Fees](implemented-proposals/transaction-fees.md)
* [Tower BFT](implemented-proposals/tower-bft.md)
* [Leader-to-Leader Transition](implemented-proposals/leader-leader-transition.md)
* [Leader-to-Validator Transition](implemented-proposals/leader-validator-transition.md)
* [Passive Stake Delegation and Rewards](implemented-proposals/passive-stake-delegation-and-rewards.md)
* [Persistent Account Storage](implemented-proposals/persistent-account-storage.md)
* [Reliable Vote Transmission](implemented-proposals/reliable-vote-transmission.md)
* [Repair Service](implemented-proposals/repair-service.md)
* [Testing Programs](implemented-proposals/testing-programs.md)
* [Credit-only Accounts](implemented-proposals/credit-only-credit-debit-accounts.md)
* [Embedding the Move Langauge](implemented-proposals/embedding-move.md)
- [Introduction](introduction.md)
- [Terminology](terminology.md)
- [Getting Started](getting-started.md)
- [Example: Web Wallet](webwallet.md)
- [Programming Model](programs.md)
- [Example: Tic-Tac-Toe](tictactoe.md)
- [Drones](drones.md)
- [A Solana Cluster](cluster.md)
- [Synchronization](synchronization.md)
- [Leader Rotation](leader-rotation.md)
- [Fork Generation](fork-generation.md)
- [Managing Forks](managing-forks.md)
- [Data Plane Fanout](data-plane-fanout.md)
- [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.md)
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
- [Anatomy of a Fullnode](fullnode.md)
- [TPU](tpu.md)
- [TVU](tvu.md)
- [Blocktree](blocktree.md)
- [Gossip Service](gossip.md)
- [The Runtime](runtime.md)
- [API Reference](api-reference.md)
- [Blockstreamer](blockstreamer.md)
- [JSON RPC API](jsonrpc-api.md)
- [JavaScript API](javascript-api.md)
- [solana-wallet CLI](wallet.md)
- [Proposed Architectural Changes](proposals.md)
- [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.md)
- [Fork Selection](fork-selection.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Leader to Leader Transition](leader-leader-transition.md)
- [Cluster Economics](ed_overview.md)
- [Validation-client Economics](ed_validation_client_economics.md)
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
- [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md)
- [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md)
- [Validation Stake Delegation](ed_vce_validation_stake_delegation.md)
- [Replication-client Economics](ed_replication_client_economics.md)
- [Storage-replication Rewards](ed_rce_storage_replication_rewards.md)
- [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md)
- [Economic Sustainability](ed_economic_sustainability.md)
- [Attack Vectors](ed_attack_vectors.md)
- [References](ed_references.md)
- [Leader-to-Validator Transition](leader-validator-transition.md)
- [Cluster Test Framework](cluster-test-framework.md)
- [Testing Programs](testing-programs.md)

View File

@@ -1,4 +0,0 @@
# API Reference
The following sections contain API references material you may find useful when developing applications utilizing a Solana cluster.

View File

@@ -1,28 +0,0 @@
# Blockstreamer
Solana supports a node type called an _blockstreamer_. This fullnode variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
A blockstreamer runs without a vote signer, and can optionally stream ledger entries out to a Unix domain socket as they are processed. The JSON-RPC service still functions as on any other node.
To run a blockstreamer, include the argument `no-signer` and \(optional\) `blockstream` socket location:
```bash
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
```
The stream will output a series of JSON objects:
* An Entry event JSON object is sent when each ledger entry is processed, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "entry"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `entry`, the entry, as JSON object
* A Block event JSON object is sent when a block is complete, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "block"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `l`, the slot leader id, as base-58 encoded string
* `id`, the block id, as base-58 encoded string

View File

@@ -1,38 +0,0 @@
# Instruction
For the purposes of building a [Transaction](../transaction.md), a more verbose instruction format is used:
* **Instruction:**
* **program\_id:** The pubkey of the on-chain program that executes the
instruction
* **accounts:** An ordered list of accounts that should be passed to
the program processing the instruction, including metadata detailing
if an account is a signer of the transaction and if it is a credit
only account.
* **data:** A byte array that is passed to the program executing the
instruction
A more compact form is actually included in a `Transaction`:
* **CompiledInstruction:**
* **program\_id\_index:** The index of the `program_id` in the
`account_keys` list
* **accounts:** An ordered list of indices into `account_keys`
specifying the accounds that should be passed to the program
processing the instruction.
* **data:** A byte array that is passed to the program executing the
instruction

View File

@@ -1,4 +0,0 @@
# JavaScript API
See [solana-web3](https://solana-labs.github.io/solana-web3.js/).

View File

@@ -1,788 +0,0 @@
# JSON RPC API
Solana nodes accept HTTP requests using the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
To interact with a Solana node inside a JavaScript application, use the [solana-web3.js](https://github.com/solana-labs/solana-web3.js) library, which gives a convenient interface for the RPC methods.
## RPC HTTP Endpoint
**Default port:** 8899 eg. [http://localhost:8899](http://localhost:8899), [http://192.168.1.88:8899](http://192.168.1.88:8899)
## RPC PubSub WebSocket Endpoint
**Default port:** 8900 eg. ws://localhost:8900, [http://192.168.1.88:8900](http://192.168.1.88:8900)
## Methods
* [confirmTransaction](jsonrpc-api.md#confirmtransaction)
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
* [getBalance](jsonrpc-api.md#getbalance)
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
* [getGenesisBlockhash](jsonrpc-api.md#getgenesisblockhash)
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
* [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash)
* [getSignatureStatus](jsonrpc-api.md#getsignaturestatus)
* [getSlot](jsonrpc-api.md#getslot)
* [getSlotLeader](jsonrpc-api.md#getslotleader)
* [getSlotsPerSegment](jsonrpc-api.md#getslotspersegment)
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
* [getTotalSupply](jsonrpc-api.md#gettotalsupply)
* [getVersion](jsonrpc-api.md#getversion)
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
* [sendTransaction](jsonrpc-api.md#sendtransaction)
* [startSubscriptionChannel](jsonrpc-api.md#startsubscriptionchannel)
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
* [accountSubscribe](jsonrpc-api.md#accountsubscribe)
* [accountUnsubscribe](jsonrpc-api.md#accountunsubscribe)
* [programSubscribe](jsonrpc-api.md#programsubscribe)
* [programUnsubscribe](jsonrpc-api.md#programunsubscribe)
* [signatureSubscribe](jsonrpc-api.md#signaturesubscribe)
* [signatureUnsubscribe](jsonrpc-api.md#signatureunsubscribe)
## Request Formatting
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
* `jsonrpc`, set to `"2.0"`
* `id`, a unique client-generated identifying integer
* `method`, a string containing the method to be invoked
* `params`, a JSON array of ordered parameter values
Example using curl:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
```
The response output will be a JSON object with the following fields:
* `jsonrpc`, matching the request specification
* `id`, matching the request identifier
* `result`, requested data or success confirmation
Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
## Definitions
* Hash: A SHA-256 hash of a chunk of data.
* Pubkey: The public key of a Ed25519 key-pair.
* Signature: An Ed25519 signature of a chunk of data.
* Transaction: A Solana instruction signed by a client key-pair.
## JSON RPC API Reference
### confirmTransaction
Returns a transaction receipt
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
#### Results:
* `boolean` - Transaction status, true if Transaction is confirmed
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":true,"id":1}
```
### getAccountInfo
Returns all information associated with the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
#### Results:
The result field will be a JSON object with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
```
### getBalance
Returns the balance of the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
#### Results:
* `integer` - quantity, as a signed 64-bit integer
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":0,"id":1}
```
### getClusterNodes
Returns information about all the nodes participating in the cluster
#### Parameters:
None
#### Results:
The result field will be an array of JSON objects, each with the following sub fields:
* `pubkey` - Node public key, as base-58 encoded string
* `gossip` - Gossip network address for the node
* `tpu` - TPU network address for the node
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
### getEpochInfo
Returns information about the current epoch
#### Parameters:
None
#### Results:
The result field will be an object with the following fields:
* `epoch`, the current epoch
* `slotIndex`, the current slot relative to the start of the current epoch
* `slotsInEpoch`, the number of slots in this epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
```
### getGenesisBlockhash
Returns the genesis block hash
#### Parameters:
None
#### Results:
* `string` - a Hash as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
### getLeaderSchedule
Returns the leader schedule for the current epoch
#### Parameters:
None
#### Results:
The result field will be an array of leader public keys \(as base-58 encoded strings\) for each slot in the current epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[...],"id":1}
```
### getProgramAccounts
Returns all accounts owned by the provided program Pubkey
#### Parameters:
* `string` - Pubkey of program, as base-58 encoded string
#### Results:
The result field will be an array of arrays. Each sub array will contain:
* `string` - the account Pubkey as base-58 encoded string and a JSON object, with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
```
### getRecentBlockhash
Returns a recent block hash from the ledger, and a fee schedule that can be used to compute the cost of submitting a transaction using it.
#### Parameters:
None
#### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
```
### getSignatureStatus
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
#### Results:
* `null` - Unknown transaction
* `object` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
```
### getSlot
Returns the current slot the node is processing
#### Parameters:
None
#### Results:
* `u64` - Current slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1234","id":1}
```
### getSlotLeader
Returns the current slot leader
#### Parameters:
None
#### Results:
* `string` - Node Id as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
```
### getSlotsPerSegment
Returns the current storage segment size in terms of slots
#### Parameters:
None
#### Results:
* `u64` - Number of slots in a storage segment
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getStorageTurn
Returns the current storage turn's blockhash and slot
#### Parameters:
None
#### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
* `u64` - the current storage turn slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
```
### getStorageTurnRate
Returns the current storage turn rate in terms of slots per turn
#### Parameters:
None
#### Results:
* `u64` - Number of slots in storage turn
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getNumBlocksSinceSignatureConfirmation
Returns the current number of blocks since signature has been confirmed.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
#### Results:
* `integer` - count, as unsigned 64-bit integer
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":8,"id":1}
```
### getTransactionCount
Returns the current Transaction count from the ledger
#### Parameters:
None
#### Results:
* `integer` - count, as unsigned 64-bit integer
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":268,"id":1}
```
### getTotalSupply
Returns the current total supply in Lamports
#### Parameters:
None
#### Results:
* `integer` - Total supply, as unsigned 64-bit integer
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":10126,"id":1}
```
### getVersion
Returns the current solana versions running on the node
#### Parameters:
None
#### Results:
The result field will be a JSON object with the following sub fields:
* `solana-core`, software version of solana-core
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
```
### getVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current bank.
#### Parameters:
None
#### Results:
The result field will be a JSON object of `current` and `delinquent` accounts, each containing an array of JSON objects with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
* `commission`, an 8-bit integer used as a fraction \(commission/MAX\_U8\) for rewards payout
* `lastVote` - Most recent slot voted on by this vote account
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
```
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
#### Parameters:
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
* `integer` - lamports, as a signed 64-bit integer
#### Results:
* `string` - Transaction Signature of airdrop, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
```
### sendTransaction
Creates new transaction
#### Parameters:
* `array` - array of octets containing a fully-signed Transaction
#### Results:
* `string` - Transaction Signature, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
```
### Subscription Websocket
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
* Submit subscription requests to the websocket using the methods below
* Multiple subscriptions may be active at once
* All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
### accountSubscribe
Subscribe to an account to receive notifications when the lamports or data for a given account public key changes
#### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
### accountUnsubscribe
Unsubscribe from account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### programSubscribe
Subscribe to a program to receive notifications when the lamports or data for a given account owned by the program changes
#### Parameters:
* `string` - program\_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
* `string` - account Pubkey, as base-58 encoded string
* `object` - account info JSON object \(see [getAccountInfo](jsonrpc-api.md#getaccountinfo) for field details\)
```bash
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
```
### programUnsubscribe
Unsubscribe from program-owned account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### signatureSubscribe
Subscribe to a transaction signature to receive notification when the transaction is confirmed On `signatureNotification`, the subscription is automatically cancelled
#### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
```
### signatureUnsubscribe
Unsubscribe from signature confirmation notification
#### Parameters:
* `integer` - subscription id to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```

View File

@@ -1,63 +0,0 @@
# Transaction
## Components of a `Transaction`
* **Transaction:**
* **message:** Defines the transaction
* **header:** Details the account types of and signatures required by
the transaction
* **num\_required\_signatures:** The total number of signatures
required to make the transaction valid.
* **num\_credit\_only\_signed\_accounts:** The last
`num_credit_only_signed_accounts` signatures refer to signing
credit only accounts. Credit only accounts can be used concurrently
by multiple parallel transactions, but their balance may only be
increased, and their account data is read-only.
* **num\_credit\_only\_unsigned\_accounts:** The last
`num_credit_only_unsigned_accounts` pubkeys in `account_keys` refer
to non-signing credit only accounts
* **account\_keys:** List of pubkeys used by the transaction, including
by the instructions and for signatures. The first
`num_required_signatures` pubkeys must sign the transaction.
* **recent\_blockhash:** The ID of a recent ledger entry. Validators will
reject transactions with a `recent_blockhash` that is too old.
* **instructions:** A list of [instructions](https://github.com/solana-labs/solana/tree/6b18db969dd1616eff07de35e7b823c75339fea8/book/src/instruction.md) that are
run sequentially and committed in one atomic transaction if all
succeed.
* **signatures:** A list of signatures applied to the transaction. The
list is always of length `num_required_signatures`, and the signature
at index `i` corresponds to the pubkey at index `i` in `account_keys`.
The list is initialized with empty signatures \(i.e. zeros\), and
populated as signatures are added.
## Transaction Signing
A `Transaction` is signed by using an ed25519 keypair to sign the serialization of the `message`. The resulting signature is placed at the index of `signatures` matching the index of the keypair's pubkey in `account_keys`.
## Transaction Serialization
`Transaction`s \(and their `message`s\) are serialized and deserialized using the [bincode](https://crates.io/crates/bincode) crate with a non-standard vector serialization that uses only one byte for the length if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3 bytes if it requires 15 or 16 bits. The vector serialization is defined by Solana's [short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).

View File

@@ -4,7 +4,7 @@ A validator votes on a PoH hash for two purposes. First, the vote indicates it
believes the ledger is valid up until that point in time. Second, since many
valid forks may exist at a given height, the vote also indicates exclusive
support for the fork. This document describes only the former. The latter is
described in [Tower BFT](tower-bft.md).
described in [fork selection](fork-selection.md).
## Current Design
@@ -50,11 +50,12 @@ log the time since the NewBlock transaction was submitted.
### Finality and Payouts
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
that payment to miners be postponed until the *stack* of validator votes reaches
a certain depth, at which point rollback is not economically feasible. The vote
program may therefore implement Tower BFT. Vote instructions would need to
reference a global Tower account so that it can track cross-block state.
Locktower is the proposed [fork selection](fork-selection.md) algorithm. It
proposes that payment to miners be postponed until the *stack* of validator
votes reaches a certain depth, at which point rollback is not economically
feasible. The vote program may therefore implement locktower. Vote instructions
would need to reference a global locktower account so that it can track
cross-block state.
## Challenges

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

@@ -9,29 +9,29 @@ naturally form as a result of leader rotation is described in
described here is how a validator copes with those forks until blocks
are finalized.
The blocktree allows a validator to record every shred it observes
on the network, in any order, as long as the shred is signed by the expected
The blocktree allows a validator to record every blob it observes
on the network, in any order, as long as the blob is signed by the expected
leader for a given slot.
Shreds are moved to a fork-able key space the tuple of `leader slot` + `shred
Blobs are moved to a fork-able key space the tuple of `leader slot` + `blob
index` (within the slot). This permits the skip-list structure of the Solana
protocol to be stored in its entirety, without a-priori choosing which fork to
follow, which Entries to persist or when to persist them.
Repair requests for recent shreds are served out of RAM or recent files and out
of deeper storage for less recent shreds, as implemented by the store backing
Repair requests for recent blobs are served out of RAM or recent files and out
of deeper storage for less recent blobs, as implemented by the store backing
Blocktree.
### Functionalities of Blocktree
1. Persistence: the Blocktree lives in the front of the nodes verification
pipeline, right behind network receive and signature verification. If the
shred received is consistent with the leader schedule (i.e. was signed by the
blob received is consistent with the leader schedule (i.e. was signed by the
leader for the indicated slot), it is immediately stored.
2. Repair: repair is the same as window repair above, but able to serve any
shred that's been received. Blocktree stores shreds with signatures,
blob that's been received. Blocktree stores blobs with signatures,
preserving the chain of origination.
3. Forks: Blocktree supports random access of shreds, so can support a
3. Forks: Blocktree supports random access of blobs, so can support a
validator's need to rollback and replay from a Bank checkpoint.
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
ordered enumeration of entries from slot 0. The logic of the replay stage
@@ -41,22 +41,22 @@ the Blocktree.
### Blocktree Design
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated
slot index and shred index for an entry, and the value is the entry data. Note shred indexes are zero-based for each slot (i.e. they're slot-relative).
slot index and blob index for an entry, and the value is the entry data. Note blob indexes are zero-based for each slot (i.e. they're slot-relative).
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
* `slot_index` - The index of this slot
* `num_blocks` - The number of blocks in the slot (used for chaining to a previous slot)
* `consumed` - The highest shred index `n`, such that for all `m < n`, there exists a shred in this slot with shred index equal to `n` (i.e. the highest consecutive shred index).
* `received` - The highest received shred index for the slot
* `consumed` - The highest blob index `n`, such that for all `m < n`, there exists a blob in this slot with blob index equal to `n` (i.e. the highest consecutive blob index).
* `received` - The highest received blob index for the slot
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
the ledger to find possible fork points.
* `last_index` - The index of the shred that is flagged as the last shred for this slot. This flag on a shred will be set by the leader for a slot when they are transmitting the last shred for a slot.
* `last_index` - The index of the blob that is flagged as the last blob for this slot. This flag on a blob will be set by the leader for a slot when they are transmitting the last blob for a slot.
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is_rooted for each slot with the following rules. Let slot(n) be the slot with index `n`, and slot(n).is_full() is true if the slot with index `n` has all the ticks expected for that slot. Let is_rooted(n) be the statement that "the slot(n).is_rooted is true". Then:
is_rooted(0)
is_rooted(n+1) iff (is_rooted(n) and slot(n).is_full()
3. Chaining - When a shred for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the shred). We then know that this new slot chains to slot `x - num_blocks`.
3. Chaining - When a blob for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the blob). We then know that this new slot chains to slot `x - num_blocks`.
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.

View File

@@ -1,865 +0,0 @@
## solana CLI
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
### Examples
#### Get Pubkey
```sh
// Command
$ solana address
// Return
<PUBKEY>
```
#### Airdrop SOL/Lamports
```sh
// Command
$ solana airdrop 2
// Return
"2.00000000 SOL"
// Command
$ solana airdrop 123 --lamports
// Return
"123 lamports"
```
#### Get Balance
```sh
// Command
$ solana balance
// Return
"3.00050001 SOL"
```
#### Confirm Transaction
```sh
// Command
$ solana confirm <TX_SIGNATURE>
// Return
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
```
#### Deploy program
```sh
// Command
$ solana deploy <PATH>
// Return
<PROGRAM_ID>
```
#### Unconditional Immediate Transfer
```sh
// Command
$ solana pay <PUBKEY> 123
// Return
<TX_SIGNATURE>
```
#### Post-Dated Transfer
```sh
// Command
$ solana pay <PUBKEY> 123 \
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
*`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key*
#### Authorized Transfer
A third party must send a signature to unlock the lamports.
```sh
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
#### Post-Dated and Authorized Transfer
```sh
// Command
$ solana pay <PUBKEY> 123 \
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
#### Multiple Witnesses
```sh
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
#### Cancelable Transfer
```sh
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--cancelable
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
#### Cancel Transfer
```sh
// Command
$ solana cancel <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
#### Send Signature
```sh
// Command
$ solana send-signature <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
#### Indicate Elapsed Time
Use the current system time:
```sh
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
Or specify some other arbitrary timestamp:
```sh
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
// Return
<TX_SIGNATURE>
```
### Usage
```manpage
solana 0.12.0
USAGE:
solana [FLAGS] [OPTIONS] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
SUBCOMMANDS:
address Get your public key
airdrop Request lamports
authorize-voter Authorize a new vote signing keypair for the given vote account
balance Get your balance
cancel Cancel a transfer
claim-storage-reward Redeem storage reward credits
cluster-version Get the version of the cluster entrypoint
confirm Confirm transaction by signature
create-replicator-storage-account Create a replicator storage account
create-storage-mining-pool-account Create mining pool account
create-validator-storage-account Create a validator storage account
create-vote-account Create a vote account
deactivate-stake Deactivate the delegated stake from the stake account
delegate-stake Delegate stake to a vote account
deploy Deploy a program
fees Display current cluster fees
get Get wallet config settings
get-slot Get current slot
get-transaction-count Get current transaction count
help Prints this message or the help of the given subcommand(s)
pay Send a payment
ping Submit transactions sequentially
redeem-vote-credits Redeem credits in the stake account
send-signature Send a signature to authorize a transfer
send-timestamp Send a timestamp to unlock a transfer
set Set a wallet config setting
show-account Show the contents of an account
show-stake-account Show the contents of a stake account
show-storage-account Show the contents of a storage account
show-vote-account Show the contents of a vote account
validator-info Publish/get Validator info on Solana
withdraw-stake Withdraw the unstaked lamports from the stake account
```
```manpage
solana-address
Get your public key
USAGE:
solana address [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-airdrop
Request a batch of lamports
USAGE:
solana airdrop [OPTIONS] <AMOUNT> [unit]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
--drone-host <HOST> Drone host to use [default: the --url host]
--drone-port <PORT> Drone port to use [default: 9900]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<AMOUNT> The airdrop amount to request (default unit SOL)
<unit> Specify unit to use for request and balance display [possible values: SOL, lamports]
```
```manpage
solana-authorize-voter
Authorize a new vote signing keypair for the given vote account
USAGE:
solana authorize-voter [OPTIONS] <VOTE ACCOUNT PUBKEY> <CURRENT VOTER KEYPAIR FILE> <NEW VOTER PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
<CURRENT VOTER KEYPAIR FILE> Keypair file for the currently authorized vote signer
<NEW VOTER PUBKEY> New vote signer to authorize
```
```manpage
solana-balance
Get your balance
USAGE:
solana balance [FLAGS] [OPTIONS] [PUBKEY]
FLAGS:
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PUBKEY> The public key of the balance to check
```
```manpage
solana-cancel
Cancel a transfer
USAGE:
solana cancel [OPTIONS] <PROCESS ID>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PROCESS ID> The process id of the transfer to cancel
```
```manpage
solana-claim-storage-reward
Redeem storage reward credits
USAGE:
solana claim-storage-reward [OPTIONS] <NODE PUBKEY> <STORAGE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<NODE PUBKEY> The node account to credit the rewards to
<STORAGE ACCOUNT PUBKEY> Storage account address to redeem credits for
```
```manpage
solana-cluster-version
Get the version of the cluster entrypoint
USAGE:
solana cluster-version [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-confirm
Confirm transaction by signature
USAGE:
solana confirm [OPTIONS] <SIGNATURE>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<SIGNATURE> The transaction signature to confirm
```
```manpage
solana-create-replicator-storage-account
Create a replicator storage account
USAGE:
solana create-replicator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
```manpage
solana-create-storage-mining-pool-account
Create mining pool account
USAGE:
solana create-storage-mining-pool-account [OPTIONS] <STORAGE ACCOUNT PUBKEY> <AMOUNT> [unit]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT PUBKEY> Storage mining pool account address to fund
<AMOUNT> The amount to assign to the storage mining pool account (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
```
```manpage
solana-create-validator-storage-account
Create a validator storage account
USAGE:
solana create-validator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
```manpage
solana-create-vote-account
Create a vote account
USAGE:
solana create-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY> <VALIDATOR PUBKEY> <LAMPORTS>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account address to fund
<VALIDATOR PUBKEY> Validator that will vote with this account
<LAMPORTS> The amount of lamports to send to the vote account
```
```manpage
solana-deactivate-stake
Deactivate the delegated stake from the stake account
USAGE:
solana deactivate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the delegate transaction.
<PUBKEY> The vote account to which the stake is currently delegated
```
```manpage
solana-delegate-stake
Delegate stake to a vote account
USAGE:
solana delegate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <VOTE ACCOUNT PUBKEY> <AMOUNT> [unit]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the new stake account
<VOTE ACCOUNT PUBKEY> The vote account to which the stake will be delegated
<AMOUNT> The amount to delegate (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
```
```manpage
solana-deploy
Deploy a program
USAGE:
solana deploy [OPTIONS] <PATH TO PROGRAM>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PATH TO PROGRAM> /path/to/program.o
```
```manpage
solana-fees
Display current cluster fees
USAGE:
solana fees [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-get
Get wallet config settings
USAGE:
solana get [OPTIONS] [CONFIG_FIELD]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<CONFIG_FIELD> Return a specific config setting [possible values: url, keypair]
```
```manpage
solana-get-slot
Get current slot
USAGE:
solana get-slot [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-get-transaction-count
Get current transaction count
USAGE:
solana get-transaction-count [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-pay
Send a payment
USAGE:
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [unit]
FLAGS:
--cancelable
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default:
/Users/tyeraeulberg/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--after <DATETIME> A timestamp after which transaction will execute
--require-timestamp-from <PUBKEY> Require timestamp from this third party
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
ARGS:
<PUBKEY> The public key of recipient
<AMOUNT> The amount to send (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
```
```manpage
solana-ping
Submit transactions sequentially
USAGE:
solana ping [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/wallet/config.yml]
-c, --count <NUMBER> Stop after submitting count transactions
-i, --interval <SECONDS> Wait interval seconds between submitting the next transaction [default: 2]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-t, --timeout <SECONDS> Wait up to timeout seconds for transaction confirmation [default: 10]
```
```manpage
solana-redeem-vote-credits
Redeem credits in the stake account
USAGE:
solana redeem-vote-credits [OPTIONS] <STAKING ACCOUNT PUBKEY> <VOTE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKING ACCOUNT PUBKEY> Staking account address to redeem credits for
<VOTE ACCOUNT PUBKEY> The vote account to which the stake was previously delegated.
```
```manpage
solana-send-signature
Send a signature to authorize a transfer
USAGE:
solana send-signature [OPTIONS] <PUBKEY> <PROCESS ID>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PUBKEY> The public key of recipient
<PROCESS ID> The process id of the transfer to authorize
```
```manpage
solana-send-timestamp
Send a timestamp to unlock a transfer
USAGE:
solana send-timestamp [OPTIONS] <PUBKEY> <PROCESS ID>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
--date <DATETIME> Optional arbitrary timestamp to apply
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PUBKEY> The public key of recipient
<PROCESS ID> The process id of the transfer to unlock
```
```manpage
solana-set
Set a wallet config setting
USAGE:
solana set [OPTIONS] <--url <URL>|--keypair <PATH>>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
```
```manpage
solana-show-account
Show the contents of an account
USAGE:
solana show-account [FLAGS] [OPTIONS] <ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-o, --output <FILE> Write the account data to this file
ARGS:
<ACCOUNT PUBKEY> Account public key
```
```manpage
solana-show-stake-account
Show the contents of a stake account
USAGE:
solana show-stake-account [OPTIONS] <STAKE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT PUBKEY> Stake account public key
```
```manpage
solana-show-storage-account
Show the contents of a storage account
USAGE:
solana show-storage-account [OPTIONS] <STORAGE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT PUBKEY> Storage account public key
```
```manpage
solana-show-vote-account
Show the contents of a vote account
USAGE:
solana show-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account public key
```
```manpage
solana-validator-info
Publish/get Validator info on Solana
USAGE:
solana validator-info [OPTIONS] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
SUBCOMMANDS:
get Get and parse Solana Validator info
help Prints this message or the help of the given subcommand(s)
publish Publish Validator info on Solana
```
```manpage
solana-withdraw-stake
Withdraw the unstaked lamports from the stake account
USAGE:
solana withdraw-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <DESTINATION PUBKEY> <AMOUNT> [unit]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the withdraw transaction.
<DESTINATION PUBKEY> The account where the lamports should be transfered
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
```

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.
@@ -51,28 +51,28 @@ At test start, the cluster has already been established and is fully connected.
The test can discover most of the available nodes over a few second.
```rust,ignore
use crate::gossip_service::discover_nodes;
use crate::gossip_service::discover;
// Discover the cluster over a few seconds.
let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
let cluster_nodes = discover(&entry_point_info, num_nodes);
```
## Cluster Configuration
To enable specific scenarios, the cluster needs to be booted with special
configurations. These configurations can be captured in
`fullnode::ValidatorConfig`.
`fullnode::FullnodeConfig`.
For example:
```rust,ignore
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_fullnode_exit = true;
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_fullnode_exit = true;
let local = LocalCluster::new_with_config(
num_nodes,
10_000,
100,
&validator_config
&fullnode_config
);
```
@@ -86,9 +86,9 @@ advertised gossip nodes.
Configure the RPC service:
```rust,ignore
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_rpc_gossip_push = true;
validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_rpc_gossip_push = true;
fullnode_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
```
Wire the RPCs and write a new test:
@@ -99,10 +99,10 @@ pub fn test_large_invalid_gossip_nodes(
funding_keypair: &Keypair,
num_nodes: usize,
) {
let cluster = discover_nodes(&entry_point_info, num_nodes);
let cluster = discover(&entry_point_info, num_nodes);
// Poison the cluster.
let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE);
let mut client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE);
for _ in 0..(num_nodes * 100) {
client.gossip_push(
cluster_info::invalid_contact_info()
@@ -112,7 +112,7 @@ pub fn test_large_invalid_gossip_nodes(
// Force refresh of the active set.
for node in &cluster {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
let mut client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
client.gossip_refresh_active_set();
}

View File

@@ -13,7 +13,7 @@ buggy and malicious nodes.
Before starting any fullnodes, one first needs to create a *genesis block*.
The block contains entries referencing two public keys, a *mint* and a
*bootstrap leader*. The fullnode holding the bootstrap leader's private key is
*bootstrap leader*. The fullnode holding the bootstrap leader's secret key is
responsible for appending the first entries to the ledger. It initializes its
internal state with the mint's account. That account will hold the number of
native tokens defined by the genesis block. The second fullnode then contacts
@@ -28,7 +28,7 @@ its copy.
## Joining a Cluster
Validators and replicators enter the cluster via registration messages sent to
Fullnodes and replicators enter the cluster via registration messages sent to
its *control plane*. The control plane is implemented using a *gossip*
protocol, meaning that a node may register with any existing node, and expect
its registration to propagate to all nodes in the cluster. The time it takes

View File

@@ -1,41 +0,0 @@
# A Solana Cluster
A Solana cluster is a set of fullnodes working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this chapter, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
## Creating a Cluster
Before starting any fullnodes, one first needs to create a _genesis block_. The block contains entries referencing two public keys, a _mint_ and a _bootstrap leader_. The fullnode holding the bootstrap leader's secret key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis block. The second fullnode then contacts the bootstrap leader to register as a _validator_ or _replicator_. Additional fullnodes then register with any registered member of the cluster.
A validator receives all entries from the leader and submits votes confirming those entries are valid. After voting, the validator is expected to store those entries until replicator nodes submit proofs that they have stored copies of it. Once the validator observes a sufficient number of copies exist, it deletes its copy.
## Joining a Cluster
Validators and replicators enter the cluster via registration messages sent to its _control plane_. The control plane is implemented using a _gossip_ protocol, meaning that a node may register with any existing node, and expect its registration to propagate to all nodes in the cluster. The time it takes for all nodes to synchronize is proportional to the square of the number of nodes participating in the cluster. Algorithmically, that's considered very slow, but in exchange for that time, a node is assured that it eventually has all the same information as every other node, and that that information cannot be censored by any one node.
## Sending Transactions to a Cluster
Clients send transactions to any fullnode's Transaction Processing Unit \(TPU\) port. If the node is in the validator role, it forwards the transaction to the designated leader. If in the leader role, the node bundles incoming transactions, timestamps them creating an _entry_, and pushes them onto the cluster's _data plane_. Once on the data plane, the transactions are validated by validator nodes and replicated by replicator nodes, effectively appending them to the ledger.
## Confirming Transactions
A Solana cluster is capable of subsecond _confirmation_ for up to 150 nodes with plans to scale up to hundreds of thousands of nodes. Once fully implemented, confirmation times are expected to increase only with the logarithm of the number of validators, where the logarithm's base is very high. If the base is one thousand, for example, it means that for the first thousand nodes, confirmation will be the duration of three network hops plus the time it takes the slowest validator of a supermajority to vote. For the next million nodes, confirmation increases by only one network hop.
Solana defines confirmation as the duration of time from when the leader timestamps a new entry to the moment when it recognizes a supermajority of ledger votes.
A gossip network is much too slow to achieve subsecond confirmation once the network grows beyond a certain size. The time it takes to send messages to all nodes is proportional to the square of the number of nodes. If a blockchain wants to achieve low confirmation and attempts to do it using a gossip network, it will be forced to centralize to just a handful of nodes.
Scalable confirmation can be achieved using the follow combination of techniques:
1. Timestamp transactions with a VDF sample and sign the timestamp.
2. Split the transactions into batches, send each to separate nodes and have
each node share its batch with its peers.
3. Repeat the previous step recursively until all nodes have all batches.
Solana rotates leaders at fixed intervals, called _slots_. Each leader may only produce entries during its allotted slot. The leader therefore timestamps transactions so that validators may lookup the public key of the designated leader. The leader then signs the timestamp so that a validator may verify the signature, proving the signer is owner of the designated leader's public key.
Next, transactions are broken into batches so that a node can send transactions to multiple parties without making multiple copies. If, for example, the leader needed to send 60 transactions to 6 nodes, it would break that collection of 60 into batches of 10 transactions and send one to each node. This allows the leader to put 60 transactions on the wire, not 60 transactions for each node. Each node then shares its batch with its peers. Once the node has collected all 6 batches, it reconstructs the original set of 60 transactions.
A batch of transactions can only be split so many times before it is so small that header information becomes the primary consumer of network bandwidth. At the time of this writing, the approach is scaling well up to about 150 validators. To scale up to hundreds of thousands of validators, each node can apply the same technique as the leader node to another set of nodes of equal size. We call the technique _data plane fanout_; learn more in the [data plan fanout](https://github.com/solana-labs/solana/tree/6b18db969dd1616eff07de35e7b823c75339fea8/book/src/data-plane-fanout.md) section.

View File

@@ -1,81 +0,0 @@
# Fork Generation
The chapter describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
## Overview
Nodes take turns being leader and generating the PoH that encodes state changes. The cluster can tolerate loss of connection to any leader by synthesizing what the leader _**would**_ have generated had it been connected but not ingesting any state changes. The possible number of forks is thereby limited to a "there/not-there" skip list of forks that may arise on leader rotation slot boundaries. At any given slot, only a single leader's transactions will be accepted.
## Message Flow
1. Transactions are ingested by the current leader.
2. Leader filters valid transactions.
3. Leader executes valid transactions updating its state.
4. Leader packages transactions into entries based off its current PoH slot.
5. Leader transmits the entries to validator nodes \(in signed blobs\)
1. The PoH stream includes ticks; empty entries that indicate liveness of
the leader and the passage of time on the cluster.
2. A leader's stream begins with the tick entries necessary complete the PoH
back to the leaders most recently observed prior leader slot.
6. Validators retransmit entries to peers in their set and to further
downstream nodes.
7. Validators validate the transactions and execute them on their state.
8. Validators compute the hash of the state.
9. At specific times, i.e. specific PoH tick counts, validators transmit votes
to the leader.
1. Votes are signatures of the hash of the computed state at that PoH tick
count
2. Votes are also propagated via gossip
10. Leader executes the votes as any other transaction and broadcasts them to
the cluster.
11. Validators observe their votes and all the votes from the cluster.
## Partitions, Forks
Forks can arise at PoH tick counts that correspond to a vote. The next leader may not have observed the last vote slot and may start their slot with generated virtual PoH entries. These empty ticks are generated by all nodes in the cluster at a cluster-configured rate for hashes/per/tick `Z`.
There are only two possible versions of the PoH during a voting slot: PoH with `T` ticks and entries generated by the current leader, or PoH with just ticks. The "just ticks" version of the PoH can be thought of as a virtual ledger, one that all nodes in the cluster can derive from the last tick in the previous slot.
Validators can ignore forks at other points \(e.g. from the wrong leader\), or slash the leader responsible for the fork.
Validators vote based on a greedy choice to maximize their reward described in [Tower BFT](../implemented-proposals/tower-bft.md).
### Validator's View
#### Time Progression
The diagram below represents a validator's view of the PoH stream with possible forks over time. L1, L2, etc. are leader slots, and `E`s represent entries from that leader during that leader's slot. The `x`s represent ticks only, and time flows downwards in the diagram.
![Fork generation](https://github.com/solana-labs/solana/tree/6b18db969dd1616eff07de35e7b823c75339fea8/book/src/img/fork-generation.svg)
Note that an `E` appearing on 2 forks at the same slot is a slashable condition, so a validator observing `E3` and `E3'` can slash L3 and safely choose `x` for that slot. Once a validator commits to a forks, other forks can be discarded below that tick count. For any slot, validators need only consider a single "has entries" chain or a "ticks only" chain to be proposed by a leader. But multiple virtual entries may overlap as they link back to the a previous slot.
#### Time Division
It's useful to consider leader rotation over PoH tick count as time division of the job of encoding state for the cluster. The following table presents the above tree of forks as a time-divided ledger.
| leader slot | L1 | L2 | L3 | L4 | L5 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| data | E1 | E2 | E3 | E4 | E5 |
| ticks since prev | | | | x | xx |
Note that only data from leader L3 will be accepted during leader slot L3. Data from L3 may include "catchup" ticks back to a slot other than L2 if L3 did not observe L2's data. L4 and L5's transmissions include the "ticks to prev" PoH entries.
This arrangement of the network data streams permits nodes to save exactly this to the ledger for replay, restart, and checkpoints.
### Leader's View
When a new leader begins a slot, it must first transmit any PoH \(ticks\) required to link the new slot with the most recently observed and voted slot. The fork the leader proposes would link the current slot to a previous fork that the leader has voted on with virtual ticks.

View File

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

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