Compare commits
	
		
			162 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3e29325410 | ||
|  | 4dc98c3dbd | ||
|  | 9caad645e2 | ||
|  | 6cb76ac326 | ||
|  | 0001e5c0a1 | ||
|  | ab32d13da1 | ||
|  | cefe46e981 | ||
|  | f4d70e78b6 | ||
|  | d130adf582 | ||
|  | 1e6285e64e | ||
|  | e3c90c3807 | ||
|  | 85750307aa | ||
|  | 0ee4a5e799 | ||
|  | 55cb9cf681 | ||
|  | d3af7e0653 | ||
|  | 729a24d557 | ||
|  | 55b92c16da | ||
|  | 835bacce4f | ||
|  | ccb7b1a698 | ||
|  | 85dbdeb4c3 | ||
|  | 397f9f11c5 | ||
|  | a11986ad1d | ||
|  | a4d373f0af | ||
|  | 52eea215ce | ||
|  | 6f48aafd3a | ||
|  | 2d94c09aee | ||
|  | 9699b61679 | ||
|  | 8865bfbd59 | ||
|  | 5f80c1d37d | ||
|  | f616f5dec6 | ||
|  | db1003b5f8 | ||
|  | f52ff777b7 | ||
|  | 4314a29953 | ||
|  | e560fff840 | ||
|  | 5ac747ea7d | ||
|  | f522dc1e18 | ||
|  | 486812bf54 | ||
|  | 7df8f76df1 | ||
|  | bbe4990e80 | ||
|  | a5baaf790d | ||
|  | 0a36ed1b8c | ||
|  | b7ad240375 | ||
|  | 2cc71f2d55 | ||
|  | 3125c74681 | ||
|  | d5b1dee8d6 | ||
|  | 4b33a2a1b8 | ||
|  | 58e6a5c281 | ||
|  | 7eb61074ab | ||
|  | 9b2edbaa9b | ||
|  | e8659b45c7 | ||
|  | a9553cb401 | ||
|  | 800c409698 | ||
|  | b6f484ddee | ||
|  | 3c39fee5a8 | ||
|  | 560f34d1f6 | ||
|  | dbda50941a | ||
|  | f1e68ac25c | ||
|  | 95029b9b05 | ||
|  | a789bf4761 | ||
|  | d2e7ffa8b9 | ||
|  | 0914519f6a | ||
|  | 43cd5f3730 | ||
|  | d396a5f45a | ||
|  | 76a7071dba | ||
|  | 133baa8ce6 | ||
|  | 5df3510fde | ||
|  | 357339273f | ||
|  | 2500881e0b | ||
|  | 0013bfff4e | ||
|  | f13498b428 | ||
|  | b567138170 | ||
|  | 653982cae5 | ||
|  | 605f4906ba | ||
|  | d27f24e312 | ||
|  | c9c1cb5c9c | ||
|  | 1cc6493ccf | ||
|  | ae47862be2 | ||
|  | 8590184df7 | ||
|  | d840bbab08 | ||
|  | 63314de516 | ||
|  | c47a6e12c7 | ||
|  | 7937c45ba4 | ||
|  | 813b11ac56 | ||
|  | ad6883b66a | ||
|  | a8f4c4e297 | ||
|  | 6d68e94e4e | ||
|  | 5dd40d7d88 | ||
|  | 3f58177670 | ||
|  | edfd65b115 | ||
|  | 51da66ec84 | ||
|  | ba36308d69 | ||
|  | ee450b2dd0 | ||
|  | 84b28fb261 | ||
|  | 1586b86797 | ||
|  | 8f065e487e | ||
|  | 953eadd983 | ||
|  | a4a792facd | ||
|  | 055f808f98 | ||
|  | 0404878445 | ||
|  | 053907f8a4 | ||
|  | f76dcc1f05 | ||
|  | 823bc138cd | ||
|  | 18f746b025 | ||
|  | c81adaf901 | ||
|  | 2d12ddd0f6 | ||
|  | bee36cc8d0 | ||
|  | f7aee67023 | ||
|  | c021727009 | ||
|  | 6653136e1d | ||
|  | 06c40c807c | ||
|  | 9b262b4915 | ||
|  | cc2d3ecfd7 | ||
|  | 92743499bf | ||
|  | aa6a00a03e | ||
|  | bd19f7c4cb | ||
|  | 988bf65ba4 | ||
|  | d5b03bd824 | ||
|  | 6a72dab111 | ||
|  | 56e8319a6d | ||
|  | aed1e51ef1 | ||
|  | f4278d61df | ||
|  | a5c3ae3cef | ||
|  | 05c052e212 | ||
|  | dc05bb648a | ||
|  | 800b65b2f6 | ||
|  | ae1a0f57c5 | ||
|  | df7c44bd0c | ||
|  | 3e29cfd712 | ||
|  | 202031538f | ||
|  | 29ff1b925d | ||
|  | 5a91db6e62 | ||
|  | 94ba700e58 | ||
|  | 1964c6ec29 | ||
|  | 4dd6591bfd | ||
|  | 163217815b | ||
|  | 37c182cd5d | ||
|  | 0c68f27ac3 | ||
|  | 5fb8da9b35 | ||
|  | 74d9fd1e4f | ||
|  | e71206c578 | ||
|  | 0141c80238 | ||
|  | ed928cfdf7 | ||
|  | 2fd319ab7a | ||
|  | 7813a1decd | ||
|  | 93e4ed1f75 | ||
|  | a70f31b3da | ||
|  | 2d25227d0a | ||
|  | fc7bfd0f67 | ||
|  | 2996291b37 | ||
|  | 3e80b9231c | ||
|  | 78231a8682 | ||
|  | ace711e7f1 | ||
|  | c9cbc39ec9 | ||
|  | 606a392d50 | ||
|  | c67596ceb4 | ||
|  | 9a42cc7555 | ||
|  | 2e5ef2a802 | ||
|  | 8c8e2c4b2b | ||
|  | 0578801f99 | ||
|  | 6141e1410a | ||
|  | 4fc86807ff | ||
|  | d2a2eba69e | 
							
								
								
									
										8
									
								
								.buildkite/env/secrets.ejson
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.buildkite/env/secrets.ejson
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,12 @@ | |||||||
| { | { | ||||||
|     "_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f", |     "_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f", | ||||||
|     "environment": { |     "environment": { | ||||||
|       "CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]" |       "CODECOV_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:JnxhrIxh09AvqdJgrVSYmb7PxSrh19aE:07WzVExCHEd1lJ1m8QizRRthGri+WBNeZRKjjEvsy5eo4gv3HD7zVEm42tVTGkqITKkBNQ==]", | ||||||
|  |       "CRATES_IO_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:d0jJqC32/axwzq/N7kMRmpxKhnRrhtpt:zvcPHwkOzGnjhNkAQSejwdy1Jkr9wR1qXFFCnfIjyt/XQYubzB1tLkoly/qdmeb5]", | ||||||
|  |       "GEOLOCATION_API_KEY": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R4gfB6Ey4i50HyfLt4UZDLBqg3qHEUye:UfZCOgt8XI6Y2g+ivCRVoS1fjFycFs7/GSevvCqh1B50mG0+hzpEyzXQLuKG5OeI]", | ||||||
|  |       "GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]", | ||||||
|  |       "INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]", | ||||||
|  |       "INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]", | ||||||
|  |       "INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,19 +3,16 @@ | |||||||
| # | # | ||||||
| # Save target/ for the next CI build on this machine | # Save target/ for the next CI build on this machine | ||||||
| # | # | ||||||
| if [[ -z $CARGO_TARGET_CACHE ]]; then | ( | ||||||
|   echo "+++ CARGO_TARGET_CACHE not defined" # pre-command should have defined it |   set -x | ||||||
| else |   d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL" | ||||||
|   ( |   mkdir -p "$d" | ||||||
|     set -x |   set -x | ||||||
|     mkdir -p "$CARGO_TARGET_CACHE" |   rsync -a --delete --link-dest="$PWD" target "$d" | ||||||
|     set -x |   du -hs "$d" | ||||||
|     rsync -a --delete --link-dest="$PWD" target "$CARGO_TARGET_CACHE" |   read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d") | ||||||
|     du -hs "$CARGO_TARGET_CACHE" |   echo "--- ${cacheSizeInGB}GB: $d" | ||||||
|     read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE") | ) | ||||||
|     echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE" |  | ||||||
|   ) |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # | # | ||||||
| # Add job_stats data point | # Add job_stats data point | ||||||
|   | |||||||
| @@ -11,29 +11,23 @@ export PS4="++" | |||||||
| # | # | ||||||
| # Restore target/ from the previous CI build on this machine | # Restore target/ from the previous CI build on this machine | ||||||
| # | # | ||||||
| eval "$(ci/channel-info.sh)" |  | ||||||
| export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL" |  | ||||||
| ( | ( | ||||||
|   set -x |   set -x | ||||||
|  |   d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL" | ||||||
|   MAX_CACHE_SIZE=18 # gigabytes |   MAX_CACHE_SIZE=18 # gigabytes | ||||||
|  |  | ||||||
|   if [[ -d $CARGO_TARGET_CACHE ]]; then |   if [[ -d $d ]]; then | ||||||
|     du -hs "$CARGO_TARGET_CACHE" |     du -hs "$d" | ||||||
|     read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE") |     read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d") | ||||||
|     echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE" |     echo "--- ${cacheSizeInGB}GB: $d" | ||||||
|     if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then |     if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then | ||||||
|       echo "--- $CARGO_TARGET_CACHE is too large, removing it" |       echo "--- $d is too large, removing it" | ||||||
|       rm -rf "$CARGO_TARGET_CACHE" |       rm -rf "$d" | ||||||
|     fi |     fi | ||||||
|   else |   else | ||||||
|     echo "--- $CARGO_TARGET_CACHE not present" |     echo "--- $d not present" | ||||||
|   fi |   fi | ||||||
|  |  | ||||||
|   mkdir -p "$CARGO_TARGET_CACHE"/target |   mkdir -p "$d"/target | ||||||
|   rsync -a --delete --link-dest="$CARGO_TARGET_CACHE" "$CARGO_TARGET_CACHE"/target . |   rsync -a --delete --link-dest="$d" "$d"/target . | ||||||
|  |  | ||||||
|   # Don't reuse BPF target build artifacts due to incremental build issues with |  | ||||||
|   # `std: |  | ||||||
|   #    "found possibly newer version of crate `std` which `xyz` depends on |  | ||||||
|   rm -rf target/bpfel-unknown-unknown |  | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -9,10 +9,23 @@ | |||||||
|  |  | ||||||
| set -e | set -e | ||||||
| cd "$(dirname "$0")"/.. | cd "$(dirname "$0")"/.. | ||||||
| source ci/_ |  | ||||||
|  |  | ||||||
| _ ci/buildkite-pipeline.sh pipeline.yml | if [[ -n $BUILDKITE_TAG ]]; then | ||||||
| echo +++ pipeline |   buildkite-agent annotate --style info --context release-tag \ | ||||||
| cat pipeline.yml |     "https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG" | ||||||
|  |   buildkite-agent pipeline upload ci/buildkite-release.yml | ||||||
|  | else | ||||||
|  |   if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then | ||||||
|  |     # Add helpful link back to the corresponding Github Pull Request | ||||||
|  |     buildkite-agent annotate --style info --context pr-backlink \ | ||||||
|  |       "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH" | ||||||
|  |   fi | ||||||
|  |  | ||||||
| _ buildkite-agent pipeline upload pipeline.yml |   if [[ $BUILDKITE_MESSAGE =~ GitBook: ]]; then | ||||||
|  |     buildkite-agent annotate --style info --context gitbook-ci-skip \ | ||||||
|  |       "GitBook commit detected, CI skipped" | ||||||
|  |     exit | ||||||
|  |   fi | ||||||
|  |  | ||||||
|  |   buildkite-agent pipeline upload ci/buildkite.yml | ||||||
|  | fi | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.gitbook.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.gitbook.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | root: ./docs/src | ||||||
|  |  | ||||||
|  | structure: | ||||||
|  |     readme: introduction.md | ||||||
|  |     summary: SUMMARY.md | ||||||
|  |  | ||||||
|  | redirects: | ||||||
|  |     wallet: ./wallet-guide/README.md | ||||||
|  |     wallet/app-wallets: ./wallet-guide/apps.md | ||||||
|  |     wallet/app-wallets/trust-wallet: ./wallet-guide/trust-wallet.md | ||||||
|  |     wallet/app-wallets/ledger-live:  ./wallet-guide/ledger-live.md | ||||||
|  |     wallet/cli-wallets:  ./wallet-guide/cli.md | ||||||
|  |     wallet/cli-wallets/paper-wallet:  ./paper-wallet/README.md | ||||||
|  |     wallet/cli-wallets/paper-wallet/paper-wallet-usage: ./paper-wallet/paper-wallet-usage.md | ||||||
|  |     wallet/cli-wallets/remote-wallet: ./hardware-wallets/README.md | ||||||
|  |     wallet/cli-wallets/remote-wallet/ledger: ./hardware-wallets/ledger.md | ||||||
|  |     wallet/cli-wallets/file-system-wallet: ./file-system-wallet/README.md | ||||||
|  |     wallet/support: ./wallet-guide/support.md | ||||||
							
								
								
									
										41
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,41 +0,0 @@ | |||||||
| # To get started with Dependabot version updates, you'll need to specify which |  | ||||||
| # package ecosystems to update and where the package manifests are located. |  | ||||||
| # Please see the documentation for all configuration options: |  | ||||||
| # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates |  | ||||||
|  |  | ||||||
| version: 2 |  | ||||||
| updates: |  | ||||||
| - package-ecosystem: cargo |  | ||||||
|   directory: "/" |  | ||||||
|   schedule: |  | ||||||
|     interval: daily |  | ||||||
|     time: "01:00" |  | ||||||
|     timezone: America/Los_Angeles |  | ||||||
|   #labels: |  | ||||||
|   #  - "automerge" |  | ||||||
|   open-pull-requests-limit: 3 |  | ||||||
|    |  | ||||||
| - package-ecosystem: npm |  | ||||||
|   directory: "/web3.js" |  | ||||||
|   schedule: |  | ||||||
|     interval: daily |  | ||||||
|     time: "01:00" |  | ||||||
|     timezone: America/Los_Angeles |  | ||||||
|   labels: |  | ||||||
|     - "automerge" |  | ||||||
|   commit-message: |  | ||||||
|     prefix: "chore:" |  | ||||||
|   open-pull-requests-limit: 3 |  | ||||||
|    |  | ||||||
| - package-ecosystem: npm |  | ||||||
|   directory: "/explorer" |  | ||||||
|   schedule: |  | ||||||
|     interval: daily |  | ||||||
|     time: "01:00" |  | ||||||
|     timezone: America/Los_Angeles |  | ||||||
|   labels: |  | ||||||
|     - "automerge" |  | ||||||
|   commit-message: |  | ||||||
|     prefix: "chore:" |  | ||||||
|     include: "scope" |  | ||||||
|   open-pull-requests-limit: 3 |  | ||||||
							
								
								
									
										1
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,6 @@ daysUntilClose: 7 | |||||||
| # Issues with these labels will never be considered stale | # Issues with these labels will never be considered stale | ||||||
| exemptLabels: | exemptLabels: | ||||||
|   - security |   - security | ||||||
|   - blocked |  | ||||||
|  |  | ||||||
| # Label to use when marking a pull request as stale | # Label to use when marking a pull request as stale | ||||||
| staleLabel: stale | staleLabel: stale | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -23,9 +23,3 @@ log-*/ | |||||||
| /.idea/ | /.idea/ | ||||||
| /solana.iml | /solana.iml | ||||||
| /.vscode/ | /.vscode/ | ||||||
|  |  | ||||||
| # fetch-spl.sh artifacts |  | ||||||
| /spl-genesis-args.sh |  | ||||||
| /spl_*.so |  | ||||||
|  |  | ||||||
| .DS_Store |  | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								.mergify.yml
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								.mergify.yml
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ pull_request_rules: | |||||||
|   - name: automatic merge (squash) on CI success |   - name: automatic merge (squash) on CI success | ||||||
|     conditions: |     conditions: | ||||||
|       - status-success=buildkite/solana |       - status-success=buildkite/solana | ||||||
|       - status-success=Travis CI - Pull Request |       #- status-success=Travis CI - Pull Request | ||||||
|       - status-success=ci-gate |       - status-success=ci-gate | ||||||
|       - label=automerge |       - label=automerge | ||||||
|       - author≠@dont-squash-my-commits |       - author≠@dont-squash-my-commits | ||||||
| @@ -18,7 +18,7 @@ pull_request_rules: | |||||||
|   - name: automatic merge (rebase) on CI success |   - name: automatic merge (rebase) on CI success | ||||||
|     conditions: |     conditions: | ||||||
|       - status-success=buildkite/solana |       - status-success=buildkite/solana | ||||||
|       - status-success=Travis CI - Pull Request |       #- status-success=Travis CI - Pull Request | ||||||
|       - status-success=ci-gate |       - status-success=ci-gate | ||||||
|       - label=automerge |       - label=automerge | ||||||
|       - author=@dont-squash-my-commits |       - author=@dont-squash-my-commits | ||||||
| @@ -50,6 +50,14 @@ pull_request_rules: | |||||||
|       label: |       label: | ||||||
|         add: |         add: | ||||||
|           - automerge |           - automerge | ||||||
|  |   - name: v1.0 backport | ||||||
|  |     conditions: | ||||||
|  |       - label=v1.0 | ||||||
|  |     actions: | ||||||
|  |       backport: | ||||||
|  |         ignore_conflicts: true | ||||||
|  |         branches: | ||||||
|  |           - v1.0 | ||||||
|   - name: v1.1 backport |   - name: v1.1 backport | ||||||
|     conditions: |     conditions: | ||||||
|       - label=v1.1 |       - label=v1.1 | ||||||
| @@ -66,11 +74,3 @@ pull_request_rules: | |||||||
|         ignore_conflicts: true |         ignore_conflicts: true | ||||||
|         branches: |         branches: | ||||||
|           - v1.2 |           - v1.2 | ||||||
|   - name: v1.3 backport |  | ||||||
|     conditions: |  | ||||||
|       - label=v1.3 |  | ||||||
|     actions: |  | ||||||
|       backport: |  | ||||||
|         ignore_conflicts: true |  | ||||||
|         branches: |  | ||||||
|           - v1.3 |  | ||||||
|   | |||||||
							
								
								
									
										160
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,134 +1,46 @@ | |||||||
|  | os: | ||||||
|  |   - osx | ||||||
|  |   - windows | ||||||
|  |  | ||||||
|  | language: rust | ||||||
|  | rust: | ||||||
|  |   - stable | ||||||
|  |  | ||||||
|  | install: | ||||||
|  |   - source ci/rust-version.sh | ||||||
|  |  | ||||||
|  | script: | ||||||
|  |   - source ci/env.sh | ||||||
|  |   - ci/publish-tarball.sh | ||||||
|  |  | ||||||
|  |  | ||||||
| branches: | branches: | ||||||
|   only: |   only: | ||||||
|     - master |     - master | ||||||
|     - /^v\d+\.\d+/ |     - /^v\d+\.\d+/ | ||||||
|  |  | ||||||
|  | if: type IN (api, cron) OR tag IS present | ||||||
|  |  | ||||||
| notifications: | notifications: | ||||||
|   email: false |  | ||||||
|   slack: |   slack: | ||||||
|     on_success: change |     on_success: change | ||||||
|     if: NOT type = pull_request |  | ||||||
|     secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU= |     secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU= | ||||||
|  |  | ||||||
| os: linux | deploy: | ||||||
| dist: bionic |   - provider: s3 | ||||||
| language: minimal |     access_key_id: $AWS_ACCESS_KEY_ID | ||||||
|  |     secret_access_key: $AWS_SECRET_ACCESS_KEY | ||||||
| jobs: |     bucket: release.solana.com | ||||||
|   include: |     region: us-west-1 | ||||||
|     - name: "Export Github Repositories" |     skip_cleanup: true | ||||||
|       if: type IN (push, cron) AND branch = master |     acl: public_read | ||||||
|       language: python |     local_dir: travis-s3-upload | ||||||
|       git: |     on: | ||||||
|         depth: false |       all_branches: true | ||||||
|       script: |   - provider: releases | ||||||
|         - .travis/export-github-repo.sh web3.js/ solana-web3.js |     api_key: $GITHUB_TOKEN | ||||||
|         - .travis/export-github-repo.sh explorer/ explorer |     skip_cleanup: true | ||||||
|  |     file_glob: true | ||||||
|     - &release-artifacts |     file: travis-release-upload/* | ||||||
|       if: type IN (api, cron) OR tag IS present |     on: | ||||||
|       name: "macOS release artifacts" |       tags: true | ||||||
|       os: osx |  | ||||||
|       language: rust |  | ||||||
|       rust: |  | ||||||
|         - stable |  | ||||||
|       install: |  | ||||||
|         - source ci/rust-version.sh |  | ||||||
|       script: |  | ||||||
|         - source ci/env.sh |  | ||||||
|         - ci/publish-tarball.sh |  | ||||||
|       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 |  | ||||||
|           token: $GITHUB_TOKEN |  | ||||||
|           skip_cleanup: true |  | ||||||
|           file_glob: true |  | ||||||
|           file: travis-release-upload/* |  | ||||||
|           on: |  | ||||||
|             tags: true |  | ||||||
|     - <<: *release-artifacts |  | ||||||
|       name: "Windows release artifacts" |  | ||||||
|       os: windows |  | ||||||
|     #  Linux release artifacts are still built by ci/buildkite-secondary.yml |  | ||||||
|     #- <<: *release-artifacts |  | ||||||
|     #  name: "Linux release artifacts" |  | ||||||
|     #  os: linux |  | ||||||
|     #  before_install: |  | ||||||
|     #    - sudo apt-get install libssl-dev libudev-dev |  | ||||||
|  |  | ||||||
|     # explorer pull request |  | ||||||
|     - name: "explorer" |  | ||||||
|       if: type = pull_request AND branch = master |  | ||||||
|  |  | ||||||
|       language: node_js |  | ||||||
|       node_js: |  | ||||||
|         - "node" |  | ||||||
|  |  | ||||||
|       cache: |  | ||||||
|         directories: |  | ||||||
|           - ~/.npm |  | ||||||
|  |  | ||||||
|       before_install: |  | ||||||
|         - .travis/affects.sh explorer/ .travis || travis_terminate 0 |  | ||||||
|         - cd explorer |  | ||||||
|  |  | ||||||
|       script: |  | ||||||
|         - npm run build |  | ||||||
|         - npm run format |  | ||||||
|  |  | ||||||
|     # web3.js pull request |  | ||||||
|     - name: "web3.js" |  | ||||||
|       if: type = pull_request AND branch = master |  | ||||||
|  |  | ||||||
|       language: node_js |  | ||||||
|       node_js: |  | ||||||
|         - "lts/*" |  | ||||||
|  |  | ||||||
|       services: |  | ||||||
|         - docker |  | ||||||
|  |  | ||||||
|       cache: |  | ||||||
|         directories: |  | ||||||
|           - ~/.npm |  | ||||||
|  |  | ||||||
|       before_install: |  | ||||||
|         - .travis/affects.sh web3.js/ .travis || travis_terminate 0 |  | ||||||
|         - cd web3.js/ |  | ||||||
|         - source .travis/before_install.sh |  | ||||||
|  |  | ||||||
|       script: |  | ||||||
|         - ../.travis/commitlint.sh |  | ||||||
|         - source .travis/script.sh |  | ||||||
|  |  | ||||||
|     # docs pull request |  | ||||||
|     - name: "docs" |  | ||||||
|       if: type IN (push, pull_request) OR tag IS present |  | ||||||
|       language: node_js |  | ||||||
|       node_js: |  | ||||||
|         - "node" |  | ||||||
|  |  | ||||||
|       services: |  | ||||||
|         - docker |  | ||||||
|  |  | ||||||
|       cache: |  | ||||||
|         directories: |  | ||||||
|           - ~/.npm |  | ||||||
|  |  | ||||||
|       before_install: |  | ||||||
|         - source ci/env.sh |  | ||||||
|         - .travis/channel_restriction.sh edge beta || travis_terminate 0 |  | ||||||
|         - .travis/affects.sh docs/ .travis || travis_terminate 0 |  | ||||||
|         - cd docs/ |  | ||||||
|         - source .travis/before_install.sh |  | ||||||
|  |  | ||||||
|       script: |  | ||||||
|         - source .travis/script.sh |  | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| #!/usr/bin/env bash |  | ||||||
| # |  | ||||||
| # Check if files in the commit range match one or more prefixes |  | ||||||
| # |  | ||||||
|  |  | ||||||
| # Always run the job if we are on a tagged release |  | ||||||
| if [[ -n "$TRAVIS_TAG" ]]; then |  | ||||||
|   exit 0 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| ( |  | ||||||
|   set -x |  | ||||||
|   git diff --name-only "$TRAVIS_COMMIT_RANGE" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| for file in $(git diff --name-only "$TRAVIS_COMMIT_RANGE"); do |  | ||||||
|   for prefix in "$@"; do |  | ||||||
|     if [[ $file =~ ^"$prefix" ]]; then |  | ||||||
|       exit 0 |  | ||||||
|     fi |  | ||||||
|     done |  | ||||||
| done |  | ||||||
|  |  | ||||||
| echo "No modifications to $*" |  | ||||||
| exit 1 |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| #!/usr/bin/env bash |  | ||||||
| # |  | ||||||
| # Only proceed if we are on one of the channels passed in, or a tag build |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set -ex |  | ||||||
|  |  | ||||||
| [[ -n $CI_TAG ]] && exit 0 |  | ||||||
|  |  | ||||||
| eval "$(ci/channel-info.sh)" |  | ||||||
|  |  | ||||||
| for acceptable_channel in "$@"; do |  | ||||||
|   if [[ "$CHANNEL" == "$acceptable_channel" ]]; then |  | ||||||
|     exit 0 |  | ||||||
|   fi |  | ||||||
| done |  | ||||||
|  |  | ||||||
| echo "Not running from one of the following channels: $*" |  | ||||||
| exit 1 |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| #!/usr/bin/env bash |  | ||||||
| # |  | ||||||
| # Runs commitlint in the provided subdirectory |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set -e |  | ||||||
|  |  | ||||||
| basedir=$1 |  | ||||||
| if [[ -z "$basedir" ]]; then |  | ||||||
|   basedir=. |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if [[ ! -d "$basedir" ]]; then |  | ||||||
|   echo "Error: not a directory: $basedir" |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if [[ ! -f "$basedir"/commitlint.config.js ]]; then |  | ||||||
|   echo "Error: No commitlint configuration found" |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if [[ -z $TRAVIS_COMMIT_RANGE ]]; then |  | ||||||
|   echo "Error: TRAVIS_COMMIT_RANGE not defined" |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| cd "$basedir" |  | ||||||
| echo "Checking commits in TRAVIS_COMMIT_RANGE: $TRAVIS_COMMIT_RANGE" |  | ||||||
| while IFS= read -r line; do |  | ||||||
|   echo "$line" | npx commitlint |  | ||||||
| done < <(git log "$TRAVIS_COMMIT_RANGE" --format=%s -- .) |  | ||||||
| @@ -1,34 +0,0 @@ | |||||||
| #!/usr/bin/env bash |  | ||||||
| # |  | ||||||
| # Exports a subdirectory into another github repository |  | ||||||
| # |  | ||||||
|  |  | ||||||
| set -e |  | ||||||
| if [[ -z $GITHUB_TOKEN ]]; then |  | ||||||
|   echo GITHUB_TOKEN not defined |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| cd "$(dirname "$0")/.." |  | ||||||
|  |  | ||||||
| pip3 install git-filter-repo |  | ||||||
|  |  | ||||||
| declare subdir=$1 |  | ||||||
| declare repo_name=$2 |  | ||||||
|  |  | ||||||
| [[ -n "$subdir" ]] || { |  | ||||||
|   echo "Error: subdir not specified" |  | ||||||
|   exit 1 |  | ||||||
| } |  | ||||||
| [[ -n "$repo_name" ]] || { |  | ||||||
|   echo "Error: repo_name not specified" |  | ||||||
|   exit 1 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| echo "Exporting $subdir" |  | ||||||
|  |  | ||||||
| set -x |  | ||||||
| rm -rf .github_export/"$repo_name" |  | ||||||
| git clone https://"$GITHUB_TOKEN"@github.com/solana-labs/"$repo_name" .github_export/"$repo_name" |  | ||||||
| git filter-repo --subdirectory-filter "$subdir" --target .github_export/"$repo_name" |  | ||||||
| git -C .github_export/"$repo_name" push https://"$GITHUB_TOKEN"@github.com/solana-labs/"$repo_name" |  | ||||||
							
								
								
									
										3556
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3556
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -5,12 +5,7 @@ members = [ | |||||||
|     "bench-tps", |     "bench-tps", | ||||||
|     "accounts-bench", |     "accounts-bench", | ||||||
|     "banking-bench", |     "banking-bench", | ||||||
|     "banks-client", |  | ||||||
|     "banks-interface", |  | ||||||
|     "banks-server", |  | ||||||
|     "clap-utils", |  | ||||||
|     "cli-config", |     "cli-config", | ||||||
|     "cli-output", |  | ||||||
|     "client", |     "client", | ||||||
|     "core", |     "core", | ||||||
|     "dos", |     "dos", | ||||||
| @@ -19,6 +14,7 @@ members = [ | |||||||
|     "perf", |     "perf", | ||||||
|     "validator", |     "validator", | ||||||
|     "genesis", |     "genesis", | ||||||
|  |     "genesis-programs", | ||||||
|     "gossip", |     "gossip", | ||||||
|     "install", |     "install", | ||||||
|     "keygen", |     "keygen", | ||||||
| @@ -29,16 +25,15 @@ members = [ | |||||||
|     "log-analyzer", |     "log-analyzer", | ||||||
|     "merkle-tree", |     "merkle-tree", | ||||||
|     "stake-o-matic", |     "stake-o-matic", | ||||||
|     "storage-bigtable", |  | ||||||
|     "streamer", |     "streamer", | ||||||
|     "measure", |     "measure", | ||||||
|     "metrics", |     "metrics", | ||||||
|     "net-shaper", |     "net-shaper", | ||||||
|     "notifier", |     "notifier", | ||||||
|     "poh-bench", |  | ||||||
|     "programs/secp256k1", |  | ||||||
|     "programs/bpf_loader", |     "programs/bpf_loader", | ||||||
|     "programs/budget", |     "programs/budget", | ||||||
|  |     "programs/btc_spv", | ||||||
|  |     "programs/btc_spv_bin", | ||||||
|     "programs/config", |     "programs/config", | ||||||
|     "programs/exchange", |     "programs/exchange", | ||||||
|     "programs/failure", |     "programs/failure", | ||||||
| @@ -57,7 +52,6 @@ members = [ | |||||||
|     "sys-tuner", |     "sys-tuner", | ||||||
|     "tokens", |     "tokens", | ||||||
|     "transaction-status", |     "transaction-status", | ||||||
|     "account-decoder", |  | ||||||
|     "upload-perf", |     "upload-perf", | ||||||
|     "net-utils", |     "net-utils", | ||||||
|     "version", |     "version", | ||||||
| @@ -69,4 +63,6 @@ members = [ | |||||||
|  |  | ||||||
| exclude = [ | exclude = [ | ||||||
|     "programs/bpf", |     "programs/bpf", | ||||||
|  |     "programs/move_loader", | ||||||
|  |     "programs/librapay", | ||||||
| ] | ] | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| Copyright 2020 Solana Foundation. | Copyright 2018 Solana Labs, Inc. | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| you may not use this file except in compliance with the License. | you may not use this file except in compliance with the License. | ||||||
|   | |||||||
| @@ -61,9 +61,8 @@ $ cargo test | |||||||
| ### Starting a local testnet | ### Starting a local testnet | ||||||
| Start your own testnet locally, instructions are in the [online docs](https://docs.solana.com/bench-tps). | Start your own testnet locally, instructions are in the [online docs](https://docs.solana.com/bench-tps). | ||||||
|  |  | ||||||
| ### Accessing the remote development cluster | ### Accessing the remote testnet | ||||||
| * `devnet` - stable public cluster for development accessible via | * `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7 | ||||||
| devnet.solana.com. Runs 24/7. Learn more about the [public clusters](https://docs.solana.com/clusters) |  | ||||||
|  |  | ||||||
| # Benchmarking | # Benchmarking | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								RELEASE.md
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								RELEASE.md
									
									
									
									
									
								
							| @@ -76,20 +76,21 @@ There are three release channels that map to branches as follows: | |||||||
|     git push -u origin <branchname> |     git push -u origin <branchname> | ||||||
|     ``` |     ``` | ||||||
|  |  | ||||||
| Alternatively use the Github UI. | ### Update master branch with the next version | ||||||
|  |  | ||||||
| ### Update master branch to the next release minor version |  | ||||||
|  |  | ||||||
| 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: | 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 |      scripts/increment-cargo-version.sh minor | ||||||
|      $ ./scripts/cargo-for-all-lock-files.sh update |  | ||||||
|      ``` |      ``` | ||||||
|  | 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: | 1. Push all the changed Cargo.toml and Cargo.lock files to the `master` branch with something like: | ||||||
|     ``` |     ``` | ||||||
|     git co -b version_update |     git co -b version_update | ||||||
|     git ls-files -m | xargs git add |     git ls-files -m | xargs git add | ||||||
|     git commit -m 'Bump version to X.Y+1.0' |     git commit -m 'Update Cargo.toml versions from X.Y to X.Y+1' | ||||||
|     git push -u origin version_update |     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`: | 1. Confirm that your freshly cut release branch is shown as `BETA_CHANNEL` and the previous release branch as `STABLE_CHANNEL`: | ||||||
| @@ -101,18 +102,15 @@ Alternatively use the Github UI. | |||||||
|  |  | ||||||
| ### Create the Release Tag on GitHub | ### Create the Release Tag on GitHub | ||||||
|  |  | ||||||
| 1. Go to [GitHub Releases](https://github.com/solana-labs/solana/releases) for tagging a release. | 1. Go to [GitHub's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release. | ||||||
| 1. Click "Draft new release".  The release tag must exactly match the `version` | 1. Click "Draft new release".  The release tag must exactly match the `version` | ||||||
|    field in `/Cargo.toml` prefixed by `v`. |    field in `/Cargo.toml` prefixed by `v`. | ||||||
|    1.  If the Cargo.toml version field is **0.12.3**, then the release tag must be **v0.12.3** |    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. 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 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 | 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.  If this is a patch release, review all the commits since the previous release on this branch and add details as needed. |    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. | 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. | ||||||
| 1. Ensure the release is marked **"This is a pre-release"**.  This flag will then need to be be removed once the the Linux binary artifacts appear later. |  | ||||||
| 1. Go back into edit the release and click "Publish release" when ready. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Update release branch with the next patch version | ### Update release branch with the next patch version | ||||||
|  |  | ||||||
| @@ -121,31 +119,68 @@ Alternatively use the Github UI. | |||||||
|      $ scripts/increment-cargo-version.sh patch |      $ scripts/increment-cargo-version.sh patch | ||||||
|      $ ./scripts/cargo-for-all-lock-files.sh tree |      $ ./scripts/cargo-for-all-lock-files.sh tree | ||||||
|      ``` |      ``` | ||||||
|  | 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: | 1. Push all the changed Cargo.toml and Cargo.lock files to the **release branch** with something like: | ||||||
|     ``` |     ``` | ||||||
|     git co -b version_update origin/vX.Y |     git co -b version_update | ||||||
|     git add -u |     git ls-files -m | xargs git add | ||||||
|     git commit -m 'Bump version to X.Y.Z+1' |     git commit -m 'Update Cargo.toml versions from X.Y.Z to X.Y.Z+1' | ||||||
|     git push -u <user-remote> version_update |     git push -u origin version_update | ||||||
|     ``` |     ``` | ||||||
| 1. Open a PR against origin/vX.Y and then merge the PR after passing CI. |  | ||||||
|  |  | ||||||
| ### Prepare for the next release |  | ||||||
| 1.  Go to [GitHub Releases](https://github.com/solana-labs/solana/releases) and create a new draft release for `X.Y.Z+1` with empty release nodes.  This allows people to incrementally add new release notes until it's time for the next release |  | ||||||
| 1.  Go to the [Github Milestones](https://github.com/solana-labs/solana/milestones).  Create a new milestone for the `X.Y.Z+1`, move over |  | ||||||
| unresolved issues still in the `X.Y.Z` milestone, then close the `X.Y.Z` milestone. |  | ||||||
|  |  | ||||||
| ### Verify release automation success | ### Verify release automation success | ||||||
| Go to [Solana Releases](https://github.com/solana-labs/solana/releases) and click on the latest release that you just published. | 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. | ||||||
| Verify that all of the build artifacts are present, then the uncheck **"This is a pre-release"** for the release. | 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. | ||||||
|  |  | ||||||
| Build artifacts can take up to 60 minutes after creating the tag before | ### Update documentation | ||||||
| appearing.  To check for progress: | TODO: Documentation update procedure is WIP as we move to gitbook | ||||||
| * The `solana-secondary` Buildkite pipeline handles creating the Linux release artifacts and updated crates.  Look for a job under the tag name of the release: https://buildkite.com/solana-labs/solana-secondary. |  | ||||||
| * The macOS and Windows release artifacts are produced by Travis CI: https://travis-ci.com/github/solana-labs/solana/branches |  | ||||||
|  |  | ||||||
| [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.  This can take 2-3 hours, and sometimes fails in the `solana-secondary` job. | Document the new recommended version by updating `docs/src/running-archiver.md` and `docs/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version. | ||||||
| If this happens and the error is non-fatal, click "Retry" on the "publish crate" job |  | ||||||
|  |  | ||||||
| ### Update software on devnet.solana.com/testnet.solama.com/mainnet-beta.solana.com | ### Update software on devnet.solana.com | ||||||
| See the documentation at https://github.com/solana-labs/cluster-ops/ |  | ||||||
|  | The testnet running on devnet.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 | ||||||
|  | devnet.solana.com is available | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "solana-account-decoder" |  | ||||||
| version = "1.3.21" |  | ||||||
| description = "Solana account decoder" |  | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] |  | ||||||
| repository = "https://github.com/solana-labs/solana" |  | ||||||
| homepage = "https://solana.com/" |  | ||||||
| license = "Apache-2.0" |  | ||||||
| edition = "2018" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| base64 = "0.12.3" |  | ||||||
| bincode = "1.3.1" |  | ||||||
| bs58 = "0.3.1" |  | ||||||
| bv = "0.11.1" |  | ||||||
| Inflector = "0.11.4" |  | ||||||
| lazy_static = "1.4.0" |  | ||||||
| serde = "1.0.112" |  | ||||||
| serde_derive = "1.0.103" |  | ||||||
| serde_json = "1.0.56" |  | ||||||
| solana-config-program = { path = "../programs/config", version = "1.3.21" } |  | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } |  | ||||||
| solana-stake-program = { path = "../programs/stake", version = "1.3.21" } |  | ||||||
| solana-vote-program = { path = "../programs/vote", version = "1.3.21" } |  | ||||||
| spl-token-v2-0 = { package = "spl-token", version = "=2.0.6", features = ["skip-no-mangle"] } |  | ||||||
| thiserror = "1.0" |  | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] |  | ||||||
| targets = ["x86_64-unknown-linux-gnu"] |  | ||||||
| @@ -1,182 +0,0 @@ | |||||||
| #[macro_use] |  | ||||||
| extern crate lazy_static; |  | ||||||
| #[macro_use] |  | ||||||
| extern crate serde_derive; |  | ||||||
|  |  | ||||||
| pub mod parse_account_data; |  | ||||||
| pub mod parse_config; |  | ||||||
| pub mod parse_nonce; |  | ||||||
| pub mod parse_stake; |  | ||||||
| pub mod parse_sysvar; |  | ||||||
| pub mod parse_token; |  | ||||||
| pub mod parse_vote; |  | ||||||
| pub mod validator_info; |  | ||||||
|  |  | ||||||
| use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount}; |  | ||||||
| use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey}; |  | ||||||
| use std::str::FromStr; |  | ||||||
|  |  | ||||||
| pub type StringAmount = String; |  | ||||||
|  |  | ||||||
| /// A duplicate representation of an Account for pretty JSON serialization |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiAccount { |  | ||||||
|     pub lamports: u64, |  | ||||||
|     pub data: UiAccountData, |  | ||||||
|     pub owner: String, |  | ||||||
|     pub executable: bool, |  | ||||||
|     pub rent_epoch: Epoch, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] |  | ||||||
| #[serde(rename_all = "camelCase", untagged)] |  | ||||||
| pub enum UiAccountData { |  | ||||||
|     LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility |  | ||||||
|     Json(ParsedAccount), |  | ||||||
|     Binary(String, UiAccountEncoding), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub enum UiAccountEncoding { |  | ||||||
|     Binary, // Legacy. Retained for RPC backwards compatibility |  | ||||||
|     Base58, |  | ||||||
|     Base64, |  | ||||||
|     JsonParsed, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl UiAccount { |  | ||||||
|     pub fn encode( |  | ||||||
|         pubkey: &Pubkey, |  | ||||||
|         account: Account, |  | ||||||
|         encoding: UiAccountEncoding, |  | ||||||
|         additional_data: Option<AccountAdditionalData>, |  | ||||||
|         data_slice_config: Option<UiDataSliceConfig>, |  | ||||||
|     ) -> Self { |  | ||||||
|         let data = match encoding { |  | ||||||
|             UiAccountEncoding::Binary => UiAccountData::LegacyBinary( |  | ||||||
|                 bs58::encode(slice_data(&account.data, data_slice_config)).into_string(), |  | ||||||
|             ), |  | ||||||
|             UiAccountEncoding::Base58 => UiAccountData::Binary( |  | ||||||
|                 bs58::encode(slice_data(&account.data, data_slice_config)).into_string(), |  | ||||||
|                 encoding, |  | ||||||
|             ), |  | ||||||
|             UiAccountEncoding::Base64 => UiAccountData::Binary( |  | ||||||
|                 base64::encode(slice_data(&account.data, data_slice_config)), |  | ||||||
|                 encoding, |  | ||||||
|             ), |  | ||||||
|             UiAccountEncoding::JsonParsed => { |  | ||||||
|                 if let Ok(parsed_data) = |  | ||||||
|                     parse_account_data(pubkey, &account.owner, &account.data, additional_data) |  | ||||||
|                 { |  | ||||||
|                     UiAccountData::Json(parsed_data) |  | ||||||
|                 } else { |  | ||||||
|                     UiAccountData::Binary(base64::encode(&account.data), UiAccountEncoding::Base64) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         UiAccount { |  | ||||||
|             lamports: account.lamports, |  | ||||||
|             data, |  | ||||||
|             owner: account.owner.to_string(), |  | ||||||
|             executable: account.executable, |  | ||||||
|             rent_epoch: account.rent_epoch, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn decode(&self) -> Option<Account> { |  | ||||||
|         let data = match &self.data { |  | ||||||
|             UiAccountData::Json(_) => None, |  | ||||||
|             UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(), |  | ||||||
|             UiAccountData::Binary(blob, encoding) => match encoding { |  | ||||||
|                 UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(), |  | ||||||
|                 UiAccountEncoding::Base64 => base64::decode(blob).ok(), |  | ||||||
|                 UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None, |  | ||||||
|             }, |  | ||||||
|         }?; |  | ||||||
|         Some(Account { |  | ||||||
|             lamports: self.lamports, |  | ||||||
|             data, |  | ||||||
|             owner: Pubkey::from_str(&self.owner).ok()?, |  | ||||||
|             executable: self.executable, |  | ||||||
|             rent_epoch: self.rent_epoch, |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiFeeCalculator { |  | ||||||
|     pub lamports_per_signature: StringAmount, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<FeeCalculator> for UiFeeCalculator { |  | ||||||
|     fn from(fee_calculator: FeeCalculator) -> Self { |  | ||||||
|         Self { |  | ||||||
|             lamports_per_signature: fee_calculator.lamports_per_signature.to_string(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for UiFeeCalculator { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self { |  | ||||||
|             lamports_per_signature: "0".to_string(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiDataSliceConfig { |  | ||||||
|     pub offset: usize, |  | ||||||
|     pub length: usize, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] { |  | ||||||
|     if let Some(UiDataSliceConfig { offset, length }) = data_slice_config { |  | ||||||
|         if offset >= data.len() { |  | ||||||
|             &[] |  | ||||||
|         } else if length > data.len() - offset { |  | ||||||
|             &data[offset..] |  | ||||||
|         } else { |  | ||||||
|             &data[offset..offset + length] |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         data |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_slice_data() { |  | ||||||
|         let data = vec![1, 2, 3, 4, 5]; |  | ||||||
|         let slice_config = Some(UiDataSliceConfig { |  | ||||||
|             offset: 0, |  | ||||||
|             length: 5, |  | ||||||
|         }); |  | ||||||
|         assert_eq!(slice_data(&data, slice_config), &data[..]); |  | ||||||
|  |  | ||||||
|         let slice_config = Some(UiDataSliceConfig { |  | ||||||
|             offset: 0, |  | ||||||
|             length: 10, |  | ||||||
|         }); |  | ||||||
|         assert_eq!(slice_data(&data, slice_config), &data[..]); |  | ||||||
|  |  | ||||||
|         let slice_config = Some(UiDataSliceConfig { |  | ||||||
|             offset: 1, |  | ||||||
|             length: 2, |  | ||||||
|         }); |  | ||||||
|         assert_eq!(slice_data(&data, slice_config), &data[1..3]); |  | ||||||
|  |  | ||||||
|         let slice_config = Some(UiDataSliceConfig { |  | ||||||
|             offset: 10, |  | ||||||
|             length: 2, |  | ||||||
|         }); |  | ||||||
|         assert_eq!(slice_data(&data, slice_config), &[] as &[u8]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,145 +0,0 @@ | |||||||
| use crate::{ |  | ||||||
|     parse_config::parse_config, |  | ||||||
|     parse_nonce::parse_nonce, |  | ||||||
|     parse_stake::parse_stake, |  | ||||||
|     parse_sysvar::parse_sysvar, |  | ||||||
|     parse_token::{parse_token, spl_token_id_v2_0}, |  | ||||||
|     parse_vote::parse_vote, |  | ||||||
| }; |  | ||||||
| use inflector::Inflector; |  | ||||||
| use serde_json::Value; |  | ||||||
| use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use thiserror::Error; |  | ||||||
|  |  | ||||||
| lazy_static! { |  | ||||||
|     static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id(); |  | ||||||
|     static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id(); |  | ||||||
|     static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); |  | ||||||
|     static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id(); |  | ||||||
|     static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); |  | ||||||
|     static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); |  | ||||||
|     pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = { |  | ||||||
|         let mut m = HashMap::new(); |  | ||||||
|         m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config); |  | ||||||
|         m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce); |  | ||||||
|         m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken); |  | ||||||
|         m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake); |  | ||||||
|         m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar); |  | ||||||
|         m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote); |  | ||||||
|         m |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Error, Debug)] |  | ||||||
| pub enum ParseAccountError { |  | ||||||
|     #[error("{0:?} account not parsable")] |  | ||||||
|     AccountNotParsable(ParsableAccount), |  | ||||||
|  |  | ||||||
|     #[error("Program not parsable")] |  | ||||||
|     ProgramNotParsable, |  | ||||||
|  |  | ||||||
|     #[error("Additional data required to parse: {0}")] |  | ||||||
|     AdditionalDataMissing(String), |  | ||||||
|  |  | ||||||
|     #[error("Instruction error")] |  | ||||||
|     InstructionError(#[from] InstructionError), |  | ||||||
|  |  | ||||||
|     #[error("Serde json error")] |  | ||||||
|     SerdeJsonError(#[from] serde_json::error::Error), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct ParsedAccount { |  | ||||||
|     pub program: String, |  | ||||||
|     pub parsed: Value, |  | ||||||
|     pub space: u64, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub enum ParsableAccount { |  | ||||||
|     Config, |  | ||||||
|     Nonce, |  | ||||||
|     SplToken, |  | ||||||
|     Stake, |  | ||||||
|     Sysvar, |  | ||||||
|     Vote, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Default)] |  | ||||||
| pub struct AccountAdditionalData { |  | ||||||
|     pub spl_token_decimals: Option<u8>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn parse_account_data( |  | ||||||
|     pubkey: &Pubkey, |  | ||||||
|     program_id: &Pubkey, |  | ||||||
|     data: &[u8], |  | ||||||
|     additional_data: Option<AccountAdditionalData>, |  | ||||||
| ) -> Result<ParsedAccount, ParseAccountError> { |  | ||||||
|     let program_name = PARSABLE_PROGRAM_IDS |  | ||||||
|         .get(program_id) |  | ||||||
|         .ok_or_else(|| ParseAccountError::ProgramNotParsable)?; |  | ||||||
|     let additional_data = additional_data.unwrap_or_default(); |  | ||||||
|     let parsed_json = match program_name { |  | ||||||
|         ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?, |  | ||||||
|         ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?, |  | ||||||
|         ParsableAccount::SplToken => { |  | ||||||
|             serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)? |  | ||||||
|         } |  | ||||||
|         ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?, |  | ||||||
|         ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?, |  | ||||||
|         ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?, |  | ||||||
|     }; |  | ||||||
|     Ok(ParsedAccount { |  | ||||||
|         program: format!("{:?}", program_name).to_kebab_case(), |  | ||||||
|         parsed: parsed_json, |  | ||||||
|         space: data.len() as u64, |  | ||||||
|     }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::nonce::{ |  | ||||||
|         state::{Data, Versions}, |  | ||||||
|         State, |  | ||||||
|     }; |  | ||||||
|     use solana_vote_program::vote_state::{VoteState, VoteStateVersions}; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_account_data() { |  | ||||||
|         let account_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let other_program = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let data = vec![0; 4]; |  | ||||||
|         assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err()); |  | ||||||
|  |  | ||||||
|         let vote_state = VoteState::default(); |  | ||||||
|         let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()]; |  | ||||||
|         let versioned = VoteStateVersions::Current(Box::new(vote_state)); |  | ||||||
|         VoteState::serialize(&versioned, &mut vote_account_data).unwrap(); |  | ||||||
|         let parsed = parse_account_data( |  | ||||||
|             &account_pubkey, |  | ||||||
|             &solana_vote_program::id(), |  | ||||||
|             &vote_account_data, |  | ||||||
|             None, |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
|         assert_eq!(parsed.program, "vote".to_string()); |  | ||||||
|         assert_eq!(parsed.space, VoteState::size_of() as u64); |  | ||||||
|  |  | ||||||
|         let nonce_data = Versions::new_current(State::Initialized(Data::default())); |  | ||||||
|         let nonce_account_data = bincode::serialize(&nonce_data).unwrap(); |  | ||||||
|         let parsed = parse_account_data( |  | ||||||
|             &account_pubkey, |  | ||||||
|             &system_program::id(), |  | ||||||
|             &nonce_account_data, |  | ||||||
|             None, |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
|         assert_eq!(parsed.program, "nonce".to_string()); |  | ||||||
|         assert_eq!(parsed.space, State::size() as u64); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,146 +0,0 @@ | |||||||
| use crate::{ |  | ||||||
|     parse_account_data::{ParsableAccount, ParseAccountError}, |  | ||||||
|     validator_info, |  | ||||||
| }; |  | ||||||
| use bincode::deserialize; |  | ||||||
| use serde_json::Value; |  | ||||||
| use solana_config_program::{get_config_data, ConfigKeys}; |  | ||||||
| use solana_sdk::pubkey::Pubkey; |  | ||||||
| use solana_stake_program::config::Config as StakeConfig; |  | ||||||
|  |  | ||||||
| pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> { |  | ||||||
|     let parsed_account = if pubkey == &solana_stake_program::config::id() { |  | ||||||
|         get_config_data(data) |  | ||||||
|             .ok() |  | ||||||
|             .and_then(|data| deserialize::<StakeConfig>(data).ok()) |  | ||||||
|             .map(|config| ConfigAccountType::StakeConfig(config.into())) |  | ||||||
|     } else { |  | ||||||
|         deserialize::<ConfigKeys>(data).ok().and_then(|key_list| { |  | ||||||
|             if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() { |  | ||||||
|                 parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| { |  | ||||||
|                     Some(ConfigAccountType::ValidatorInfo(UiConfig { |  | ||||||
|                         keys: validator_info.keys, |  | ||||||
|                         config_data: serde_json::from_str(&validator_info.config_data).ok()?, |  | ||||||
|                     })) |  | ||||||
|                 }) |  | ||||||
|             } else { |  | ||||||
|                 None |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     }; |  | ||||||
|     parsed_account.ok_or(ParseAccountError::AccountNotParsable( |  | ||||||
|         ParsableAccount::Config, |  | ||||||
|     )) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>> |  | ||||||
| where |  | ||||||
|     T: serde::de::DeserializeOwned, |  | ||||||
| { |  | ||||||
|     let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?; |  | ||||||
|     let keys = keys |  | ||||||
|         .iter() |  | ||||||
|         .map(|key| UiConfigKey { |  | ||||||
|             pubkey: key.0.to_string(), |  | ||||||
|             signer: key.1, |  | ||||||
|         }) |  | ||||||
|         .collect(); |  | ||||||
|     Some(UiConfig { keys, config_data }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum ConfigAccountType { |  | ||||||
|     StakeConfig(UiStakeConfig), |  | ||||||
|     ValidatorInfo(UiConfig<Value>), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiConfigKey { |  | ||||||
|     pub pubkey: String, |  | ||||||
|     pub signer: bool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiStakeConfig { |  | ||||||
|     pub warmup_cooldown_rate: f64, |  | ||||||
|     pub slash_penalty: u8, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<StakeConfig> for UiStakeConfig { |  | ||||||
|     fn from(config: StakeConfig) -> Self { |  | ||||||
|         Self { |  | ||||||
|             warmup_cooldown_rate: config.warmup_cooldown_rate, |  | ||||||
|             slash_penalty: config.slash_penalty, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiConfig<T> { |  | ||||||
|     pub keys: Vec<UiConfigKey>, |  | ||||||
|     pub config_data: T, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use crate::validator_info::ValidatorInfo; |  | ||||||
|     use serde_json::json; |  | ||||||
|     use solana_config_program::create_config_account; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_config() { |  | ||||||
|         let stake_config = StakeConfig { |  | ||||||
|             warmup_cooldown_rate: 0.25, |  | ||||||
|             slash_penalty: 50, |  | ||||||
|         }; |  | ||||||
|         let stake_config_account = create_config_account(vec![], &stake_config, 10); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_config( |  | ||||||
|                 &stake_config_account.data, |  | ||||||
|                 &solana_stake_program::config::id() |  | ||||||
|             ) |  | ||||||
|             .unwrap(), |  | ||||||
|             ConfigAccountType::StakeConfig(UiStakeConfig { |  | ||||||
|                 warmup_cooldown_rate: 0.25, |  | ||||||
|                 slash_penalty: 50, |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let validator_info = ValidatorInfo { |  | ||||||
|             info: serde_json::to_string(&json!({ |  | ||||||
|                 "name": "Solana", |  | ||||||
|             })) |  | ||||||
|             .unwrap(), |  | ||||||
|         }; |  | ||||||
|         let info_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let validator_info_config_account = create_config_account( |  | ||||||
|             vec![(validator_info::id(), false), (info_pubkey, true)], |  | ||||||
|             &validator_info, |  | ||||||
|             10, |  | ||||||
|         ); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(), |  | ||||||
|             ConfigAccountType::ValidatorInfo(UiConfig { |  | ||||||
|                 keys: vec![ |  | ||||||
|                     UiConfigKey { |  | ||||||
|                         pubkey: validator_info::id().to_string(), |  | ||||||
|                         signer: false, |  | ||||||
|                     }, |  | ||||||
|                     UiConfigKey { |  | ||||||
|                         pubkey: info_pubkey.to_string(), |  | ||||||
|                         signer: true, |  | ||||||
|                     } |  | ||||||
|                 ], |  | ||||||
|                 config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(), |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![0; 4]; |  | ||||||
|         assert!(parse_config(&bad_data, &info_pubkey).is_err()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,67 +0,0 @@ | |||||||
| use crate::{parse_account_data::ParseAccountError, UiFeeCalculator}; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     instruction::InstructionError, |  | ||||||
|     nonce::{state::Versions, State}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> { |  | ||||||
|     let nonce_state: Versions = bincode::deserialize(data) |  | ||||||
|         .map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?; |  | ||||||
|     let nonce_state = nonce_state.convert_to_current(); |  | ||||||
|     match nonce_state { |  | ||||||
|         State::Uninitialized => Ok(UiNonceState::Uninitialized), |  | ||||||
|         State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData { |  | ||||||
|             authority: data.authority.to_string(), |  | ||||||
|             blockhash: data.blockhash.to_string(), |  | ||||||
|             fee_calculator: data.fee_calculator.into(), |  | ||||||
|         })), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// A duplicate representation of NonceState for pretty JSON serialization |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum UiNonceState { |  | ||||||
|     Uninitialized, |  | ||||||
|     Initialized(UiNonceData), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiNonceData { |  | ||||||
|     pub authority: String, |  | ||||||
|     pub blockhash: String, |  | ||||||
|     pub fee_calculator: UiFeeCalculator, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::{ |  | ||||||
|         hash::Hash, |  | ||||||
|         nonce::{ |  | ||||||
|             state::{Data, Versions}, |  | ||||||
|             State, |  | ||||||
|         }, |  | ||||||
|         pubkey::Pubkey, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_nonce() { |  | ||||||
|         let nonce_data = Versions::new_current(State::Initialized(Data::default())); |  | ||||||
|         let nonce_account_data = bincode::serialize(&nonce_data).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_nonce(&nonce_account_data).unwrap(), |  | ||||||
|             UiNonceState::Initialized(UiNonceData { |  | ||||||
|                 authority: Pubkey::default().to_string(), |  | ||||||
|                 blockhash: Hash::default().to_string(), |  | ||||||
|                 fee_calculator: UiFeeCalculator { |  | ||||||
|                     lamports_per_signature: 0.to_string(), |  | ||||||
|                 }, |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![0; 4]; |  | ||||||
|         assert!(parse_nonce(&bad_data).is_err()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,234 +0,0 @@ | |||||||
| use crate::{ |  | ||||||
|     parse_account_data::{ParsableAccount, ParseAccountError}, |  | ||||||
|     StringAmount, |  | ||||||
| }; |  | ||||||
| use bincode::deserialize; |  | ||||||
| use solana_sdk::clock::{Epoch, UnixTimestamp}; |  | ||||||
| use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState}; |  | ||||||
|  |  | ||||||
| pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> { |  | ||||||
|     let stake_state: StakeState = deserialize(data) |  | ||||||
|         .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?; |  | ||||||
|     let parsed_account = match stake_state { |  | ||||||
|         StakeState::Uninitialized => StakeAccountType::Uninitialized, |  | ||||||
|         StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount { |  | ||||||
|             meta: meta.into(), |  | ||||||
|             stake: None, |  | ||||||
|         }), |  | ||||||
|         StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount { |  | ||||||
|             meta: meta.into(), |  | ||||||
|             stake: Some(stake.into()), |  | ||||||
|         }), |  | ||||||
|         StakeState::RewardsPool => StakeAccountType::RewardsPool, |  | ||||||
|     }; |  | ||||||
|     Ok(parsed_account) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum StakeAccountType { |  | ||||||
|     Uninitialized, |  | ||||||
|     Initialized(UiStakeAccount), |  | ||||||
|     Delegated(UiStakeAccount), |  | ||||||
|     RewardsPool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiStakeAccount { |  | ||||||
|     pub meta: UiMeta, |  | ||||||
|     pub stake: Option<UiStake>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiMeta { |  | ||||||
|     pub rent_exempt_reserve: StringAmount, |  | ||||||
|     pub authorized: UiAuthorized, |  | ||||||
|     pub lockup: UiLockup, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Meta> for UiMeta { |  | ||||||
|     fn from(meta: Meta) -> Self { |  | ||||||
|         Self { |  | ||||||
|             rent_exempt_reserve: meta.rent_exempt_reserve.to_string(), |  | ||||||
|             authorized: meta.authorized.into(), |  | ||||||
|             lockup: meta.lockup.into(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiLockup { |  | ||||||
|     pub unix_timestamp: UnixTimestamp, |  | ||||||
|     pub epoch: Epoch, |  | ||||||
|     pub custodian: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Lockup> for UiLockup { |  | ||||||
|     fn from(lockup: Lockup) -> Self { |  | ||||||
|         Self { |  | ||||||
|             unix_timestamp: lockup.unix_timestamp, |  | ||||||
|             epoch: lockup.epoch, |  | ||||||
|             custodian: lockup.custodian.to_string(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiAuthorized { |  | ||||||
|     pub staker: String, |  | ||||||
|     pub withdrawer: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Authorized> for UiAuthorized { |  | ||||||
|     fn from(authorized: Authorized) -> Self { |  | ||||||
|         Self { |  | ||||||
|             staker: authorized.staker.to_string(), |  | ||||||
|             withdrawer: authorized.withdrawer.to_string(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiStake { |  | ||||||
|     pub delegation: UiDelegation, |  | ||||||
|     pub credits_observed: u64, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Stake> for UiStake { |  | ||||||
|     fn from(stake: Stake) -> Self { |  | ||||||
|         Self { |  | ||||||
|             delegation: stake.delegation.into(), |  | ||||||
|             credits_observed: stake.credits_observed, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiDelegation { |  | ||||||
|     pub voter: String, |  | ||||||
|     pub stake: StringAmount, |  | ||||||
|     pub activation_epoch: StringAmount, |  | ||||||
|     pub deactivation_epoch: StringAmount, |  | ||||||
|     pub warmup_cooldown_rate: f64, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Delegation> for UiDelegation { |  | ||||||
|     fn from(delegation: Delegation) -> Self { |  | ||||||
|         Self { |  | ||||||
|             voter: delegation.voter_pubkey.to_string(), |  | ||||||
|             stake: delegation.stake.to_string(), |  | ||||||
|             activation_epoch: delegation.activation_epoch.to_string(), |  | ||||||
|             deactivation_epoch: delegation.deactivation_epoch.to_string(), |  | ||||||
|             warmup_cooldown_rate: delegation.warmup_cooldown_rate, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use bincode::serialize; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_stake() { |  | ||||||
|         let stake_state = StakeState::Uninitialized; |  | ||||||
|         let stake_data = serialize(&stake_state).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_stake(&stake_data).unwrap(), |  | ||||||
|             StakeAccountType::Uninitialized |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let custodian = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let authorized = Authorized::auto(&pubkey); |  | ||||||
|         let lockup = Lockup { |  | ||||||
|             unix_timestamp: 0, |  | ||||||
|             epoch: 1, |  | ||||||
|             custodian, |  | ||||||
|         }; |  | ||||||
|         let meta = Meta { |  | ||||||
|             rent_exempt_reserve: 42, |  | ||||||
|             authorized, |  | ||||||
|             lockup, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         let stake_state = StakeState::Initialized(meta); |  | ||||||
|         let stake_data = serialize(&stake_state).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_stake(&stake_data).unwrap(), |  | ||||||
|             StakeAccountType::Initialized(UiStakeAccount { |  | ||||||
|                 meta: UiMeta { |  | ||||||
|                     rent_exempt_reserve: 42.to_string(), |  | ||||||
|                     authorized: UiAuthorized { |  | ||||||
|                         staker: pubkey.to_string(), |  | ||||||
|                         withdrawer: pubkey.to_string(), |  | ||||||
|                     }, |  | ||||||
|                     lockup: UiLockup { |  | ||||||
|                         unix_timestamp: 0, |  | ||||||
|                         epoch: 1, |  | ||||||
|                         custodian: custodian.to_string(), |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|                 stake: None, |  | ||||||
|             }) |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let voter_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let stake = Stake { |  | ||||||
|             delegation: Delegation { |  | ||||||
|                 voter_pubkey, |  | ||||||
|                 stake: 20, |  | ||||||
|                 activation_epoch: 2, |  | ||||||
|                 deactivation_epoch: std::u64::MAX, |  | ||||||
|                 warmup_cooldown_rate: 0.25, |  | ||||||
|             }, |  | ||||||
|             credits_observed: 10, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         let stake_state = StakeState::Stake(meta, stake); |  | ||||||
|         let stake_data = serialize(&stake_state).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_stake(&stake_data).unwrap(), |  | ||||||
|             StakeAccountType::Delegated(UiStakeAccount { |  | ||||||
|                 meta: UiMeta { |  | ||||||
|                     rent_exempt_reserve: 42.to_string(), |  | ||||||
|                     authorized: UiAuthorized { |  | ||||||
|                         staker: pubkey.to_string(), |  | ||||||
|                         withdrawer: pubkey.to_string(), |  | ||||||
|                     }, |  | ||||||
|                     lockup: UiLockup { |  | ||||||
|                         unix_timestamp: 0, |  | ||||||
|                         epoch: 1, |  | ||||||
|                         custodian: custodian.to_string(), |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|                 stake: Some(UiStake { |  | ||||||
|                     delegation: UiDelegation { |  | ||||||
|                         voter: voter_pubkey.to_string(), |  | ||||||
|                         stake: 20.to_string(), |  | ||||||
|                         activation_epoch: 2.to_string(), |  | ||||||
|                         deactivation_epoch: std::u64::MAX.to_string(), |  | ||||||
|                         warmup_cooldown_rate: 0.25, |  | ||||||
|                     }, |  | ||||||
|                     credits_observed: 10, |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let stake_state = StakeState::RewardsPool; |  | ||||||
|         let stake_data = serialize(&stake_state).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_stake(&stake_data).unwrap(), |  | ||||||
|             StakeAccountType::RewardsPool |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![1, 2, 3, 4]; |  | ||||||
|         assert!(parse_stake(&bad_data).is_err()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,328 +0,0 @@ | |||||||
| use crate::{ |  | ||||||
|     parse_account_data::{ParsableAccount, ParseAccountError}, |  | ||||||
|     StringAmount, UiFeeCalculator, |  | ||||||
| }; |  | ||||||
| use bincode::deserialize; |  | ||||||
| use bv::BitVec; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     clock::{Clock, Epoch, Slot, UnixTimestamp}, |  | ||||||
|     epoch_schedule::EpochSchedule, |  | ||||||
|     pubkey::Pubkey, |  | ||||||
|     rent::Rent, |  | ||||||
|     slot_hashes::SlotHashes, |  | ||||||
|     slot_history::{self, SlotHistory}, |  | ||||||
|     stake_history::{StakeHistory, StakeHistoryEntry}, |  | ||||||
|     sysvar::{self, fees::Fees, recent_blockhashes::RecentBlockhashes, rewards::Rewards}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> { |  | ||||||
|     let parsed_account = { |  | ||||||
|         if pubkey == &sysvar::clock::id() { |  | ||||||
|             deserialize::<Clock>(data) |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|clock| SysvarAccountType::Clock(clock.into())) |  | ||||||
|         } else if pubkey == &sysvar::epoch_schedule::id() { |  | ||||||
|             deserialize(data).ok().map(SysvarAccountType::EpochSchedule) |  | ||||||
|         } else if pubkey == &sysvar::fees::id() { |  | ||||||
|             deserialize::<Fees>(data) |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|fees| SysvarAccountType::Fees(fees.into())) |  | ||||||
|         } else if pubkey == &sysvar::recent_blockhashes::id() { |  | ||||||
|             deserialize::<RecentBlockhashes>(data) |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|recent_blockhashes| { |  | ||||||
|                     let recent_blockhashes = recent_blockhashes |  | ||||||
|                         .iter() |  | ||||||
|                         .map(|entry| UiRecentBlockhashesEntry { |  | ||||||
|                             blockhash: entry.blockhash.to_string(), |  | ||||||
|                             fee_calculator: entry.fee_calculator.clone().into(), |  | ||||||
|                         }) |  | ||||||
|                         .collect(); |  | ||||||
|                     SysvarAccountType::RecentBlockhashes(recent_blockhashes) |  | ||||||
|                 }) |  | ||||||
|         } else if pubkey == &sysvar::rent::id() { |  | ||||||
|             deserialize::<Rent>(data) |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|rent| SysvarAccountType::Rent(rent.into())) |  | ||||||
|         } else if pubkey == &sysvar::rewards::id() { |  | ||||||
|             deserialize::<Rewards>(data) |  | ||||||
|                 .ok() |  | ||||||
|                 .map(|rewards| SysvarAccountType::Rewards(rewards.into())) |  | ||||||
|         } else if pubkey == &sysvar::slot_hashes::id() { |  | ||||||
|             deserialize::<SlotHashes>(data).ok().map(|slot_hashes| { |  | ||||||
|                 let slot_hashes = slot_hashes |  | ||||||
|                     .iter() |  | ||||||
|                     .map(|slot_hash| UiSlotHashEntry { |  | ||||||
|                         slot: slot_hash.0, |  | ||||||
|                         hash: slot_hash.1.to_string(), |  | ||||||
|                     }) |  | ||||||
|                     .collect(); |  | ||||||
|                 SysvarAccountType::SlotHashes(slot_hashes) |  | ||||||
|             }) |  | ||||||
|         } else if pubkey == &sysvar::slot_history::id() { |  | ||||||
|             deserialize::<SlotHistory>(data).ok().map(|slot_history| { |  | ||||||
|                 SysvarAccountType::SlotHistory(UiSlotHistory { |  | ||||||
|                     next_slot: slot_history.next_slot, |  | ||||||
|                     bits: format!("{:?}", SlotHistoryBits(slot_history.bits)), |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|         } else if pubkey == &sysvar::stake_history::id() { |  | ||||||
|             deserialize::<StakeHistory>(data).ok().map(|stake_history| { |  | ||||||
|                 let stake_history = stake_history |  | ||||||
|                     .iter() |  | ||||||
|                     .map(|entry| UiStakeHistoryEntry { |  | ||||||
|                         epoch: entry.0, |  | ||||||
|                         stake_history: entry.1.clone(), |  | ||||||
|                     }) |  | ||||||
|                     .collect(); |  | ||||||
|                 SysvarAccountType::StakeHistory(stake_history) |  | ||||||
|             }) |  | ||||||
|         } else { |  | ||||||
|             None |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|     parsed_account.ok_or(ParseAccountError::AccountNotParsable( |  | ||||||
|         ParsableAccount::Sysvar, |  | ||||||
|     )) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum SysvarAccountType { |  | ||||||
|     Clock(UiClock), |  | ||||||
|     EpochSchedule(EpochSchedule), |  | ||||||
|     Fees(UiFees), |  | ||||||
|     RecentBlockhashes(Vec<UiRecentBlockhashesEntry>), |  | ||||||
|     Rent(UiRent), |  | ||||||
|     Rewards(UiRewards), |  | ||||||
|     SlotHashes(Vec<UiSlotHashEntry>), |  | ||||||
|     SlotHistory(UiSlotHistory), |  | ||||||
|     StakeHistory(Vec<UiStakeHistoryEntry>), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Default)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiClock { |  | ||||||
|     pub slot: Slot, |  | ||||||
|     pub epoch: Epoch, |  | ||||||
|     pub leader_schedule_epoch: Epoch, |  | ||||||
|     pub unix_timestamp: UnixTimestamp, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Clock> for UiClock { |  | ||||||
|     fn from(clock: Clock) -> Self { |  | ||||||
|         Self { |  | ||||||
|             slot: clock.slot, |  | ||||||
|             epoch: clock.epoch, |  | ||||||
|             leader_schedule_epoch: clock.leader_schedule_epoch, |  | ||||||
|             unix_timestamp: clock.unix_timestamp, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Default)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiFees { |  | ||||||
|     pub fee_calculator: UiFeeCalculator, |  | ||||||
| } |  | ||||||
| impl From<Fees> for UiFees { |  | ||||||
|     fn from(fees: Fees) -> Self { |  | ||||||
|         Self { |  | ||||||
|             fee_calculator: fees.fee_calculator.into(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Default)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiRent { |  | ||||||
|     pub lamports_per_byte_year: StringAmount, |  | ||||||
|     pub exemption_threshold: f64, |  | ||||||
|     pub burn_percent: u8, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Rent> for UiRent { |  | ||||||
|     fn from(rent: Rent) -> Self { |  | ||||||
|         Self { |  | ||||||
|             lamports_per_byte_year: rent.lamports_per_byte_year.to_string(), |  | ||||||
|             exemption_threshold: rent.exemption_threshold, |  | ||||||
|             burn_percent: rent.burn_percent, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq, Default)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiRewards { |  | ||||||
|     pub validator_point_value: f64, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<Rewards> for UiRewards { |  | ||||||
|     fn from(rewards: Rewards) -> Self { |  | ||||||
|         Self { |  | ||||||
|             validator_point_value: rewards.validator_point_value, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiRecentBlockhashesEntry { |  | ||||||
|     pub blockhash: String, |  | ||||||
|     pub fee_calculator: UiFeeCalculator, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiSlotHashEntry { |  | ||||||
|     pub slot: Slot, |  | ||||||
|     pub hash: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiSlotHistory { |  | ||||||
|     pub next_slot: Slot, |  | ||||||
|     pub bits: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| struct SlotHistoryBits(BitVec<u64>); |  | ||||||
|  |  | ||||||
| impl std::fmt::Debug for SlotHistoryBits { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         for i in 0..slot_history::MAX_ENTRIES { |  | ||||||
|             if self.0.get(i) { |  | ||||||
|                 write!(f, "1")?; |  | ||||||
|             } else { |  | ||||||
|                 write!(f, "0")?; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiStakeHistoryEntry { |  | ||||||
|     pub epoch: Epoch, |  | ||||||
|     pub stake_history: StakeHistoryEntry, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::{ |  | ||||||
|         fee_calculator::FeeCalculator, |  | ||||||
|         hash::Hash, |  | ||||||
|         sysvar::{recent_blockhashes::IterItem, Sysvar}, |  | ||||||
|     }; |  | ||||||
|     use std::iter::FromIterator; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_sysvars() { |  | ||||||
|         let clock_sysvar = Clock::default().create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(), |  | ||||||
|             SysvarAccountType::Clock(UiClock::default()), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let epoch_schedule = EpochSchedule { |  | ||||||
|             slots_per_epoch: 12, |  | ||||||
|             leader_schedule_slot_offset: 0, |  | ||||||
|             warmup: false, |  | ||||||
|             first_normal_epoch: 1, |  | ||||||
|             first_normal_slot: 12, |  | ||||||
|         }; |  | ||||||
|         let epoch_schedule_sysvar = epoch_schedule.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(), |  | ||||||
|             SysvarAccountType::EpochSchedule(epoch_schedule), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let fees_sysvar = Fees::default().create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(), |  | ||||||
|             SysvarAccountType::Fees(UiFees::default()), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let hash = Hash::new(&[1; 32]); |  | ||||||
|         let fee_calculator = FeeCalculator { |  | ||||||
|             lamports_per_signature: 10, |  | ||||||
|         }; |  | ||||||
|         let recent_blockhashes = |  | ||||||
|             RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter()); |  | ||||||
|         let recent_blockhashes_sysvar = recent_blockhashes.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar( |  | ||||||
|                 &recent_blockhashes_sysvar.data, |  | ||||||
|                 &sysvar::recent_blockhashes::id() |  | ||||||
|             ) |  | ||||||
|             .unwrap(), |  | ||||||
|             SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry { |  | ||||||
|                 blockhash: hash.to_string(), |  | ||||||
|                 fee_calculator: fee_calculator.into(), |  | ||||||
|             }]), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let rent = Rent { |  | ||||||
|             lamports_per_byte_year: 10, |  | ||||||
|             exemption_threshold: 2.0, |  | ||||||
|             burn_percent: 5, |  | ||||||
|         }; |  | ||||||
|         let rent_sysvar = rent.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(), |  | ||||||
|             SysvarAccountType::Rent(rent.into()), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let rewards_sysvar = Rewards::default().create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(), |  | ||||||
|             SysvarAccountType::Rewards(UiRewards::default()), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let mut slot_hashes = SlotHashes::default(); |  | ||||||
|         slot_hashes.add(1, hash); |  | ||||||
|         let slot_hashes_sysvar = slot_hashes.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(), |  | ||||||
|             SysvarAccountType::SlotHashes(vec![UiSlotHashEntry { |  | ||||||
|                 slot: 1, |  | ||||||
|                 hash: hash.to_string(), |  | ||||||
|             }]), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let mut slot_history = SlotHistory::default(); |  | ||||||
|         slot_history.add(42); |  | ||||||
|         let slot_history_sysvar = slot_history.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(), |  | ||||||
|             SysvarAccountType::SlotHistory(UiSlotHistory { |  | ||||||
|                 next_slot: slot_history.next_slot, |  | ||||||
|                 bits: format!("{:?}", SlotHistoryBits(slot_history.bits)), |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let mut stake_history = StakeHistory::default(); |  | ||||||
|         let stake_history_entry = StakeHistoryEntry { |  | ||||||
|             effective: 10, |  | ||||||
|             activating: 2, |  | ||||||
|             deactivating: 3, |  | ||||||
|         }; |  | ||||||
|         stake_history.add(1, stake_history_entry.clone()); |  | ||||||
|         let stake_history_sysvar = stake_history.create_account(1); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(), |  | ||||||
|             SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry { |  | ||||||
|                 epoch: 1, |  | ||||||
|                 stake_history: stake_history_entry, |  | ||||||
|             }]), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err()); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![0; 4]; |  | ||||||
|         assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,340 +0,0 @@ | |||||||
| use crate::{ |  | ||||||
|     parse_account_data::{ParsableAccount, ParseAccountError}, |  | ||||||
|     StringAmount, |  | ||||||
| }; |  | ||||||
| use solana_sdk::pubkey::Pubkey; |  | ||||||
| use spl_token_v2_0::{ |  | ||||||
|     solana_sdk::{program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey}, |  | ||||||
|     state::{Account, AccountState, Mint, Multisig}, |  | ||||||
| }; |  | ||||||
| use std::str::FromStr; |  | ||||||
|  |  | ||||||
| // A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to |  | ||||||
| // solana_sdk::pubkey::Pubkey |  | ||||||
| pub fn spl_token_id_v2_0() -> Pubkey { |  | ||||||
|     Pubkey::from_str(&spl_token_v2_0::id().to_string()).unwrap() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to |  | ||||||
| // solana_sdk::pubkey::Pubkey |  | ||||||
| pub fn spl_token_v2_0_native_mint() -> Pubkey { |  | ||||||
|     Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn parse_token( |  | ||||||
|     data: &[u8], |  | ||||||
|     mint_decimals: Option<u8>, |  | ||||||
| ) -> Result<TokenAccountType, ParseAccountError> { |  | ||||||
|     if data.len() == Account::get_packed_len() { |  | ||||||
|         let account = Account::unpack(data) |  | ||||||
|             .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; |  | ||||||
|         let decimals = mint_decimals.ok_or_else(|| { |  | ||||||
|             ParseAccountError::AdditionalDataMissing( |  | ||||||
|                 "no mint_decimals provided to parse spl-token account".to_string(), |  | ||||||
|             ) |  | ||||||
|         })?; |  | ||||||
|         Ok(TokenAccountType::Account(UiTokenAccount { |  | ||||||
|             mint: account.mint.to_string(), |  | ||||||
|             owner: account.owner.to_string(), |  | ||||||
|             token_amount: token_amount_to_ui_amount(account.amount, decimals), |  | ||||||
|             delegate: match account.delegate { |  | ||||||
|                 COption::Some(pubkey) => Some(pubkey.to_string()), |  | ||||||
|                 COption::None => None, |  | ||||||
|             }, |  | ||||||
|             state: account.state.into(), |  | ||||||
|             is_native: account.is_native(), |  | ||||||
|             rent_exempt_reserve: match account.is_native { |  | ||||||
|                 COption::Some(reserve) => Some(token_amount_to_ui_amount(reserve, decimals)), |  | ||||||
|                 COption::None => None, |  | ||||||
|             }, |  | ||||||
|             delegated_amount: if account.delegate.is_none() { |  | ||||||
|                 None |  | ||||||
|             } else { |  | ||||||
|                 Some(token_amount_to_ui_amount( |  | ||||||
|                     account.delegated_amount, |  | ||||||
|                     decimals, |  | ||||||
|                 )) |  | ||||||
|             }, |  | ||||||
|             close_authority: match account.close_authority { |  | ||||||
|                 COption::Some(pubkey) => Some(pubkey.to_string()), |  | ||||||
|                 COption::None => None, |  | ||||||
|             }, |  | ||||||
|         })) |  | ||||||
|     } else if data.len() == Mint::get_packed_len() { |  | ||||||
|         let mint = Mint::unpack(data) |  | ||||||
|             .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; |  | ||||||
|         Ok(TokenAccountType::Mint(UiMint { |  | ||||||
|             mint_authority: match mint.mint_authority { |  | ||||||
|                 COption::Some(pubkey) => Some(pubkey.to_string()), |  | ||||||
|                 COption::None => None, |  | ||||||
|             }, |  | ||||||
|             supply: mint.supply.to_string(), |  | ||||||
|             decimals: mint.decimals, |  | ||||||
|             is_initialized: mint.is_initialized, |  | ||||||
|             freeze_authority: match mint.freeze_authority { |  | ||||||
|                 COption::Some(pubkey) => Some(pubkey.to_string()), |  | ||||||
|                 COption::None => None, |  | ||||||
|             }, |  | ||||||
|         })) |  | ||||||
|     } else if data.len() == Multisig::get_packed_len() { |  | ||||||
|         let multisig = Multisig::unpack(data) |  | ||||||
|             .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; |  | ||||||
|         Ok(TokenAccountType::Multisig(UiMultisig { |  | ||||||
|             num_required_signers: multisig.m, |  | ||||||
|             num_valid_signers: multisig.n, |  | ||||||
|             is_initialized: multisig.is_initialized, |  | ||||||
|             signers: multisig |  | ||||||
|                 .signers |  | ||||||
|                 .iter() |  | ||||||
|                 .filter_map(|pubkey| { |  | ||||||
|                     if pubkey != &SplTokenPubkey::default() { |  | ||||||
|                         Some(pubkey.to_string()) |  | ||||||
|                     } else { |  | ||||||
|                         None |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|                 .collect(), |  | ||||||
|         })) |  | ||||||
|     } else { |  | ||||||
|         Err(ParseAccountError::AccountNotParsable( |  | ||||||
|             ParsableAccount::SplToken, |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum TokenAccountType { |  | ||||||
|     Account(UiTokenAccount), |  | ||||||
|     Mint(UiMint), |  | ||||||
|     Multisig(UiMultisig), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiTokenAccount { |  | ||||||
|     pub mint: String, |  | ||||||
|     pub owner: String, |  | ||||||
|     pub token_amount: UiTokenAmount, |  | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub delegate: Option<String>, |  | ||||||
|     pub state: UiAccountState, |  | ||||||
|     pub is_native: bool, |  | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub rent_exempt_reserve: Option<UiTokenAmount>, |  | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub delegated_amount: Option<UiTokenAmount>, |  | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub close_authority: Option<String>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub enum UiAccountState { |  | ||||||
|     Uninitialized, |  | ||||||
|     Initialized, |  | ||||||
|     Frozen, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<AccountState> for UiAccountState { |  | ||||||
|     fn from(state: AccountState) -> Self { |  | ||||||
|         match state { |  | ||||||
|             AccountState::Uninitialized => UiAccountState::Uninitialized, |  | ||||||
|             AccountState::Initialized => UiAccountState::Initialized, |  | ||||||
|             AccountState::Frozen => UiAccountState::Frozen, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiTokenAmount { |  | ||||||
|     pub ui_amount: f64, |  | ||||||
|     pub decimals: u8, |  | ||||||
|     pub amount: StringAmount, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl UiTokenAmount { |  | ||||||
|     pub fn real_number_string(&self) -> String { |  | ||||||
|         let decimals = self.decimals as usize; |  | ||||||
|         if decimals > 0 { |  | ||||||
|             let amount = u64::from_str(&self.amount).unwrap_or(0); |  | ||||||
|  |  | ||||||
|             // Left-pad zeros to decimals + 1, so we at least have an integer zero |  | ||||||
|             let mut s = format!("{:01$}", amount, decimals + 1); |  | ||||||
|  |  | ||||||
|             // Add the decimal point (Sorry, "," locales!) |  | ||||||
|             s.insert(s.len() - decimals, '.'); |  | ||||||
|             s |  | ||||||
|         } else { |  | ||||||
|             self.amount.clone() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn real_number_string_trimmed(&self) -> String { |  | ||||||
|         let s = self.real_number_string(); |  | ||||||
|         let zeros_trimmed = s.trim_end_matches('0'); |  | ||||||
|         let decimal_trimmed = zeros_trimmed.trim_end_matches('.'); |  | ||||||
|         decimal_trimmed.to_string() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount { |  | ||||||
|     // Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211 |  | ||||||
|     let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64; |  | ||||||
|     UiTokenAmount { |  | ||||||
|         ui_amount: amount_decimals, |  | ||||||
|         decimals, |  | ||||||
|         amount: amount.to_string(), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiMint { |  | ||||||
|     pub mint_authority: Option<String>, |  | ||||||
|     pub supply: StringAmount, |  | ||||||
|     pub decimals: u8, |  | ||||||
|     pub is_initialized: bool, |  | ||||||
|     pub freeze_authority: Option<String>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiMultisig { |  | ||||||
|     pub num_required_signers: u8, |  | ||||||
|     pub num_valid_signers: u8, |  | ||||||
|     pub is_initialized: bool, |  | ||||||
|     pub signers: Vec<String>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> { |  | ||||||
|     if data.len() == Account::get_packed_len() { |  | ||||||
|         Some(Pubkey::new(&data[0..32])) |  | ||||||
|     } else { |  | ||||||
|         None |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_token() { |  | ||||||
|         let mint_pubkey = SplTokenPubkey::new(&[2; 32]); |  | ||||||
|         let owner_pubkey = SplTokenPubkey::new(&[3; 32]); |  | ||||||
|         let mut account_data = vec![0; Account::get_packed_len()]; |  | ||||||
|         let mut account = Account::unpack_unchecked(&account_data).unwrap(); |  | ||||||
|         account.mint = mint_pubkey; |  | ||||||
|         account.owner = owner_pubkey; |  | ||||||
|         account.amount = 42; |  | ||||||
|         account.state = AccountState::Initialized; |  | ||||||
|         account.is_native = COption::None; |  | ||||||
|         account.close_authority = COption::Some(owner_pubkey); |  | ||||||
|         Account::pack(account, &mut account_data).unwrap(); |  | ||||||
|  |  | ||||||
|         assert!(parse_token(&account_data, None).is_err()); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_token(&account_data, Some(2)).unwrap(), |  | ||||||
|             TokenAccountType::Account(UiTokenAccount { |  | ||||||
|                 mint: mint_pubkey.to_string(), |  | ||||||
|                 owner: owner_pubkey.to_string(), |  | ||||||
|                 token_amount: UiTokenAmount { |  | ||||||
|                     ui_amount: 0.42, |  | ||||||
|                     decimals: 2, |  | ||||||
|                     amount: "42".to_string() |  | ||||||
|                 }, |  | ||||||
|                 delegate: None, |  | ||||||
|                 state: UiAccountState::Initialized, |  | ||||||
|                 is_native: false, |  | ||||||
|                 rent_exempt_reserve: None, |  | ||||||
|                 delegated_amount: None, |  | ||||||
|                 close_authority: Some(owner_pubkey.to_string()), |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let mut mint_data = vec![0; Mint::get_packed_len()]; |  | ||||||
|         let mut mint = Mint::unpack_unchecked(&mint_data).unwrap(); |  | ||||||
|         mint.mint_authority = COption::Some(owner_pubkey); |  | ||||||
|         mint.supply = 42; |  | ||||||
|         mint.decimals = 3; |  | ||||||
|         mint.is_initialized = true; |  | ||||||
|         mint.freeze_authority = COption::Some(owner_pubkey); |  | ||||||
|         Mint::pack(mint, &mut mint_data).unwrap(); |  | ||||||
|  |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_token(&mint_data, None).unwrap(), |  | ||||||
|             TokenAccountType::Mint(UiMint { |  | ||||||
|                 mint_authority: Some(owner_pubkey.to_string()), |  | ||||||
|                 supply: 42.to_string(), |  | ||||||
|                 decimals: 3, |  | ||||||
|                 is_initialized: true, |  | ||||||
|                 freeze_authority: Some(owner_pubkey.to_string()), |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let signer1 = SplTokenPubkey::new(&[1; 32]); |  | ||||||
|         let signer2 = SplTokenPubkey::new(&[2; 32]); |  | ||||||
|         let signer3 = SplTokenPubkey::new(&[3; 32]); |  | ||||||
|         let mut multisig_data = vec![0; Multisig::get_packed_len()]; |  | ||||||
|         let mut signers = [SplTokenPubkey::default(); 11]; |  | ||||||
|         signers[0] = signer1; |  | ||||||
|         signers[1] = signer2; |  | ||||||
|         signers[2] = signer3; |  | ||||||
|         let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap(); |  | ||||||
|         multisig.m = 2; |  | ||||||
|         multisig.n = 3; |  | ||||||
|         multisig.is_initialized = true; |  | ||||||
|         multisig.signers = signers; |  | ||||||
|         Multisig::pack(multisig, &mut multisig_data).unwrap(); |  | ||||||
|  |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_token(&multisig_data, None).unwrap(), |  | ||||||
|             TokenAccountType::Multisig(UiMultisig { |  | ||||||
|                 num_required_signers: 2, |  | ||||||
|                 num_valid_signers: 3, |  | ||||||
|                 is_initialized: true, |  | ||||||
|                 signers: vec![ |  | ||||||
|                     signer1.to_string(), |  | ||||||
|                     signer2.to_string(), |  | ||||||
|                     signer3.to_string() |  | ||||||
|                 ], |  | ||||||
|             }), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![0; 4]; |  | ||||||
|         assert!(parse_token(&bad_data, None).is_err()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_get_token_account_mint() { |  | ||||||
|         let mint_pubkey = SplTokenPubkey::new(&[2; 32]); |  | ||||||
|         let mut account_data = vec![0; Account::get_packed_len()]; |  | ||||||
|         let mut account = Account::unpack_unchecked(&account_data).unwrap(); |  | ||||||
|         account.mint = mint_pubkey; |  | ||||||
|         Account::pack(account, &mut account_data).unwrap(); |  | ||||||
|  |  | ||||||
|         let expected_mint_pubkey = Pubkey::new(&[2; 32]); |  | ||||||
|         assert_eq!( |  | ||||||
|             get_token_account_mint(&account_data), |  | ||||||
|             Some(expected_mint_pubkey) |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_ui_token_amount_real_string() { |  | ||||||
|         let token_amount = token_amount_to_ui_amount(1, 0); |  | ||||||
|         assert_eq!(&token_amount.real_number_string(), "1"); |  | ||||||
|         assert_eq!(&token_amount.real_number_string_trimmed(), "1"); |  | ||||||
|         let token_amount = token_amount_to_ui_amount(1, 9); |  | ||||||
|         assert_eq!(&token_amount.real_number_string(), "0.000000001"); |  | ||||||
|         assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001"); |  | ||||||
|         let token_amount = token_amount_to_ui_amount(1_000_000_000, 9); |  | ||||||
|         assert_eq!(&token_amount.real_number_string(), "1.000000000"); |  | ||||||
|         assert_eq!(&token_amount.real_number_string_trimmed(), "1"); |  | ||||||
|         let token_amount = token_amount_to_ui_amount(1_234_567_890, 3); |  | ||||||
|         assert_eq!(&token_amount.real_number_string(), "1234567.890"); |  | ||||||
|         assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,144 +0,0 @@ | |||||||
| use crate::{parse_account_data::ParseAccountError, StringAmount}; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     clock::{Epoch, Slot}, |  | ||||||
|     pubkey::Pubkey, |  | ||||||
| }; |  | ||||||
| use solana_vote_program::vote_state::{BlockTimestamp, Lockout, VoteState}; |  | ||||||
|  |  | ||||||
| pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, ParseAccountError> { |  | ||||||
|     let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?; |  | ||||||
|     let epoch_credits = vote_state |  | ||||||
|         .epoch_credits() |  | ||||||
|         .iter() |  | ||||||
|         .map(|(epoch, credits, previous_credits)| UiEpochCredits { |  | ||||||
|             epoch: *epoch, |  | ||||||
|             credits: credits.to_string(), |  | ||||||
|             previous_credits: previous_credits.to_string(), |  | ||||||
|         }) |  | ||||||
|         .collect(); |  | ||||||
|     let votes = vote_state |  | ||||||
|         .votes |  | ||||||
|         .iter() |  | ||||||
|         .map(|lockout| UiLockout { |  | ||||||
|             slot: lockout.slot, |  | ||||||
|             confirmation_count: lockout.confirmation_count, |  | ||||||
|         }) |  | ||||||
|         .collect(); |  | ||||||
|     let authorized_voters = vote_state |  | ||||||
|         .authorized_voters() |  | ||||||
|         .iter() |  | ||||||
|         .map(|(epoch, authorized_voter)| UiAuthorizedVoters { |  | ||||||
|             epoch: *epoch, |  | ||||||
|             authorized_voter: authorized_voter.to_string(), |  | ||||||
|         }) |  | ||||||
|         .collect(); |  | ||||||
|     let prior_voters = vote_state |  | ||||||
|         .prior_voters() |  | ||||||
|         .buf() |  | ||||||
|         .iter() |  | ||||||
|         .filter(|(pubkey, _, _)| pubkey != &Pubkey::default()) |  | ||||||
|         .map( |  | ||||||
|             |(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters { |  | ||||||
|                 authorized_pubkey: authorized_pubkey.to_string(), |  | ||||||
|                 epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch, |  | ||||||
|                 target_epoch: *target_epoch, |  | ||||||
|             }, |  | ||||||
|         ) |  | ||||||
|         .collect(); |  | ||||||
|     Ok(VoteAccountType::Vote(UiVoteState { |  | ||||||
|         node_pubkey: vote_state.node_pubkey.to_string(), |  | ||||||
|         authorized_withdrawer: vote_state.authorized_withdrawer.to_string(), |  | ||||||
|         commission: vote_state.commission, |  | ||||||
|         votes, |  | ||||||
|         root_slot: vote_state.root_slot, |  | ||||||
|         authorized_voters, |  | ||||||
|         prior_voters, |  | ||||||
|         epoch_credits, |  | ||||||
|         last_timestamp: vote_state.last_timestamp, |  | ||||||
|     })) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// A wrapper enum for consistency across programs |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase", tag = "type", content = "info")] |  | ||||||
| pub enum VoteAccountType { |  | ||||||
|     Vote(UiVoteState), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// A duplicate representation of VoteState for pretty JSON serialization |  | ||||||
| #[derive(Debug, Serialize, Deserialize, Default, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct UiVoteState { |  | ||||||
|     node_pubkey: String, |  | ||||||
|     authorized_withdrawer: String, |  | ||||||
|     commission: u8, |  | ||||||
|     votes: Vec<UiLockout>, |  | ||||||
|     root_slot: Option<Slot>, |  | ||||||
|     authorized_voters: Vec<UiAuthorizedVoters>, |  | ||||||
|     prior_voters: Vec<UiPriorVoters>, |  | ||||||
|     epoch_credits: Vec<UiEpochCredits>, |  | ||||||
|     last_timestamp: BlockTimestamp, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| struct UiLockout { |  | ||||||
|     slot: Slot, |  | ||||||
|     confirmation_count: u32, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<&Lockout> for UiLockout { |  | ||||||
|     fn from(lockout: &Lockout) -> Self { |  | ||||||
|         Self { |  | ||||||
|             slot: lockout.slot, |  | ||||||
|             confirmation_count: lockout.confirmation_count, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| struct UiAuthorizedVoters { |  | ||||||
|     epoch: Epoch, |  | ||||||
|     authorized_voter: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| struct UiPriorVoters { |  | ||||||
|     authorized_pubkey: String, |  | ||||||
|     epoch_of_last_authorized_switch: Epoch, |  | ||||||
|     target_epoch: Epoch, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, Deserialize, PartialEq)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| struct UiEpochCredits { |  | ||||||
|     epoch: Epoch, |  | ||||||
|     credits: StringAmount, |  | ||||||
|     previous_credits: StringAmount, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_vote_program::vote_state::VoteStateVersions; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_parse_vote() { |  | ||||||
|         let vote_state = VoteState::default(); |  | ||||||
|         let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()]; |  | ||||||
|         let versioned = VoteStateVersions::Current(Box::new(vote_state)); |  | ||||||
|         VoteState::serialize(&versioned, &mut vote_account_data).unwrap(); |  | ||||||
|         let mut expected_vote_state = UiVoteState::default(); |  | ||||||
|         expected_vote_state.node_pubkey = Pubkey::default().to_string(); |  | ||||||
|         expected_vote_state.authorized_withdrawer = Pubkey::default().to_string(); |  | ||||||
|         assert_eq!( |  | ||||||
|             parse_vote(&vote_account_data).unwrap(), |  | ||||||
|             VoteAccountType::Vote(expected_vote_state) |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let bad_data = vec![0; 4]; |  | ||||||
|         assert!(parse_vote(&bad_data).is_err()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| use solana_config_program::ConfigState; |  | ||||||
|  |  | ||||||
| pub const MAX_SHORT_FIELD_LENGTH: usize = 70; |  | ||||||
| pub const MAX_LONG_FIELD_LENGTH: usize = 300; |  | ||||||
| pub const MAX_VALIDATOR_INFO: u64 = 576; |  | ||||||
|  |  | ||||||
| solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111"); |  | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, PartialEq, Serialize, Default)] |  | ||||||
| pub struct ValidatorInfo { |  | ||||||
|     pub info: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ConfigState for ValidatorInfo { |  | ||||||
|     fn max_space() -> u64 { |  | ||||||
|         MAX_VALIDATOR_INFO |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,21 +1,19 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-accounts-bench" | name = "solana-accounts-bench" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| publish = false |  | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| log = "0.4.6" | log = "0.4.6" | ||||||
| rayon = "1.4.0" | rayon = "1.3.0" | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } | solana-runtime = { path = "../runtime", version = "1.2.7" } | ||||||
| solana-measure = { path = "../measure", version = "1.3.21" } | solana-measure = { path = "../measure", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
| solana-version = { path = "../version", version = "1.3.21" } |  | ||||||
| rand = "0.7.0" | rand = "0.7.0" | ||||||
| clap = "2.33.1" | clap = "2.33.1" | ||||||
| crossbeam-channel = "0.4" | crossbeam-channel = "0.4" | ||||||
|   | |||||||
| @@ -1,21 +1,20 @@ | |||||||
| use clap::{crate_description, crate_name, value_t, App, Arg}; | use clap::{value_t, App, Arg}; | ||||||
| use rayon::prelude::*; | use rayon::prelude::*; | ||||||
| use solana_measure::measure::Measure; | use solana_measure::measure::Measure; | ||||||
| use solana_runtime::{ | use solana_runtime::{ | ||||||
|     accounts::{create_test_accounts, update_accounts, Accounts}, |     accounts::{create_test_accounts, update_accounts, Accounts}, | ||||||
|     accounts_index::Ancestors, |     accounts_index::Ancestors, | ||||||
| }; | }; | ||||||
| use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey}; | use solana_sdk::pubkey::Pubkey; | ||||||
| use std::env; |  | ||||||
| use std::fs; | use std::fs; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
|  |  | ||||||
| fn main() { | fn main() { | ||||||
|     solana_logger::setup(); |     solana_logger::setup(); | ||||||
|  |  | ||||||
|     let matches = App::new(crate_name!()) |     let matches = App::new("crate") | ||||||
|         .about(crate_description!()) |         .about("about") | ||||||
|         .version(solana_version::version!()) |         .version("version") | ||||||
|         .arg( |         .arg( | ||||||
|             Arg::with_name("num_slots") |             Arg::with_name("num_slots") | ||||||
|                 .long("num_slots") |                 .long("num_slots") | ||||||
| @@ -51,12 +50,11 @@ fn main() { | |||||||
|     let clean = matches.is_present("clean"); |     let clean = matches.is_present("clean"); | ||||||
|     println!("clean: {:?}", clean); |     println!("clean: {:?}", clean); | ||||||
|  |  | ||||||
|     let path = PathBuf::from(env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_owned())) |     let path = PathBuf::from("farf/accounts-bench"); | ||||||
|         .join("accounts-bench"); |  | ||||||
|     if fs::remove_dir_all(path.clone()).is_err() { |     if fs::remove_dir_all(path.clone()).is_err() { | ||||||
|         println!("Warning: Couldn't remove {:?}", path); |         println!("Warning: Couldn't remove {:?}", path); | ||||||
|     } |     } | ||||||
|     let accounts = Accounts::new(vec![path], &ClusterType::Testnet); |     let accounts = Accounts::new(vec![path]); | ||||||
|     println!("Creating {} accounts", num_accounts); |     println!("Creating {} accounts", num_accounts); | ||||||
|     let mut create_time = Measure::start("create accounts"); |     let mut create_time = Measure::start("create accounts"); | ||||||
|     let pubkeys: Vec<_> = (0..num_slots) |     let pubkeys: Vec<_> = (0..num_slots) | ||||||
| @@ -98,7 +96,7 @@ fn main() { | |||||||
|         } else { |         } else { | ||||||
|             let mut pubkeys: Vec<Pubkey> = vec![]; |             let mut pubkeys: Vec<Pubkey> = vec![]; | ||||||
|             let mut time = Measure::start("hash"); |             let mut time = Measure::start("hash"); | ||||||
|             let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors).0; |             let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors); | ||||||
|             time.stop(); |             time.stop(); | ||||||
|             println!("hash: {} {}", hash, time); |             println!("hash: {} {}", hash, time); | ||||||
|             create_test_accounts(&accounts, &mut pubkeys, 1, 0); |             create_test_accounts(&accounts, &mut pubkeys, 1, 0); | ||||||
|   | |||||||
| @@ -1,29 +1,28 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-banking-bench" | name = "solana-banking-bench" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| publish = false |  | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| clap = "2.33.1" | clap = "2.33.1" | ||||||
| crossbeam-channel = "0.4" | crossbeam-channel = "0.4" | ||||||
| log = "0.4.6" | log = "0.4.6" | ||||||
| rand = "0.7.0" | rand = "0.7.0" | ||||||
| rayon = "1.4.0" | rayon = "1.3.0" | ||||||
| solana-core = { path = "../core", version = "1.3.21" } | solana-core = { path = "../core", version = "1.2.7" } | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } | solana-clap-utils = { path = "../clap-utils", version = "1.2.7" } | ||||||
| solana-streamer = { path = "../streamer", version = "1.3.21" } | solana-streamer = { path = "../streamer", version = "1.2.7" } | ||||||
| solana-perf = { path = "../perf", version = "1.3.21" } | solana-perf = { path = "../perf", version = "1.2.7" } | ||||||
| solana-ledger = { path = "../ledger", version = "1.3.21" } | solana-ledger = { path = "../ledger", version = "1.2.7" } | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } | solana-runtime = { path = "../runtime", version = "1.2.7" } | ||||||
| solana-measure = { path = "../measure", version = "1.3.21" } | solana-measure = { path = "../measure", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
| solana-version = { path = "../version", version = "1.3.21" } | solana-version = { path = "../version", version = "1.2.7" } | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
| targets = ["x86_64-unknown-linux-gnu"] | targets = ["x86_64-unknown-linux-gnu"] | ||||||
|   | |||||||
| @@ -11,15 +11,17 @@ use solana_core::{ | |||||||
|     poh_recorder::WorkingBankEntry, |     poh_recorder::WorkingBankEntry, | ||||||
| }; | }; | ||||||
| use solana_ledger::{ | use solana_ledger::{ | ||||||
|  |     bank_forks::BankForks, | ||||||
|     blockstore::Blockstore, |     blockstore::Blockstore, | ||||||
|     genesis_utils::{create_genesis_config, GenesisConfigInfo}, |     genesis_utils::{create_genesis_config, GenesisConfigInfo}, | ||||||
|     get_tmp_ledger_path, |     get_tmp_ledger_path, | ||||||
| }; | }; | ||||||
| use solana_measure::measure::Measure; | use solana_measure::measure::Measure; | ||||||
| use solana_perf::packet::to_packets_chunked; | use solana_perf::packet::to_packets_chunked; | ||||||
| use solana_runtime::{bank::Bank, bank_forks::BankForks}; | use solana_runtime::bank::Bank; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     hash::Hash, |     hash::Hash, | ||||||
|  |     pubkey::Pubkey, | ||||||
|     signature::Keypair, |     signature::Keypair, | ||||||
|     signature::Signature, |     signature::Signature, | ||||||
|     system_transaction, |     system_transaction, | ||||||
| @@ -68,7 +70,7 @@ fn make_accounts_txs( | |||||||
|     hash: Hash, |     hash: Hash, | ||||||
|     same_payer: bool, |     same_payer: bool, | ||||||
| ) -> Vec<Transaction> { | ) -> Vec<Transaction> { | ||||||
|     let to_pubkey = solana_sdk::pubkey::new_rand(); |     let to_pubkey = Pubkey::new_rand(); | ||||||
|     let payer_key = Keypair::new(); |     let payer_key = Keypair::new(); | ||||||
|     let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash); |     let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash); | ||||||
|     (0..total_num_transactions) |     (0..total_num_transactions) | ||||||
| @@ -77,9 +79,9 @@ fn make_accounts_txs( | |||||||
|             let mut new = dummy.clone(); |             let mut new = dummy.clone(); | ||||||
|             let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect(); |             let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect(); | ||||||
|             if !same_payer { |             if !same_payer { | ||||||
|                 new.message.account_keys[0] = solana_sdk::pubkey::new_rand(); |                 new.message.account_keys[0] = Pubkey::new_rand(); | ||||||
|             } |             } | ||||||
|             new.message.account_keys[1] = solana_sdk::pubkey::new_rand(); |             new.message.account_keys[1] = Pubkey::new_rand(); | ||||||
|             new.signatures = vec![Signature::new(&sig[0..64])]; |             new.signatures = vec![Signature::new(&sig[0..64])]; | ||||||
|             new |             new | ||||||
|         }) |         }) | ||||||
| @@ -166,7 +168,6 @@ fn main() { | |||||||
|  |  | ||||||
|     let (verified_sender, verified_receiver) = unbounded(); |     let (verified_sender, verified_receiver) = unbounded(); | ||||||
|     let (vote_sender, vote_receiver) = unbounded(); |     let (vote_sender, vote_receiver) = unbounded(); | ||||||
|     let (replay_vote_sender, _replay_vote_receiver) = unbounded(); |  | ||||||
|     let bank0 = Bank::new(&genesis_config); |     let bank0 = Bank::new(&genesis_config); | ||||||
|     let mut bank_forks = BankForks::new(bank0); |     let mut bank_forks = BankForks::new(bank0); | ||||||
|     let mut bank = bank_forks.working_bank(); |     let mut bank = bank_forks.working_bank(); | ||||||
| @@ -208,7 +209,7 @@ fn main() { | |||||||
|         bank.clear_signatures(); |         bank.clear_signatures(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let mut verified: Vec<_> = to_packets_chunked(&transactions, packets_per_chunk); |     let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), packets_per_chunk); | ||||||
|     let ledger_path = get_tmp_ledger_path!(); |     let ledger_path = get_tmp_ledger_path!(); | ||||||
|     { |     { | ||||||
|         let blockstore = Arc::new( |         let blockstore = Arc::new( | ||||||
| @@ -224,7 +225,6 @@ fn main() { | |||||||
|             verified_receiver, |             verified_receiver, | ||||||
|             vote_receiver, |             vote_receiver, | ||||||
|             None, |             None, | ||||||
|             replay_vote_sender, |  | ||||||
|         ); |         ); | ||||||
|         poh_recorder.lock().unwrap().set_bank(&bank); |         poh_recorder.lock().unwrap().set_bank(&bank); | ||||||
|  |  | ||||||
| @@ -240,7 +240,7 @@ fn main() { | |||||||
|         let base_tx_count = bank.transaction_count(); |         let base_tx_count = bank.transaction_count(); | ||||||
|         let mut txs_processed = 0; |         let mut txs_processed = 0; | ||||||
|         let mut root = 1; |         let mut root = 1; | ||||||
|         let collector = solana_sdk::pubkey::new_rand(); |         let collector = Pubkey::new_rand(); | ||||||
|         let config = Config { |         let config = Config { | ||||||
|             packets_per_batch: packets_per_chunk, |             packets_per_batch: packets_per_chunk, | ||||||
|             chunk_len, |             chunk_len, | ||||||
|   | |||||||
| @@ -1,30 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "solana-banks-client" |  | ||||||
| version = "1.3.21" |  | ||||||
| description = "Solana banks client" |  | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] |  | ||||||
| repository = "https://github.com/solana-labs/solana" |  | ||||||
| license = "Apache-2.0" |  | ||||||
| homepage = "https://solana.com/" |  | ||||||
| edition = "2018" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| async-trait = "0.1.36" |  | ||||||
| bincode = "1.3.1" |  | ||||||
| futures = "0.3" |  | ||||||
| solana-banks-interface = { path = "../banks-interface", version = "1.3.21" } |  | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } |  | ||||||
| tarpc = { version = "0.21.0", features = ["full"] } |  | ||||||
| tokio = "0.2" |  | ||||||
| tokio-serde = { version = "0.6", features = ["bincode"] } |  | ||||||
|  |  | ||||||
| [dev-dependencies] |  | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } |  | ||||||
| solana-banks-server = { path = "../banks-server", version = "1.3.21" } |  | ||||||
|  |  | ||||||
| [lib] |  | ||||||
| crate-type = ["lib"] |  | ||||||
| name = "solana_banks_client" |  | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] |  | ||||||
| targets = ["x86_64-unknown-linux-gnu"] |  | ||||||
| @@ -1,296 +0,0 @@ | |||||||
| //! A client for the ledger state, from the perspective of an arbitrary validator. |  | ||||||
| //! |  | ||||||
| //! Use start_tcp_client() to create a client and then import BanksClientExt to |  | ||||||
| //! access its methods. Additional "*_with_context" methods are also available, |  | ||||||
| //! but they are undocumented, may change over time, and are generally more |  | ||||||
| //! cumbersome to use. |  | ||||||
|  |  | ||||||
| use async_trait::async_trait; |  | ||||||
| use futures::future::join_all; |  | ||||||
| pub use solana_banks_interface::{BanksClient, TransactionStatus}; |  | ||||||
| use solana_banks_interface::{BanksRequest, BanksResponse}; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     account::Account, clock::Slot, commitment_config::CommitmentLevel, |  | ||||||
|     fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey, signature::Signature, |  | ||||||
|     transaction::Transaction, transport, |  | ||||||
| }; |  | ||||||
| use std::io::{self, Error, ErrorKind}; |  | ||||||
| use tarpc::{ |  | ||||||
|     client, context, |  | ||||||
|     rpc::{transport::channel::UnboundedChannel, ClientMessage, Response}, |  | ||||||
|     serde_transport::tcp, |  | ||||||
| }; |  | ||||||
| use tokio::{net::ToSocketAddrs, time::Duration}; |  | ||||||
| use tokio_serde::formats::Bincode; |  | ||||||
|  |  | ||||||
| #[async_trait] |  | ||||||
| pub trait BanksClientExt { |  | ||||||
|     /// Send a transaction and return immediately. The server will resend the |  | ||||||
|     /// transaction until either it is accepted by the cluster or the transaction's |  | ||||||
|     /// blockhash expires. |  | ||||||
|     async fn send_transaction(&mut self, transaction: Transaction) -> io::Result<()>; |  | ||||||
|  |  | ||||||
|     /// Return a recent, rooted blockhash from the server. The cluster will only accept |  | ||||||
|     /// transactions with a blockhash that has not yet expired. Use the `get_fees` |  | ||||||
|     /// method to get both a blockhash and the blockhash's last valid slot. |  | ||||||
|     async fn get_recent_blockhash(&mut self) -> io::Result<Hash>; |  | ||||||
|  |  | ||||||
|     /// Return the fee parameters associated with a recent, rooted blockhash. The cluster |  | ||||||
|     /// will use the transaction's blockhash to look up these same fee parameters and |  | ||||||
|     /// use them to calculate the transaction fee. |  | ||||||
|     async fn get_fees(&mut self) -> io::Result<(FeeCalculator, Hash, Slot)>; |  | ||||||
|  |  | ||||||
|     /// Send a transaction and return after the transaction has been rejected or |  | ||||||
|     /// reached the given level of commitment. |  | ||||||
|     async fn process_transaction_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         transaction: Transaction, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> transport::Result<()>; |  | ||||||
|  |  | ||||||
|     /// Send a transaction and return after the transaction has been finalized or rejected. |  | ||||||
|     async fn process_transaction(&mut self, transaction: Transaction) -> transport::Result<()>; |  | ||||||
|  |  | ||||||
|     /// Return the status of a transaction with a signature matching the transaction's first |  | ||||||
|     /// signature. Return None if the transaction is not found, which may be because the |  | ||||||
|     /// blockhash was expired or the fee-paying account had insufficient funds to pay the |  | ||||||
|     /// transaction fee. Note that servers rarely store the full transaction history. This |  | ||||||
|     /// method may return None if the transaction status has been discarded. |  | ||||||
|     async fn get_transaction_status( |  | ||||||
|         &mut self, |  | ||||||
|         signature: Signature, |  | ||||||
|     ) -> io::Result<Option<TransactionStatus>>; |  | ||||||
|  |  | ||||||
|     /// Same as get_transaction_status, but for multiple transactions. |  | ||||||
|     async fn get_transaction_statuses( |  | ||||||
|         &mut self, |  | ||||||
|         signatures: Vec<Signature>, |  | ||||||
|     ) -> io::Result<Vec<Option<TransactionStatus>>>; |  | ||||||
|  |  | ||||||
|     /// Return the most recent rooted slot height. All transactions at or below this height |  | ||||||
|     /// are said to be finalized. The cluster will not fork to a higher slot height. |  | ||||||
|     async fn get_root_slot(&mut self) -> io::Result<Slot>; |  | ||||||
|  |  | ||||||
|     /// Return the account at the given address at the slot corresponding to the given |  | ||||||
|     /// commitment level. If the account is not found, None is returned. |  | ||||||
|     async fn get_account_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> io::Result<Option<Account>>; |  | ||||||
|  |  | ||||||
|     /// Return the account at the given address at the time of the most recent root slot. |  | ||||||
|     /// If the account is not found, None is returned. |  | ||||||
|     async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>>; |  | ||||||
|  |  | ||||||
|     /// Return the balance in lamports of an account at the given address at the slot |  | ||||||
|     /// corresponding to the given commitment level. |  | ||||||
|     async fn get_balance_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> io::Result<u64>; |  | ||||||
|  |  | ||||||
|     /// Return the balance in lamports of an account at the given address at the time |  | ||||||
|     /// of the most recent root slot. |  | ||||||
|     async fn get_balance(&mut self, address: Pubkey) -> io::Result<u64>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[async_trait] |  | ||||||
| impl BanksClientExt for BanksClient { |  | ||||||
|     async fn send_transaction(&mut self, transaction: Transaction) -> io::Result<()> { |  | ||||||
|         self.send_transaction_with_context(context::current(), transaction) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_fees(&mut self) -> io::Result<(FeeCalculator, Hash, Slot)> { |  | ||||||
|         self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::Root) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_recent_blockhash(&mut self) -> io::Result<Hash> { |  | ||||||
|         Ok(self.get_fees().await?.1) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn process_transaction_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         transaction: Transaction, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> transport::Result<()> { |  | ||||||
|         let mut ctx = context::current(); |  | ||||||
|         ctx.deadline += Duration::from_secs(50); |  | ||||||
|         let result = self |  | ||||||
|             .process_transaction_with_commitment_and_context(ctx, transaction, commitment) |  | ||||||
|             .await?; |  | ||||||
|         match result { |  | ||||||
|             None => Err(Error::new(ErrorKind::TimedOut, "invalid blockhash or fee-payer").into()), |  | ||||||
|             Some(transaction_result) => Ok(transaction_result?), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn process_transaction(&mut self, transaction: Transaction) -> transport::Result<()> { |  | ||||||
|         self.process_transaction_with_commitment(transaction, CommitmentLevel::default()) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_root_slot(&mut self) -> io::Result<Slot> { |  | ||||||
|         self.get_slot_with_context(context::current(), CommitmentLevel::Root) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_account_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> io::Result<Option<Account>> { |  | ||||||
|         self.get_account_with_commitment_and_context(context::current(), address, commitment) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>> { |  | ||||||
|         self.get_account_with_commitment(address, CommitmentLevel::default()) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_balance_with_commitment( |  | ||||||
|         &mut self, |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> io::Result<u64> { |  | ||||||
|         let account = self |  | ||||||
|             .get_account_with_commitment_and_context(context::current(), address, commitment) |  | ||||||
|             .await?; |  | ||||||
|         Ok(account.map(|x| x.lamports).unwrap_or(0)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_balance(&mut self, address: Pubkey) -> io::Result<u64> { |  | ||||||
|         self.get_balance_with_commitment(address, CommitmentLevel::default()) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_transaction_status( |  | ||||||
|         &mut self, |  | ||||||
|         signature: Signature, |  | ||||||
|     ) -> io::Result<Option<TransactionStatus>> { |  | ||||||
|         self.get_transaction_status_with_context(context::current(), signature) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_transaction_statuses( |  | ||||||
|         &mut self, |  | ||||||
|         signatures: Vec<Signature>, |  | ||||||
|     ) -> io::Result<Vec<Option<TransactionStatus>>> { |  | ||||||
|         // tarpc futures oddly hold a mutable reference back to the client so clone the client upfront |  | ||||||
|         let mut clients_and_signatures: Vec<_> = signatures |  | ||||||
|             .into_iter() |  | ||||||
|             .map(|signature| (self.clone(), signature)) |  | ||||||
|             .collect(); |  | ||||||
|  |  | ||||||
|         let futs = clients_and_signatures |  | ||||||
|             .iter_mut() |  | ||||||
|             .map(|(client, signature)| client.get_transaction_status(*signature)); |  | ||||||
|  |  | ||||||
|         let statuses = join_all(futs).await; |  | ||||||
|  |  | ||||||
|         // Convert Vec<Result<_, _>> to Result<Vec<_>> |  | ||||||
|         statuses.into_iter().collect() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn start_client( |  | ||||||
|     transport: UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>>, |  | ||||||
| ) -> io::Result<BanksClient> { |  | ||||||
|     BanksClient::new(client::Config::default(), transport).spawn() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> io::Result<BanksClient> { |  | ||||||
|     let transport = tcp::connect(addr, Bincode::default()).await?; |  | ||||||
|     BanksClient::new(client::Config::default(), transport).spawn() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_banks_server::banks_server::start_local_server; |  | ||||||
|     use solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config}; |  | ||||||
|     use solana_sdk::{message::Message, signature::Signer, system_instruction}; |  | ||||||
|     use std::sync::{Arc, RwLock}; |  | ||||||
|     use tarpc::transport; |  | ||||||
|     use tokio::{runtime::Runtime, time::delay_for}; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_banks_client_new() { |  | ||||||
|         let (client_transport, _server_transport) = transport::channel::unbounded(); |  | ||||||
|         BanksClient::new(client::Config::default(), client_transport); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_banks_server_transfer_via_server() -> io::Result<()> { |  | ||||||
|         // This test shows the preferred way to interact with BanksServer. |  | ||||||
|         // It creates a runtime explicitly (no globals via tokio macros) and calls |  | ||||||
|         // `runtime.block_on()` just once, to run all the async code. |  | ||||||
|  |  | ||||||
|         let genesis = create_genesis_config(10); |  | ||||||
|         let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new( |  | ||||||
|             &genesis.genesis_config, |  | ||||||
|         )))); |  | ||||||
|  |  | ||||||
|         let bob_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let mint_pubkey = genesis.mint_keypair.pubkey(); |  | ||||||
|         let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1); |  | ||||||
|         let message = Message::new(&[instruction], Some(&mint_pubkey)); |  | ||||||
|  |  | ||||||
|         Runtime::new()?.block_on(async { |  | ||||||
|             let client_transport = start_local_server(&bank_forks).await; |  | ||||||
|             let mut banks_client = |  | ||||||
|                 BanksClient::new(client::Config::default(), client_transport).spawn()?; |  | ||||||
|  |  | ||||||
|             let recent_blockhash = banks_client.get_recent_blockhash().await?; |  | ||||||
|             let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash); |  | ||||||
|             banks_client.process_transaction(transaction).await.unwrap(); |  | ||||||
|             assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1); |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_banks_server_transfer_via_client() -> io::Result<()> { |  | ||||||
|         // The caller may not want to hold the connection open until the transaction |  | ||||||
|         // is processed (or blockhash expires). In this test, we verify the |  | ||||||
|         // server-side functionality is available to the client. |  | ||||||
|  |  | ||||||
|         let genesis = create_genesis_config(10); |  | ||||||
|         let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new( |  | ||||||
|             &genesis.genesis_config, |  | ||||||
|         )))); |  | ||||||
|  |  | ||||||
|         let mint_pubkey = &genesis.mint_keypair.pubkey(); |  | ||||||
|         let bob_pubkey = solana_sdk::pubkey::new_rand(); |  | ||||||
|         let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1); |  | ||||||
|         let message = Message::new(&[instruction], Some(&mint_pubkey)); |  | ||||||
|  |  | ||||||
|         Runtime::new()?.block_on(async { |  | ||||||
|             let client_transport = start_local_server(&bank_forks).await; |  | ||||||
|             let mut banks_client = |  | ||||||
|                 BanksClient::new(client::Config::default(), client_transport).spawn()?; |  | ||||||
|             let (_, recent_blockhash, last_valid_slot) = banks_client.get_fees().await?; |  | ||||||
|             let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash); |  | ||||||
|             let signature = transaction.signatures[0]; |  | ||||||
|             banks_client.send_transaction(transaction).await?; |  | ||||||
|  |  | ||||||
|             let mut status = banks_client.get_transaction_status(signature).await?; |  | ||||||
|  |  | ||||||
|             while status.is_none() { |  | ||||||
|                 let root_slot = banks_client.get_root_slot().await?; |  | ||||||
|                 if root_slot > last_valid_slot { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 delay_for(Duration::from_millis(100)).await; |  | ||||||
|                 status = banks_client.get_transaction_status(signature).await?; |  | ||||||
|             } |  | ||||||
|             assert!(status.unwrap().err.is_none()); |  | ||||||
|             assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1); |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "solana-banks-interface" |  | ||||||
| version = "1.3.21" |  | ||||||
| description = "Solana banks RPC interface" |  | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] |  | ||||||
| repository = "https://github.com/solana-labs/solana" |  | ||||||
| license = "Apache-2.0" |  | ||||||
| homepage = "https://solana.com/" |  | ||||||
| edition = "2018" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| serde = { version = "1.0.112", features = ["derive"] } |  | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } |  | ||||||
| tarpc = { version = "0.21.0", features = ["full"] } |  | ||||||
|  |  | ||||||
| [lib] |  | ||||||
| crate-type = ["lib"] |  | ||||||
| name = "solana_banks_interface" |  | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] |  | ||||||
| targets = ["x86_64-unknown-linux-gnu"] |  | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| use serde::{Deserialize, Serialize}; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     account::Account, |  | ||||||
|     clock::Slot, |  | ||||||
|     commitment_config::CommitmentLevel, |  | ||||||
|     fee_calculator::FeeCalculator, |  | ||||||
|     hash::Hash, |  | ||||||
|     pubkey::Pubkey, |  | ||||||
|     signature::Signature, |  | ||||||
|     transaction::{self, Transaction, TransactionError}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] |  | ||||||
| pub struct TransactionStatus { |  | ||||||
|     pub slot: Slot, |  | ||||||
|     pub confirmations: Option<usize>, // None = rooted |  | ||||||
|     pub err: Option<TransactionError>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tarpc::service] |  | ||||||
| pub trait Banks { |  | ||||||
|     async fn send_transaction_with_context(transaction: Transaction); |  | ||||||
|     async fn get_fees_with_commitment_and_context( |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> (FeeCalculator, Hash, Slot); |  | ||||||
|     async fn get_transaction_status_with_context(signature: Signature) |  | ||||||
|         -> Option<TransactionStatus>; |  | ||||||
|     async fn get_slot_with_context(commitment: CommitmentLevel) -> Slot; |  | ||||||
|     async fn process_transaction_with_commitment_and_context( |  | ||||||
|         transaction: Transaction, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> Option<transaction::Result<()>>; |  | ||||||
|     async fn get_account_with_commitment_and_context( |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> Option<Account>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use tarpc::{client, transport}; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_banks_client_new() { |  | ||||||
|         let (client_transport, _server_transport) = transport::channel::unbounded(); |  | ||||||
|         BanksClient::new(client::Config::default(), client_transport); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "solana-banks-server" |  | ||||||
| version = "1.3.21" |  | ||||||
| description = "Solana banks server" |  | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] |  | ||||||
| repository = "https://github.com/solana-labs/solana" |  | ||||||
| license = "Apache-2.0" |  | ||||||
| homepage = "https://solana.com/" |  | ||||||
| edition = "2018" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| bincode = "1.3.1" |  | ||||||
| futures = "0.3" |  | ||||||
| log = "0.4.8" |  | ||||||
| solana-banks-interface = { path = "../banks-interface", version = "1.3.21" } |  | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } |  | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } |  | ||||||
| solana-metrics = { path = "../metrics", version = "1.3.21" } |  | ||||||
| tarpc = { version = "0.21.0", features = ["full"] } |  | ||||||
| tokio = "0.2" |  | ||||||
| tokio-serde = { version = "0.6", features = ["bincode"] } |  | ||||||
|  |  | ||||||
| [lib] |  | ||||||
| crate-type = ["lib"] |  | ||||||
| name = "solana_banks_server" |  | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] |  | ||||||
| targets = ["x86_64-unknown-linux-gnu"] |  | ||||||
| @@ -1,270 +0,0 @@ | |||||||
| use crate::send_transaction_service::{SendTransactionService, TransactionInfo}; |  | ||||||
| use bincode::{deserialize, serialize}; |  | ||||||
| use futures::{ |  | ||||||
|     future, |  | ||||||
|     prelude::stream::{self, StreamExt}, |  | ||||||
| }; |  | ||||||
| use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus}; |  | ||||||
| use solana_runtime::{ |  | ||||||
|     bank::Bank, |  | ||||||
|     bank_forks::BankForks, |  | ||||||
|     commitment::{BlockCommitmentCache, CommitmentSlots}, |  | ||||||
| }; |  | ||||||
| use solana_sdk::{ |  | ||||||
|     account::Account, |  | ||||||
|     clock::Slot, |  | ||||||
|     commitment_config::CommitmentLevel, |  | ||||||
|     fee_calculator::FeeCalculator, |  | ||||||
|     hash::Hash, |  | ||||||
|     pubkey::Pubkey, |  | ||||||
|     signature::Signature, |  | ||||||
|     transaction::{self, Transaction}, |  | ||||||
| }; |  | ||||||
| use std::{ |  | ||||||
|     collections::HashMap, |  | ||||||
|     io, |  | ||||||
|     net::{Ipv4Addr, SocketAddr}, |  | ||||||
|     sync::{ |  | ||||||
|         mpsc::{channel, Receiver, Sender}, |  | ||||||
|         Arc, RwLock, |  | ||||||
|     }, |  | ||||||
|     thread::Builder, |  | ||||||
|     time::Duration, |  | ||||||
| }; |  | ||||||
| use tarpc::{ |  | ||||||
|     context::Context, |  | ||||||
|     rpc::{transport::channel::UnboundedChannel, ClientMessage, Response}, |  | ||||||
|     serde_transport::tcp, |  | ||||||
|     server::{self, Channel, Handler}, |  | ||||||
|     transport, |  | ||||||
| }; |  | ||||||
| use tokio::time::delay_for; |  | ||||||
| use tokio_serde::formats::Bincode; |  | ||||||
|  |  | ||||||
| #[derive(Clone)] |  | ||||||
| struct BanksServer { |  | ||||||
|     bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|     block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
|     transaction_sender: Sender<TransactionInfo>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl BanksServer { |  | ||||||
|     /// Return a BanksServer that forwards transactions to the |  | ||||||
|     /// given sender. If unit-testing, those transactions can go to |  | ||||||
|     /// a bank in the given BankForks. Otherwise, the receiver should |  | ||||||
|     /// forward them to a validator in the leader schedule. |  | ||||||
|     fn new( |  | ||||||
|         bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|         block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
|         transaction_sender: Sender<TransactionInfo>, |  | ||||||
|     ) -> Self { |  | ||||||
|         Self { |  | ||||||
|             bank_forks, |  | ||||||
|             block_commitment_cache, |  | ||||||
|             transaction_sender, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn run(bank: &Bank, transaction_receiver: Receiver<TransactionInfo>) { |  | ||||||
|         while let Ok(info) = transaction_receiver.recv() { |  | ||||||
|             let mut transaction_infos = vec![info]; |  | ||||||
|             while let Ok(info) = transaction_receiver.try_recv() { |  | ||||||
|                 transaction_infos.push(info); |  | ||||||
|             } |  | ||||||
|             let transactions: Vec<_> = transaction_infos |  | ||||||
|                 .into_iter() |  | ||||||
|                 .map(|info| deserialize(&info.wire_transaction).unwrap()) |  | ||||||
|                 .collect(); |  | ||||||
|             let _ = bank.process_transactions(&transactions); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Useful for unit-testing |  | ||||||
|     fn new_loopback(bank_forks: Arc<RwLock<BankForks>>) -> Self { |  | ||||||
|         let (transaction_sender, transaction_receiver) = channel(); |  | ||||||
|         let bank = bank_forks.read().unwrap().working_bank(); |  | ||||||
|         let slot = bank.slot(); |  | ||||||
|         let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::new( |  | ||||||
|             HashMap::default(), |  | ||||||
|             0, |  | ||||||
|             CommitmentSlots { |  | ||||||
|                 slot, |  | ||||||
|                 root: 0, |  | ||||||
|                 highest_confirmed_slot: 0, |  | ||||||
|                 highest_confirmed_root: 0, |  | ||||||
|             }, |  | ||||||
|         ))); |  | ||||||
|         Builder::new() |  | ||||||
|             .name("solana-bank-forks-client".to_string()) |  | ||||||
|             .spawn(move || Self::run(&bank, transaction_receiver)) |  | ||||||
|             .unwrap(); |  | ||||||
|         Self::new(bank_forks, block_commitment_cache, transaction_sender) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn slot(&self, commitment: CommitmentLevel) -> Slot { |  | ||||||
|         self.block_commitment_cache |  | ||||||
|             .read() |  | ||||||
|             .unwrap() |  | ||||||
|             .slot_with_commitment(commitment) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn bank(&self, commitment: CommitmentLevel) -> Arc<Bank> { |  | ||||||
|         self.bank_forks.read().unwrap()[self.slot(commitment)].clone() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn poll_signature_status( |  | ||||||
|         self, |  | ||||||
|         signature: Signature, |  | ||||||
|         last_valid_slot: Slot, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> Option<transaction::Result<()>> { |  | ||||||
|         let mut status = self.bank(commitment).get_signature_status(&signature); |  | ||||||
|         while status.is_none() { |  | ||||||
|             delay_for(Duration::from_millis(200)).await; |  | ||||||
|             let bank = self.bank(commitment); |  | ||||||
|             if bank.slot() > last_valid_slot { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             status = bank.get_signature_status(&signature); |  | ||||||
|         } |  | ||||||
|         status |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tarpc::server] |  | ||||||
| impl Banks for BanksServer { |  | ||||||
|     async fn send_transaction_with_context(self, _: Context, transaction: Transaction) { |  | ||||||
|         let blockhash = &transaction.message.recent_blockhash; |  | ||||||
|         let last_valid_slot = self |  | ||||||
|             .bank_forks |  | ||||||
|             .read() |  | ||||||
|             .unwrap() |  | ||||||
|             .root_bank() |  | ||||||
|             .get_blockhash_last_valid_slot(&blockhash) |  | ||||||
|             .unwrap(); |  | ||||||
|         let signature = transaction.signatures.get(0).cloned().unwrap_or_default(); |  | ||||||
|         let info = |  | ||||||
|             TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot); |  | ||||||
|         self.transaction_sender.send(info).unwrap(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_fees_with_commitment_and_context( |  | ||||||
|         self, |  | ||||||
|         _: Context, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> (FeeCalculator, Hash, Slot) { |  | ||||||
|         let bank = self.bank(commitment); |  | ||||||
|         let (blockhash, fee_calculator) = bank.last_blockhash_with_fee_calculator(); |  | ||||||
|         let last_valid_slot = bank.get_blockhash_last_valid_slot(&blockhash).unwrap(); |  | ||||||
|         (fee_calculator, blockhash, last_valid_slot) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_transaction_status_with_context( |  | ||||||
|         self, |  | ||||||
|         _: Context, |  | ||||||
|         signature: Signature, |  | ||||||
|     ) -> Option<TransactionStatus> { |  | ||||||
|         let bank = self.bank(CommitmentLevel::Recent); |  | ||||||
|         let (slot, status) = bank.get_signature_status_slot(&signature)?; |  | ||||||
|         let r_block_commitment_cache = self.block_commitment_cache.read().unwrap(); |  | ||||||
|  |  | ||||||
|         let confirmations = if r_block_commitment_cache.root() >= slot { |  | ||||||
|             None |  | ||||||
|         } else { |  | ||||||
|             r_block_commitment_cache |  | ||||||
|                 .get_confirmation_count(slot) |  | ||||||
|                 .or(Some(0)) |  | ||||||
|         }; |  | ||||||
|         Some(TransactionStatus { |  | ||||||
|             slot, |  | ||||||
|             confirmations, |  | ||||||
|             err: status.err(), |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_slot_with_context(self, _: Context, commitment: CommitmentLevel) -> Slot { |  | ||||||
|         self.slot(commitment) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn process_transaction_with_commitment_and_context( |  | ||||||
|         self, |  | ||||||
|         _: Context, |  | ||||||
|         transaction: Transaction, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> Option<transaction::Result<()>> { |  | ||||||
|         let blockhash = &transaction.message.recent_blockhash; |  | ||||||
|         let last_valid_slot = self |  | ||||||
|             .bank_forks |  | ||||||
|             .read() |  | ||||||
|             .unwrap() |  | ||||||
|             .root_bank() |  | ||||||
|             .get_blockhash_last_valid_slot(&blockhash) |  | ||||||
|             .unwrap(); |  | ||||||
|         let signature = transaction.signatures.get(0).cloned().unwrap_or_default(); |  | ||||||
|         let info = |  | ||||||
|             TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot); |  | ||||||
|         self.transaction_sender.send(info).unwrap(); |  | ||||||
|         self.poll_signature_status(signature, last_valid_slot, commitment) |  | ||||||
|             .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get_account_with_commitment_and_context( |  | ||||||
|         self, |  | ||||||
|         _: Context, |  | ||||||
|         address: Pubkey, |  | ||||||
|         commitment: CommitmentLevel, |  | ||||||
|     ) -> Option<Account> { |  | ||||||
|         let bank = self.bank(commitment); |  | ||||||
|         bank.get_account(&address) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn start_local_server( |  | ||||||
|     bank_forks: &Arc<RwLock<BankForks>>, |  | ||||||
| ) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> { |  | ||||||
|     let banks_server = BanksServer::new_loopback(bank_forks.clone()); |  | ||||||
|     let (client_transport, server_transport) = transport::channel::unbounded(); |  | ||||||
|     let server = server::new(server::Config::default()) |  | ||||||
|         .incoming(stream::once(future::ready(server_transport))) |  | ||||||
|         .respond_with(banks_server.serve()); |  | ||||||
|     tokio::spawn(server); |  | ||||||
|     client_transport |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn start_tcp_server( |  | ||||||
|     listen_addr: SocketAddr, |  | ||||||
|     tpu_addr: SocketAddr, |  | ||||||
|     bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|     block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
| ) -> io::Result<()> { |  | ||||||
|     // Note: These settings are copied straight from the tarpc example. |  | ||||||
|     let server = tcp::listen(listen_addr, Bincode::default) |  | ||||||
|         .await? |  | ||||||
|         // Ignore accept errors. |  | ||||||
|         .filter_map(|r| future::ready(r.ok())) |  | ||||||
|         .map(server::BaseChannel::with_defaults) |  | ||||||
|         // Limit channels to 1 per IP. |  | ||||||
|         .max_channels_per_key(1, |t| { |  | ||||||
|             t.as_ref() |  | ||||||
|                 .peer_addr() |  | ||||||
|                 .map(|x| x.ip()) |  | ||||||
|                 .unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0).into()) |  | ||||||
|         }) |  | ||||||
|         // serve is generated by the service attribute. It takes as input any type implementing |  | ||||||
|         // the generated Banks trait. |  | ||||||
|         .map(move |chan| { |  | ||||||
|             let (sender, receiver) = channel(); |  | ||||||
|  |  | ||||||
|             SendTransactionService::new(tpu_addr, &bank_forks, receiver); |  | ||||||
|  |  | ||||||
|             let server = |  | ||||||
|                 BanksServer::new(bank_forks.clone(), block_commitment_cache.clone(), sender); |  | ||||||
|             chan.respond_with(server.serve()).execute() |  | ||||||
|         }) |  | ||||||
|         // Max 10 channels. |  | ||||||
|         .buffer_unordered(10) |  | ||||||
|         .for_each(|_| async {}); |  | ||||||
|  |  | ||||||
|     server.await; |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| pub mod banks_server; |  | ||||||
| pub mod rpc_banks_service; |  | ||||||
| pub mod send_transaction_service; |  | ||||||
|  |  | ||||||
| #[macro_use] |  | ||||||
| extern crate solana_metrics; |  | ||||||
| @@ -1,116 +0,0 @@ | |||||||
| //! The `rpc_banks_service` module implements the Solana Banks RPC API. |  | ||||||
|  |  | ||||||
| use crate::banks_server::start_tcp_server; |  | ||||||
| use futures::{future::FutureExt, pin_mut, prelude::stream::StreamExt, select}; |  | ||||||
| use solana_runtime::{bank_forks::BankForks, commitment::BlockCommitmentCache}; |  | ||||||
| use std::{ |  | ||||||
|     net::SocketAddr, |  | ||||||
|     sync::{ |  | ||||||
|         atomic::{AtomicBool, Ordering}, |  | ||||||
|         Arc, RwLock, |  | ||||||
|     }, |  | ||||||
|     thread::{self, Builder, JoinHandle}, |  | ||||||
| }; |  | ||||||
| use tokio::{ |  | ||||||
|     runtime::Runtime, |  | ||||||
|     time::{self, Duration}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub struct RpcBanksService { |  | ||||||
|     thread_hdl: JoinHandle<()>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Run the TCP service until `exit` is set to true |  | ||||||
| async fn start_abortable_tcp_server( |  | ||||||
|     listen_addr: SocketAddr, |  | ||||||
|     tpu_addr: SocketAddr, |  | ||||||
|     bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|     block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
|     exit: Arc<AtomicBool>, |  | ||||||
| ) { |  | ||||||
|     let server = start_tcp_server( |  | ||||||
|         listen_addr, |  | ||||||
|         tpu_addr, |  | ||||||
|         bank_forks.clone(), |  | ||||||
|         block_commitment_cache.clone(), |  | ||||||
|     ) |  | ||||||
|     .fuse(); |  | ||||||
|     let interval = time::interval(Duration::from_millis(100)).fuse(); |  | ||||||
|     pin_mut!(server, interval); |  | ||||||
|     loop { |  | ||||||
|         select! { |  | ||||||
|             _ = server => {}, |  | ||||||
|             _ = interval.select_next_some() => { |  | ||||||
|                 if exit.load(Ordering::Relaxed) { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl RpcBanksService { |  | ||||||
|     fn run( |  | ||||||
|         listen_addr: SocketAddr, |  | ||||||
|         tpu_addr: SocketAddr, |  | ||||||
|         bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|         block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
|         exit: Arc<AtomicBool>, |  | ||||||
|     ) { |  | ||||||
|         let server = start_abortable_tcp_server( |  | ||||||
|             listen_addr, |  | ||||||
|             tpu_addr, |  | ||||||
|             bank_forks, |  | ||||||
|             block_commitment_cache, |  | ||||||
|             exit, |  | ||||||
|         ); |  | ||||||
|         Runtime::new().unwrap().block_on(server); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn new( |  | ||||||
|         listen_addr: SocketAddr, |  | ||||||
|         tpu_addr: SocketAddr, |  | ||||||
|         bank_forks: &Arc<RwLock<BankForks>>, |  | ||||||
|         block_commitment_cache: &Arc<RwLock<BlockCommitmentCache>>, |  | ||||||
|         exit: &Arc<AtomicBool>, |  | ||||||
|     ) -> Self { |  | ||||||
|         let bank_forks = bank_forks.clone(); |  | ||||||
|         let block_commitment_cache = block_commitment_cache.clone(); |  | ||||||
|         let exit = exit.clone(); |  | ||||||
|         let thread_hdl = Builder::new() |  | ||||||
|             .name("solana-rpc-banks".to_string()) |  | ||||||
|             .spawn(move || { |  | ||||||
|                 Self::run( |  | ||||||
|                     listen_addr, |  | ||||||
|                     tpu_addr, |  | ||||||
|                     bank_forks, |  | ||||||
|                     block_commitment_cache, |  | ||||||
|                     exit, |  | ||||||
|                 ) |  | ||||||
|             }) |  | ||||||
|             .unwrap(); |  | ||||||
|  |  | ||||||
|         Self { thread_hdl } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn join(self) -> thread::Result<()> { |  | ||||||
|         self.thread_hdl.join() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_runtime::bank::Bank; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_rpc_banks_server_exit() { |  | ||||||
|         let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::default()))); |  | ||||||
|         let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); |  | ||||||
|         let exit = Arc::new(AtomicBool::new(false)); |  | ||||||
|         let addr = "127.0.0.1:0".parse().unwrap(); |  | ||||||
|         let service = RpcBanksService::new(addr, addr, &bank_forks, &block_commitment_cache, &exit); |  | ||||||
|         exit.store(true, Ordering::Relaxed); |  | ||||||
|         service.join().unwrap(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,343 +0,0 @@ | |||||||
| // TODO: Merge this implementation with the one at `core/src/send_transaction_service.rs` |  | ||||||
| use log::*; |  | ||||||
| use solana_metrics::{datapoint_warn, inc_new_counter_info}; |  | ||||||
| use solana_runtime::{bank::Bank, bank_forks::BankForks}; |  | ||||||
| use solana_sdk::{clock::Slot, signature::Signature}; |  | ||||||
| use std::{ |  | ||||||
|     collections::HashMap, |  | ||||||
|     net::{SocketAddr, UdpSocket}, |  | ||||||
|     sync::{ |  | ||||||
|         mpsc::{Receiver, RecvTimeoutError}, |  | ||||||
|         Arc, RwLock, |  | ||||||
|     }, |  | ||||||
|     thread::{self, Builder, JoinHandle}, |  | ||||||
|     time::{Duration, Instant}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// Maximum size of the transaction queue |  | ||||||
| const MAX_TRANSACTION_QUEUE_SIZE: usize = 10_000; // This seems like a lot but maybe it needs to be bigger one day |  | ||||||
|  |  | ||||||
| pub struct SendTransactionService { |  | ||||||
|     thread: JoinHandle<()>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub struct TransactionInfo { |  | ||||||
|     pub signature: Signature, |  | ||||||
|     pub wire_transaction: Vec<u8>, |  | ||||||
|     pub last_valid_slot: Slot, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl TransactionInfo { |  | ||||||
|     pub fn new(signature: Signature, wire_transaction: Vec<u8>, last_valid_slot: Slot) -> Self { |  | ||||||
|         Self { |  | ||||||
|             signature, |  | ||||||
|             wire_transaction, |  | ||||||
|             last_valid_slot, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Default, Debug, PartialEq)] |  | ||||||
| struct ProcessTransactionsResult { |  | ||||||
|     rooted: u64, |  | ||||||
|     expired: u64, |  | ||||||
|     retried: u64, |  | ||||||
|     failed: u64, |  | ||||||
|     retained: u64, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl SendTransactionService { |  | ||||||
|     pub fn new( |  | ||||||
|         tpu_address: SocketAddr, |  | ||||||
|         bank_forks: &Arc<RwLock<BankForks>>, |  | ||||||
|         receiver: Receiver<TransactionInfo>, |  | ||||||
|     ) -> Self { |  | ||||||
|         let thread = Self::retry_thread(receiver, bank_forks.clone(), tpu_address); |  | ||||||
|         Self { thread } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn retry_thread( |  | ||||||
|         receiver: Receiver<TransactionInfo>, |  | ||||||
|         bank_forks: Arc<RwLock<BankForks>>, |  | ||||||
|         tpu_address: SocketAddr, |  | ||||||
|     ) -> JoinHandle<()> { |  | ||||||
|         let mut last_status_check = Instant::now(); |  | ||||||
|         let mut transactions = HashMap::new(); |  | ||||||
|         let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); |  | ||||||
|  |  | ||||||
|         Builder::new() |  | ||||||
|             .name("send-tx-svc".to_string()) |  | ||||||
|             .spawn(move || loop { |  | ||||||
|                 match receiver.recv_timeout(Duration::from_secs(1)) { |  | ||||||
|                     Err(RecvTimeoutError::Disconnected) => break, |  | ||||||
|                     Err(RecvTimeoutError::Timeout) => {} |  | ||||||
|                     Ok(transaction_info) => { |  | ||||||
|                         Self::send_transaction( |  | ||||||
|                             &send_socket, |  | ||||||
|                             &tpu_address, |  | ||||||
|                             &transaction_info.wire_transaction, |  | ||||||
|                         ); |  | ||||||
|                         if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE { |  | ||||||
|                             transactions.insert(transaction_info.signature, transaction_info); |  | ||||||
|                         } else { |  | ||||||
|                             datapoint_warn!("send_transaction_service-queue-overflow"); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if Instant::now().duration_since(last_status_check).as_secs() >= 5 { |  | ||||||
|                     if !transactions.is_empty() { |  | ||||||
|                         datapoint_info!( |  | ||||||
|                             "send_transaction_service-queue-size", |  | ||||||
|                             ("len", transactions.len(), i64) |  | ||||||
|                         ); |  | ||||||
|                         let bank_forks = bank_forks.read().unwrap(); |  | ||||||
|                         let root_bank = bank_forks.root_bank(); |  | ||||||
|                         let working_bank = bank_forks.working_bank(); |  | ||||||
|  |  | ||||||
|                         let _result = Self::process_transactions( |  | ||||||
|                             &working_bank, |  | ||||||
|                             &root_bank, |  | ||||||
|                             &send_socket, |  | ||||||
|                             &tpu_address, |  | ||||||
|                             &mut transactions, |  | ||||||
|                         ); |  | ||||||
|                     } |  | ||||||
|                     last_status_check = Instant::now(); |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|             .unwrap() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn process_transactions( |  | ||||||
|         working_bank: &Arc<Bank>, |  | ||||||
|         root_bank: &Arc<Bank>, |  | ||||||
|         send_socket: &UdpSocket, |  | ||||||
|         tpu_address: &SocketAddr, |  | ||||||
|         transactions: &mut HashMap<Signature, TransactionInfo>, |  | ||||||
|     ) -> ProcessTransactionsResult { |  | ||||||
|         let mut result = ProcessTransactionsResult::default(); |  | ||||||
|  |  | ||||||
|         transactions.retain(|signature, transaction_info| { |  | ||||||
|             if root_bank.has_signature(signature) { |  | ||||||
|                 info!("Transaction is rooted: {}", signature); |  | ||||||
|                 result.rooted += 1; |  | ||||||
|                 inc_new_counter_info!("send_transaction_service-rooted", 1); |  | ||||||
|                 false |  | ||||||
|             } else if transaction_info.last_valid_slot < root_bank.slot() { |  | ||||||
|                 info!("Dropping expired transaction: {}", signature); |  | ||||||
|                 result.expired += 1; |  | ||||||
|                 inc_new_counter_info!("send_transaction_service-expired", 1); |  | ||||||
|                 false |  | ||||||
|             } else { |  | ||||||
|                 match working_bank.get_signature_status_slot(signature) { |  | ||||||
|                     None => { |  | ||||||
|                         // Transaction is unknown to the working bank, it might have been |  | ||||||
|                         // dropped or landed in another fork.  Re-send it |  | ||||||
|                         info!("Retrying transaction: {}", signature); |  | ||||||
|                         result.retried += 1; |  | ||||||
|                         inc_new_counter_info!("send_transaction_service-retry", 1); |  | ||||||
|                         Self::send_transaction( |  | ||||||
|                             &send_socket, |  | ||||||
|                             &tpu_address, |  | ||||||
|                             &transaction_info.wire_transaction, |  | ||||||
|                         ); |  | ||||||
|                         true |  | ||||||
|                     } |  | ||||||
|                     Some((_slot, status)) => { |  | ||||||
|                         if status.is_err() { |  | ||||||
|                             info!("Dropping failed transaction: {}", signature); |  | ||||||
|                             result.failed += 1; |  | ||||||
|                             inc_new_counter_info!("send_transaction_service-failed", 1); |  | ||||||
|                             false |  | ||||||
|                         } else { |  | ||||||
|                             result.retained += 1; |  | ||||||
|                             true |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         result |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn send_transaction( |  | ||||||
|         send_socket: &UdpSocket, |  | ||||||
|         tpu_address: &SocketAddr, |  | ||||||
|         wire_transaction: &[u8], |  | ||||||
|     ) { |  | ||||||
|         if let Err(err) = send_socket.send_to(wire_transaction, tpu_address) { |  | ||||||
|             warn!("Failed to send transaction to {}: {:?}", tpu_address, err); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn join(self) -> thread::Result<()> { |  | ||||||
|         self.thread.join() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::{ |  | ||||||
|         genesis_config::create_genesis_config, pubkey::Pubkey, signature::Signer, |  | ||||||
|         system_transaction, |  | ||||||
|     }; |  | ||||||
|     use std::sync::mpsc::channel; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn service_exit() { |  | ||||||
|         let tpu_address = "127.0.0.1:0".parse().unwrap(); |  | ||||||
|         let bank = Bank::default(); |  | ||||||
|         let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); |  | ||||||
|         let (sender, receiver) = channel(); |  | ||||||
|  |  | ||||||
|         let send_tranaction_service = |  | ||||||
|             SendTransactionService::new(tpu_address, &bank_forks, receiver); |  | ||||||
|  |  | ||||||
|         drop(sender); |  | ||||||
|         send_tranaction_service.join().unwrap(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn process_transactions() { |  | ||||||
|         let (genesis_config, mint_keypair) = create_genesis_config(4); |  | ||||||
|         let bank = Bank::new(&genesis_config); |  | ||||||
|         let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); |  | ||||||
|         let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); |  | ||||||
|         let tpu_address = "127.0.0.1:0".parse().unwrap(); |  | ||||||
|  |  | ||||||
|         let root_bank = Arc::new(Bank::new_from_parent( |  | ||||||
|             &bank_forks.read().unwrap().working_bank(), |  | ||||||
|             &Pubkey::default(), |  | ||||||
|             1, |  | ||||||
|         )); |  | ||||||
|         let rooted_signature = root_bank |  | ||||||
|             .transfer(1, &mint_keypair, &mint_keypair.pubkey()) |  | ||||||
|             .unwrap(); |  | ||||||
|  |  | ||||||
|         let working_bank = Arc::new(Bank::new_from_parent(&root_bank, &Pubkey::default(), 2)); |  | ||||||
|  |  | ||||||
|         let non_rooted_signature = working_bank |  | ||||||
|             .transfer(2, &mint_keypair, &mint_keypair.pubkey()) |  | ||||||
|             .unwrap(); |  | ||||||
|  |  | ||||||
|         let failed_signature = { |  | ||||||
|             let blockhash = working_bank.last_blockhash(); |  | ||||||
|             let transaction = |  | ||||||
|                 system_transaction::transfer(&mint_keypair, &Pubkey::default(), 1, blockhash); |  | ||||||
|             let signature = transaction.signatures[0]; |  | ||||||
|             working_bank.process_transaction(&transaction).unwrap_err(); |  | ||||||
|             signature |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         let mut transactions = HashMap::new(); |  | ||||||
|  |  | ||||||
|         info!("Expired transactions are dropped.."); |  | ||||||
|         transactions.insert( |  | ||||||
|             Signature::default(), |  | ||||||
|             TransactionInfo::new(Signature::default(), vec![], root_bank.slot() - 1), |  | ||||||
|         ); |  | ||||||
|         let result = SendTransactionService::process_transactions( |  | ||||||
|             &working_bank, |  | ||||||
|             &root_bank, |  | ||||||
|             &send_socket, |  | ||||||
|             &tpu_address, |  | ||||||
|             &mut transactions, |  | ||||||
|         ); |  | ||||||
|         assert!(transactions.is_empty()); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             ProcessTransactionsResult { |  | ||||||
|                 expired: 1, |  | ||||||
|                 ..ProcessTransactionsResult::default() |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         info!("Rooted transactions are dropped..."); |  | ||||||
|         transactions.insert( |  | ||||||
|             rooted_signature, |  | ||||||
|             TransactionInfo::new(rooted_signature, vec![], working_bank.slot()), |  | ||||||
|         ); |  | ||||||
|         let result = SendTransactionService::process_transactions( |  | ||||||
|             &working_bank, |  | ||||||
|             &root_bank, |  | ||||||
|             &send_socket, |  | ||||||
|             &tpu_address, |  | ||||||
|             &mut transactions, |  | ||||||
|         ); |  | ||||||
|         assert!(transactions.is_empty()); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             ProcessTransactionsResult { |  | ||||||
|                 rooted: 1, |  | ||||||
|                 ..ProcessTransactionsResult::default() |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         info!("Failed transactions are dropped..."); |  | ||||||
|         transactions.insert( |  | ||||||
|             failed_signature, |  | ||||||
|             TransactionInfo::new(failed_signature, vec![], working_bank.slot()), |  | ||||||
|         ); |  | ||||||
|         let result = SendTransactionService::process_transactions( |  | ||||||
|             &working_bank, |  | ||||||
|             &root_bank, |  | ||||||
|             &send_socket, |  | ||||||
|             &tpu_address, |  | ||||||
|             &mut transactions, |  | ||||||
|         ); |  | ||||||
|         assert!(transactions.is_empty()); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             ProcessTransactionsResult { |  | ||||||
|                 failed: 1, |  | ||||||
|                 ..ProcessTransactionsResult::default() |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         info!("Non-rooted transactions are kept..."); |  | ||||||
|         transactions.insert( |  | ||||||
|             non_rooted_signature, |  | ||||||
|             TransactionInfo::new(non_rooted_signature, vec![], working_bank.slot()), |  | ||||||
|         ); |  | ||||||
|         let result = SendTransactionService::process_transactions( |  | ||||||
|             &working_bank, |  | ||||||
|             &root_bank, |  | ||||||
|             &send_socket, |  | ||||||
|             &tpu_address, |  | ||||||
|             &mut transactions, |  | ||||||
|         ); |  | ||||||
|         assert_eq!(transactions.len(), 1); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             ProcessTransactionsResult { |  | ||||||
|                 retained: 1, |  | ||||||
|                 ..ProcessTransactionsResult::default() |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|         transactions.clear(); |  | ||||||
|  |  | ||||||
|         info!("Unknown transactions are retried..."); |  | ||||||
|         transactions.insert( |  | ||||||
|             Signature::default(), |  | ||||||
|             TransactionInfo::new(Signature::default(), vec![], working_bank.slot()), |  | ||||||
|         ); |  | ||||||
|         let result = SendTransactionService::process_transactions( |  | ||||||
|             &working_bank, |  | ||||||
|             &root_bank, |  | ||||||
|             &send_socket, |  | ||||||
|             &tpu_address, |  | ||||||
|             &mut transactions, |  | ||||||
|         ); |  | ||||||
|         assert_eq!(transactions.len(), 1); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             ProcessTransactionsResult { |  | ||||||
|                 retried: 1, |  | ||||||
|                 ..ProcessTransactionsResult::default() |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,8 +1,8 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-bench-exchange" | name = "solana-bench-exchange" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| @@ -15,24 +15,24 @@ log = "0.4.8" | |||||||
| num-derive = "0.3" | num-derive = "0.3" | ||||||
| num-traits = "0.2" | num-traits = "0.2" | ||||||
| rand = "0.7.0" | rand = "0.7.0" | ||||||
| rayon = "1.4.0" | rayon = "1.3.0" | ||||||
| serde_json = "1.0.56" | serde_json = "1.0.53" | ||||||
| serde_yaml = "0.8.13" | serde_yaml = "0.8.12" | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } | solana-clap-utils = { path = "../clap-utils", version = "1.2.7" } | ||||||
| solana-core = { path = "../core", version = "1.3.21" } | solana-core = { path = "../core", version = "1.2.7" } | ||||||
| solana-genesis = { path = "../genesis", version = "1.3.21" } | solana-genesis = { path = "../genesis", version = "1.2.7" } | ||||||
| solana-client = { path = "../client", version = "1.3.21" } | solana-client = { path = "../client", version = "1.2.7" } | ||||||
| solana-faucet = { path = "../faucet", version = "1.3.21" } | solana-faucet = { path = "../faucet", version = "1.2.7" } | ||||||
| solana-exchange-program = { path = "../programs/exchange", version = "1.3.21" } | solana-exchange-program = { path = "../programs/exchange", version = "1.2.7" } | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-metrics = { path = "../metrics", version = "1.3.21" } | solana-metrics = { path = "../metrics", version = "1.2.7" } | ||||||
| solana-net-utils = { path = "../net-utils", version = "1.3.21" } | solana-net-utils = { path = "../net-utils", version = "1.2.7" } | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } | solana-runtime = { path = "../runtime", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
| solana-version = { path = "../version", version = "1.3.21" } | solana-version = { path = "../version", version = "1.2.7" } | ||||||
|  |  | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| solana-local-cluster = { path = "../local-cluster", version = "1.3.21" } | solana-local-cluster = { path = "../local-cluster", version = "1.2.7" } | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
| targets = ["x86_64-unknown-linux-gnu"] | targets = ["x86_64-unknown-linux-gnu"] | ||||||
|   | |||||||
| @@ -179,13 +179,19 @@ where | |||||||
|  |  | ||||||
|     info!("Generating {:?} account keys", total_keys); |     info!("Generating {:?} account keys", total_keys); | ||||||
|     let mut account_keypairs = generate_keypairs(total_keys); |     let mut account_keypairs = generate_keypairs(total_keys); | ||||||
|     let src_keypairs: Vec<_> = account_keypairs.drain(0..accounts_in_groups).collect(); |     let src_keypairs: Vec<_> = account_keypairs | ||||||
|  |         .drain(0..accounts_in_groups) | ||||||
|  |         .map(|keypair| keypair) | ||||||
|  |         .collect(); | ||||||
|     let src_pubkeys: Vec<Pubkey> = src_keypairs |     let src_pubkeys: Vec<Pubkey> = src_keypairs | ||||||
|         .iter() |         .iter() | ||||||
|         .map(|keypair| keypair.pubkey()) |         .map(|keypair| keypair.pubkey()) | ||||||
|         .collect(); |         .collect(); | ||||||
|  |  | ||||||
|     let profit_keypairs: Vec<_> = account_keypairs.drain(0..accounts_in_groups).collect(); |     let profit_keypairs: Vec<_> = account_keypairs | ||||||
|  |         .drain(0..accounts_in_groups) | ||||||
|  |         .map(|keypair| keypair) | ||||||
|  |         .collect(); | ||||||
|     let profit_pubkeys: Vec<Pubkey> = profit_keypairs |     let profit_pubkeys: Vec<Pubkey> = profit_keypairs | ||||||
|         .iter() |         .iter() | ||||||
|         .map(|keypair| keypair.pubkey()) |         .map(|keypair| keypair.pubkey()) | ||||||
|   | |||||||
| @@ -1,20 +1,19 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-bench-streamer" | name = "solana-bench-streamer" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| publish = false |  | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| clap = "2.33.1" | clap = "2.33.1" | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } | solana-clap-utils = { path = "../clap-utils", version = "1.2.7" } | ||||||
| solana-streamer = { path = "../streamer", version = "1.3.21" } | solana-streamer = { path = "../streamer", version = "1.2.7" } | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-net-utils = { path = "../net-utils", version = "1.3.21" } | solana-net-utils = { path = "../net-utils", version = "1.2.7" } | ||||||
| solana-version = { path = "../version", version = "1.3.21" } | solana-version = { path = "../version", version = "1.2.7" } | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
| targets = ["x86_64-unknown-linux-gnu"] | targets = ["x86_64-unknown-linux-gnu"] | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> { | |||||||
|         let mut num = 0; |         let mut num = 0; | ||||||
|         for p in &msgs.packets { |         for p in &msgs.packets { | ||||||
|             let a = p.meta.addr(); |             let a = p.meta.addr(); | ||||||
|             assert!(p.meta.size <= PACKET_DATA_SIZE); |             assert!(p.meta.size < PACKET_DATA_SIZE); | ||||||
|             send.send_to(&p.data[..p.meta.size], &a).unwrap(); |             send.send_to(&p.data[..p.meta.size], &a).unwrap(); | ||||||
|             num += 1; |             num += 1; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,37 +1,41 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-bench-tps" | name = "solana-bench-tps" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| publish = false |  | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| bincode = "1.3.1" | bincode = "1.2.1" | ||||||
| clap = "2.33.1" | clap = "2.33.1" | ||||||
| log = "0.4.8" | log = "0.4.8" | ||||||
| rayon = "1.4.0" | rayon = "1.3.0" | ||||||
| serde_json = "1.0.56" | serde_json = "1.0.53" | ||||||
| serde_yaml = "0.8.13" | serde_yaml = "0.8.12" | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } | solana-clap-utils = { path = "../clap-utils", version = "1.2.7" } | ||||||
| solana-core = { path = "../core", version = "1.3.21" } | solana-core = { path = "../core", version = "1.2.7" } | ||||||
| solana-genesis = { path = "../genesis", version = "1.3.21" } | solana-genesis = { path = "../genesis", version = "1.2.7" } | ||||||
| solana-client = { path = "../client", version = "1.3.21" } | solana-client = { path = "../client", version = "1.2.7" } | ||||||
| solana-faucet = { path = "../faucet", version = "1.3.21" } | solana-faucet = { path = "../faucet", version = "1.2.7" } | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-librapay = { path = "../programs/librapay", version = "1.2.7", optional = true } | ||||||
| solana-metrics = { path = "../metrics", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-measure = { path = "../measure", version = "1.3.21" } | solana-metrics = { path = "../metrics", version = "1.2.7" } | ||||||
| solana-net-utils = { path = "../net-utils", version = "1.3.21" } | solana-measure = { path = "../measure", version = "1.2.7" } | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } | solana-net-utils = { path = "../net-utils", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-runtime = { path = "../runtime", version = "1.2.7" } | ||||||
| solana-version = { path = "../version", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
|  | solana-move-loader-program = { path = "../programs/move_loader", version = "1.2.7", optional = true } | ||||||
|  | solana-version = { path = "../version", version = "1.2.7" } | ||||||
|  |  | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| serial_test = "0.4.0" | serial_test = "0.4.0" | ||||||
| serial_test_derive = "0.4.0" | serial_test_derive = "0.4.0" | ||||||
| solana-local-cluster = { path = "../local-cluster", version = "1.3.21" } | solana-local-cluster = { path = "../local-cluster", version = "1.2.7" } | ||||||
|  |  | ||||||
|  | [features] | ||||||
|  | move = ["solana-librapay", "solana-move-loader-program"] | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
| targets = ["x86_64-unknown-linux-gnu"] | targets = ["x86_64-unknown-linux-gnu"] | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ use rayon::prelude::*; | |||||||
| use solana_client::perf_utils::{sample_txs, SampleStats}; | use solana_client::perf_utils::{sample_txs, SampleStats}; | ||||||
| use solana_core::gen_keys::GenKeys; | use solana_core::gen_keys::GenKeys; | ||||||
| use solana_faucet::faucet::request_airdrop_transaction; | use solana_faucet::faucet::request_airdrop_transaction; | ||||||
|  | #[cfg(feature = "move")] | ||||||
|  | use solana_librapay::{create_genesis, upload_mint_script, upload_payment_script}; | ||||||
| use solana_measure::measure::Measure; | use solana_measure::measure::Measure; | ||||||
| use solana_metrics::{self, datapoint_info}; | use solana_metrics::{self, datapoint_info}; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
| @@ -35,6 +37,9 @@ use std::{ | |||||||
| const MAX_TX_QUEUE_AGE: u64 = | const MAX_TX_QUEUE_AGE: u64 = | ||||||
|     MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND; |     MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND; | ||||||
|  |  | ||||||
|  | #[cfg(feature = "move")] | ||||||
|  | use solana_librapay::librapay_transaction; | ||||||
|  |  | ||||||
| pub const MAX_SPENDS_PER_TX: u64 = 4; | pub const MAX_SPENDS_PER_TX: u64 = 4; | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| @@ -46,6 +51,8 @@ pub type Result<T> = std::result::Result<T, BenchTpsError>; | |||||||
|  |  | ||||||
| pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>; | pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>; | ||||||
|  |  | ||||||
|  | type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>); | ||||||
|  |  | ||||||
| fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) { | fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) { | ||||||
|     loop { |     loop { | ||||||
|         match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) { |         match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) { | ||||||
| @@ -115,6 +122,7 @@ fn generate_chunked_transfers( | |||||||
|     threads: usize, |     threads: usize, | ||||||
|     duration: Duration, |     duration: Duration, | ||||||
|     sustained: bool, |     sustained: bool, | ||||||
|  |     libra_args: Option<LibraKeys>, | ||||||
| ) { | ) { | ||||||
|     // generate and send transactions for the specified duration |     // generate and send transactions for the specified duration | ||||||
|     let start = Instant::now(); |     let start = Instant::now(); | ||||||
| @@ -129,6 +137,7 @@ fn generate_chunked_transfers( | |||||||
|             &dest_keypair_chunks[chunk_index], |             &dest_keypair_chunks[chunk_index], | ||||||
|             threads, |             threads, | ||||||
|             reclaim_lamports_back_to_source_account, |             reclaim_lamports_back_to_source_account, | ||||||
|  |             &libra_args, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         // In sustained mode, overlap the transfers with generation. This has higher average |         // In sustained mode, overlap the transfers with generation. This has higher average | ||||||
| @@ -196,7 +205,12 @@ where | |||||||
|         .collect() |         .collect() | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn do_bench_tps<T>(client: Arc<T>, config: Config, gen_keypairs: Vec<Keypair>) -> u64 | pub fn do_bench_tps<T>( | ||||||
|  |     client: Arc<T>, | ||||||
|  |     config: Config, | ||||||
|  |     gen_keypairs: Vec<Keypair>, | ||||||
|  |     libra_args: Option<LibraKeys>, | ||||||
|  | ) -> u64 | ||||||
| where | where | ||||||
|     T: 'static + Client + Send + Sync, |     T: 'static + Client + Send + Sync, | ||||||
| { | { | ||||||
| @@ -280,6 +294,7 @@ where | |||||||
|         threads, |         threads, | ||||||
|         duration, |         duration, | ||||||
|         sustained, |         sustained, | ||||||
|  |         libra_args, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // Stop the sampling threads so it will collect the stats |     // Stop the sampling threads so it will collect the stats | ||||||
| @@ -325,6 +340,52 @@ fn metrics_submit_lamport_balance(lamport_balance: u64) { | |||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "move")] | ||||||
|  | fn generate_move_txs( | ||||||
|  |     source: &[&Keypair], | ||||||
|  |     dest: &VecDeque<&Keypair>, | ||||||
|  |     reclaim: bool, | ||||||
|  |     move_keypairs: &[Keypair], | ||||||
|  |     libra_pay_program_id: &Pubkey, | ||||||
|  |     libra_mint_id: &Pubkey, | ||||||
|  |     blockhash: &Hash, | ||||||
|  | ) -> Vec<(Transaction, u64)> { | ||||||
|  |     let count = move_keypairs.len() / 2; | ||||||
|  |     let source_move = &move_keypairs[..count]; | ||||||
|  |     let dest_move = &move_keypairs[count..]; | ||||||
|  |     let pairs: Vec<_> = if !reclaim { | ||||||
|  |         source_move | ||||||
|  |             .iter() | ||||||
|  |             .zip(dest_move.iter()) | ||||||
|  |             .zip(source.iter()) | ||||||
|  |             .collect() | ||||||
|  |     } else { | ||||||
|  |         dest_move | ||||||
|  |             .iter() | ||||||
|  |             .zip(source_move.iter()) | ||||||
|  |             .zip(dest.iter()) | ||||||
|  |             .collect() | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     pairs | ||||||
|  |         .par_iter() | ||||||
|  |         .map(|((from, to), payer)| { | ||||||
|  |             ( | ||||||
|  |                 librapay_transaction::transfer( | ||||||
|  |                     libra_pay_program_id, | ||||||
|  |                     libra_mint_id, | ||||||
|  |                     &payer, | ||||||
|  |                     &from, | ||||||
|  |                     &to.pubkey(), | ||||||
|  |                     1, | ||||||
|  |                     *blockhash, | ||||||
|  |                 ), | ||||||
|  |                 timestamp(), | ||||||
|  |             ) | ||||||
|  |         }) | ||||||
|  |         .collect() | ||||||
|  | } | ||||||
|  |  | ||||||
| fn generate_system_txs( | fn generate_system_txs( | ||||||
|     source: &[&Keypair], |     source: &[&Keypair], | ||||||
|     dest: &VecDeque<&Keypair>, |     dest: &VecDeque<&Keypair>, | ||||||
| @@ -355,6 +416,7 @@ fn generate_txs( | |||||||
|     dest: &VecDeque<&Keypair>, |     dest: &VecDeque<&Keypair>, | ||||||
|     threads: usize, |     threads: usize, | ||||||
|     reclaim: bool, |     reclaim: bool, | ||||||
|  |     libra_args: &Option<LibraKeys>, | ||||||
| ) { | ) { | ||||||
|     let blockhash = *blockhash.read().unwrap(); |     let blockhash = *blockhash.read().unwrap(); | ||||||
|     let tx_count = source.len(); |     let tx_count = source.len(); | ||||||
| @@ -364,7 +426,33 @@ fn generate_txs( | |||||||
|     ); |     ); | ||||||
|     let signing_start = Instant::now(); |     let signing_start = Instant::now(); | ||||||
|  |  | ||||||
|     let transactions = generate_system_txs(source, dest, reclaim, &blockhash); |     let transactions = if let Some(( | ||||||
|  |         _libra_genesis_keypair, | ||||||
|  |         _libra_pay_program_id, | ||||||
|  |         _libra_mint_program_id, | ||||||
|  |         _libra_keys, | ||||||
|  |     )) = libra_args | ||||||
|  |     { | ||||||
|  |         #[cfg(not(feature = "move"))] | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[cfg(feature = "move")] | ||||||
|  |         { | ||||||
|  |             generate_move_txs( | ||||||
|  |                 source, | ||||||
|  |                 dest, | ||||||
|  |                 reclaim, | ||||||
|  |                 &_libra_keys, | ||||||
|  |                 _libra_pay_program_id, | ||||||
|  |                 &_libra_genesis_keypair.pubkey(), | ||||||
|  |                 &blockhash, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         generate_system_txs(source, dest, reclaim, &blockhash) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     let duration = signing_start.elapsed(); |     let duration = signing_start.elapsed(); | ||||||
|     let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); |     let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos()); | ||||||
| @@ -866,13 +954,181 @@ pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u | |||||||
|     (rnd.gen_n_keypairs(total_keys), extra) |     (rnd.gen_n_keypairs(total_keys), extra) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "move")] | ||||||
|  | fn fund_move_keys<T: Client>( | ||||||
|  |     client: &T, | ||||||
|  |     funding_key: &Keypair, | ||||||
|  |     keypairs: &[Keypair], | ||||||
|  |     total: u64, | ||||||
|  |     libra_pay_program_id: &Pubkey, | ||||||
|  |     libra_mint_program_id: &Pubkey, | ||||||
|  |     libra_genesis_key: &Keypair, | ||||||
|  | ) { | ||||||
|  |     let (mut blockhash, _fee_calculator) = get_recent_blockhash(client); | ||||||
|  |  | ||||||
|  |     info!("creating the libra funding account.."); | ||||||
|  |     let libra_funding_key = Keypair::new(); | ||||||
|  |     let tx = librapay_transaction::create_account(funding_key, &libra_funding_key, 1, blockhash); | ||||||
|  |     client | ||||||
|  |         .send_and_confirm_message(&[funding_key, &libra_funding_key], tx.message) | ||||||
|  |         .unwrap(); | ||||||
|  |  | ||||||
|  |     info!("minting to funding keypair"); | ||||||
|  |     let tx = librapay_transaction::mint_tokens( | ||||||
|  |         &libra_mint_program_id, | ||||||
|  |         funding_key, | ||||||
|  |         libra_genesis_key, | ||||||
|  |         &libra_funding_key.pubkey(), | ||||||
|  |         total, | ||||||
|  |         blockhash, | ||||||
|  |     ); | ||||||
|  |     client | ||||||
|  |         .send_and_confirm_message(&[funding_key, libra_genesis_key], tx.message) | ||||||
|  |         .unwrap(); | ||||||
|  |  | ||||||
|  |     info!("creating {} move accounts...", keypairs.len()); | ||||||
|  |     let total_len = keypairs.len(); | ||||||
|  |     let create_len = 5; | ||||||
|  |     let mut funding_time = Measure::start("funding_time"); | ||||||
|  |     for (i, keys) in keypairs.chunks(create_len).enumerate() { | ||||||
|  |         if client | ||||||
|  |             .get_balance_with_commitment(&keys[0].pubkey(), CommitmentConfig::recent()) | ||||||
|  |             .unwrap_or(0) | ||||||
|  |             > 0 | ||||||
|  |         { | ||||||
|  |             // already created these accounts. | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let keypairs: Vec<_> = keys.iter().map(|k| k).collect(); | ||||||
|  |         let tx = librapay_transaction::create_accounts(funding_key, &keypairs, 1, blockhash); | ||||||
|  |         let ser_size = bincode::serialized_size(&tx).unwrap(); | ||||||
|  |         let mut keys = vec![funding_key]; | ||||||
|  |         keys.extend(&keypairs); | ||||||
|  |         client.send_and_confirm_message(&keys, tx.message).unwrap(); | ||||||
|  |  | ||||||
|  |         if i % 10 == 0 { | ||||||
|  |             info!( | ||||||
|  |                 "created {} accounts of {} (size {})", | ||||||
|  |                 i, | ||||||
|  |                 total_len / create_len, | ||||||
|  |                 ser_size, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const NUM_FUNDING_KEYS: usize = 10; | ||||||
|  |     let funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect(); | ||||||
|  |     let pubkey_amounts: Vec<_> = funding_keys | ||||||
|  |         .iter() | ||||||
|  |         .map(|key| (key.pubkey(), total / NUM_FUNDING_KEYS as u64)) | ||||||
|  |         .collect(); | ||||||
|  |     let instructions = system_instruction::transfer_many(&funding_key.pubkey(), &pubkey_amounts); | ||||||
|  |     let message = Message::new(&instructions, Some(&funding_key.pubkey())); | ||||||
|  |     let tx = Transaction::new(&[funding_key], message, blockhash); | ||||||
|  |     client | ||||||
|  |         .send_and_confirm_message(&[funding_key], tx.message) | ||||||
|  |         .unwrap(); | ||||||
|  |     let mut balance = 0; | ||||||
|  |     for _ in 0..20 { | ||||||
|  |         if let Ok(balance_) = client | ||||||
|  |             .get_balance_with_commitment(&funding_keys[0].pubkey(), CommitmentConfig::recent()) | ||||||
|  |         { | ||||||
|  |             if balance_ > 0 { | ||||||
|  |                 balance = balance_; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         sleep(Duration::from_millis(100)); | ||||||
|  |     } | ||||||
|  |     assert!(balance > 0); | ||||||
|  |     info!( | ||||||
|  |         "funded multiple funding accounts with {:?} lanports", | ||||||
|  |         balance | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     let libra_funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect(); | ||||||
|  |     for (i, key) in libra_funding_keys.iter().enumerate() { | ||||||
|  |         let tx = librapay_transaction::create_account(&funding_keys[i], &key, 1, blockhash); | ||||||
|  |         client | ||||||
|  |             .send_and_confirm_message(&[&funding_keys[i], &key], tx.message) | ||||||
|  |             .unwrap(); | ||||||
|  |  | ||||||
|  |         let tx = librapay_transaction::transfer( | ||||||
|  |             libra_pay_program_id, | ||||||
|  |             &libra_genesis_key.pubkey(), | ||||||
|  |             &funding_keys[i], | ||||||
|  |             &libra_funding_key, | ||||||
|  |             &key.pubkey(), | ||||||
|  |             total / NUM_FUNDING_KEYS as u64, | ||||||
|  |             blockhash, | ||||||
|  |         ); | ||||||
|  |         client | ||||||
|  |             .send_and_confirm_message(&[&funding_keys[i], &libra_funding_key], tx.message) | ||||||
|  |             .unwrap(); | ||||||
|  |  | ||||||
|  |         info!("funded libra funding key {}", i); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let keypair_count = keypairs.len(); | ||||||
|  |     let amount = total / (keypair_count as u64); | ||||||
|  |     for (i, keys) in keypairs[..keypair_count] | ||||||
|  |         .chunks(NUM_FUNDING_KEYS) | ||||||
|  |         .enumerate() | ||||||
|  |     { | ||||||
|  |         for (j, key) in keys.iter().enumerate() { | ||||||
|  |             let tx = librapay_transaction::transfer( | ||||||
|  |                 libra_pay_program_id, | ||||||
|  |                 &libra_genesis_key.pubkey(), | ||||||
|  |                 &funding_keys[j], | ||||||
|  |                 &libra_funding_keys[j], | ||||||
|  |                 &key.pubkey(), | ||||||
|  |                 amount, | ||||||
|  |                 blockhash, | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             let _sig = client | ||||||
|  |                 .async_send_transaction(tx.clone()) | ||||||
|  |                 .expect("create_account in generate_and_fund_keypairs"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (j, key) in keys.iter().enumerate() { | ||||||
|  |             let mut times = 0; | ||||||
|  |             loop { | ||||||
|  |                 let balance = | ||||||
|  |                     librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap(); | ||||||
|  |                 if balance >= amount { | ||||||
|  |                     break; | ||||||
|  |                 } else if times > 20 { | ||||||
|  |                     info!("timed out.. {} key: {} balance: {}", i, j, balance); | ||||||
|  |                     break; | ||||||
|  |                 } else { | ||||||
|  |                     times += 1; | ||||||
|  |                     sleep(Duration::from_millis(100)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         info!( | ||||||
|  |             "funded group {} of {}", | ||||||
|  |             i + 1, | ||||||
|  |             keypairs.len() / NUM_FUNDING_KEYS | ||||||
|  |         ); | ||||||
|  |         blockhash = get_recent_blockhash(client).0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     funding_time.stop(); | ||||||
|  |     info!("done funding keys, took {} ms", funding_time.as_ms()); | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | ||||||
|     client: Arc<T>, |     client: Arc<T>, | ||||||
|     faucet_addr: Option<SocketAddr>, |     faucet_addr: Option<SocketAddr>, | ||||||
|     funding_key: &Keypair, |     funding_key: &Keypair, | ||||||
|     keypair_count: usize, |     keypair_count: usize, | ||||||
|     lamports_per_account: u64, |     lamports_per_account: u64, | ||||||
| ) -> Result<Vec<Keypair>> { |     use_move: bool, | ||||||
|  | ) -> Result<(Vec<Keypair>, Option<LibraKeys>)> { | ||||||
|     info!("Creating {} keypairs...", keypair_count); |     info!("Creating {} keypairs...", keypair_count); | ||||||
|     let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64); |     let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64); | ||||||
|     info!("Get lamports..."); |     info!("Get lamports..."); | ||||||
| @@ -885,6 +1141,12 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | |||||||
|     let last_key = keypairs[keypair_count - 1].pubkey(); |     let last_key = keypairs[keypair_count - 1].pubkey(); | ||||||
|     let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0); |     let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0); | ||||||
|  |  | ||||||
|  |     #[cfg(feature = "move")] | ||||||
|  |     let mut move_keypairs_ret = None; | ||||||
|  |  | ||||||
|  |     #[cfg(not(feature = "move"))] | ||||||
|  |     let move_keypairs_ret = None; | ||||||
|  |  | ||||||
|     // Repeated runs will eat up keypair balances from transaction fees. In order to quickly |     // Repeated runs will eat up keypair balances from transaction fees. In order to quickly | ||||||
|     //   start another bench-tps run without re-funding all of the keypairs, check if the |     //   start another bench-tps run without re-funding all of the keypairs, check if the | ||||||
|     //   keypairs still have at least 80% of the expected funds. That should be enough to |     //   keypairs still have at least 80% of the expected funds. That should be enough to | ||||||
| @@ -895,7 +1157,10 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | |||||||
|         let max_fee = fee_rate_governor.max_lamports_per_signature; |         let max_fee = fee_rate_governor.max_lamports_per_signature; | ||||||
|         let extra_fees = extra * max_fee; |         let extra_fees = extra * max_fee; | ||||||
|         let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair |         let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair | ||||||
|         let total = lamports_per_account * total_keypairs + extra_fees; |         let mut total = lamports_per_account * total_keypairs + extra_fees; | ||||||
|  |         if use_move { | ||||||
|  |             total *= 3; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         let funding_key_balance = client.get_balance(&funding_key.pubkey()).unwrap_or(0); |         let funding_key_balance = client.get_balance(&funding_key.pubkey()).unwrap_or(0); | ||||||
|         info!( |         info!( | ||||||
| @@ -907,6 +1172,40 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | |||||||
|             airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?; |             airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         #[cfg(feature = "move")] | ||||||
|  |         { | ||||||
|  |             if use_move { | ||||||
|  |                 let libra_genesis_keypair = | ||||||
|  |                     create_genesis(&funding_key, client.as_ref(), 10_000_000); | ||||||
|  |                 let libra_mint_program_id = upload_mint_script(&funding_key, client.as_ref()); | ||||||
|  |                 let libra_pay_program_id = upload_payment_script(&funding_key, client.as_ref()); | ||||||
|  |  | ||||||
|  |                 // Generate another set of keypairs for move accounts. | ||||||
|  |                 // Still fund the solana ones which will be used for fees. | ||||||
|  |                 let seed = [0u8; 32]; | ||||||
|  |                 let mut rnd = GenKeys::new(seed); | ||||||
|  |                 let move_keypairs = rnd.gen_n_keypairs(keypair_count as u64); | ||||||
|  |                 fund_move_keys( | ||||||
|  |                     client.as_ref(), | ||||||
|  |                     funding_key, | ||||||
|  |                     &move_keypairs, | ||||||
|  |                     total / 3, | ||||||
|  |                     &libra_pay_program_id, | ||||||
|  |                     &libra_mint_program_id, | ||||||
|  |                     &libra_genesis_keypair, | ||||||
|  |                 ); | ||||||
|  |                 move_keypairs_ret = Some(( | ||||||
|  |                     libra_genesis_keypair, | ||||||
|  |                     libra_pay_program_id, | ||||||
|  |                     libra_mint_program_id, | ||||||
|  |                     move_keypairs, | ||||||
|  |                 )); | ||||||
|  |  | ||||||
|  |                 // Give solana keys 1/3 and move keys 1/3 the lamports. Keep 1/3 for fees. | ||||||
|  |                 total /= 3; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         fund_keys( |         fund_keys( | ||||||
|             client, |             client, | ||||||
|             funding_key, |             funding_key, | ||||||
| @@ -920,7 +1219,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>( | |||||||
|     // 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys. |     // 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys. | ||||||
|     keypairs.truncate(keypair_count); |     keypairs.truncate(keypair_count); | ||||||
|  |  | ||||||
|     Ok(keypairs) |     Ok((keypairs, move_keypairs_ret)) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @@ -944,11 +1243,11 @@ mod tests { | |||||||
|         config.duration = Duration::from_secs(5); |         config.duration = Duration::from_secs(5); | ||||||
|  |  | ||||||
|         let keypair_count = config.tx_count * config.keypair_multiplier; |         let keypair_count = config.tx_count * config.keypair_multiplier; | ||||||
|         let keypairs = |         let (keypairs, _move_keypairs) = | ||||||
|             generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20) |             generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20, false) | ||||||
|                 .unwrap(); |                 .unwrap(); | ||||||
|  |  | ||||||
|         do_bench_tps(client, config, keypairs); |         do_bench_tps(client, config, keypairs, None); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
| @@ -959,8 +1258,9 @@ mod tests { | |||||||
|         let keypair_count = 20; |         let keypair_count = 20; | ||||||
|         let lamports = 20; |         let lamports = 20; | ||||||
|  |  | ||||||
|         let keypairs = |         let (keypairs, _move_keypairs) = | ||||||
|             generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap(); |             generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false) | ||||||
|  |                 .unwrap(); | ||||||
|  |  | ||||||
|         for kp in &keypairs { |         for kp in &keypairs { | ||||||
|             assert_eq!( |             assert_eq!( | ||||||
| @@ -982,8 +1282,9 @@ mod tests { | |||||||
|         let keypair_count = 20; |         let keypair_count = 20; | ||||||
|         let lamports = 20; |         let lamports = 20; | ||||||
|  |  | ||||||
|         let keypairs = |         let (keypairs, _move_keypairs) = | ||||||
|             generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap(); |             generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false) | ||||||
|  |                 .unwrap(); | ||||||
|  |  | ||||||
|         for kp in &keypairs { |         for kp in &keypairs { | ||||||
|             assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports); |             assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports); | ||||||
|   | |||||||
| @@ -1,10 +1,7 @@ | |||||||
| use clap::{crate_description, crate_name, App, Arg, ArgMatches}; | use clap::{crate_description, crate_name, App, Arg, ArgMatches}; | ||||||
| use solana_faucet::faucet::FAUCET_PORT; | use solana_faucet::faucet::FAUCET_PORT; | ||||||
| use solana_sdk::fee_calculator::FeeRateGovernor; | use solana_sdk::fee_calculator::FeeRateGovernor; | ||||||
| use solana_sdk::{ | use solana_sdk::signature::{read_keypair_file, Keypair}; | ||||||
|     pubkey::Pubkey, |  | ||||||
|     signature::{read_keypair_file, Keypair}, |  | ||||||
| }; |  | ||||||
| use std::{net::SocketAddr, process::exit, time::Duration}; | use std::{net::SocketAddr, process::exit, time::Duration}; | ||||||
|  |  | ||||||
| const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL; | const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL; | ||||||
| @@ -26,9 +23,9 @@ pub struct Config { | |||||||
|     pub read_from_client_file: bool, |     pub read_from_client_file: bool, | ||||||
|     pub target_lamports_per_signature: u64, |     pub target_lamports_per_signature: u64, | ||||||
|     pub multi_client: bool, |     pub multi_client: bool, | ||||||
|  |     pub use_move: bool, | ||||||
|     pub num_lamports_per_account: u64, |     pub num_lamports_per_account: u64, | ||||||
|     pub target_slots_per_epoch: u64, |     pub target_slots_per_epoch: u64, | ||||||
|     pub target_node: Option<Pubkey>, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Config { | impl Default for Config { | ||||||
| @@ -49,9 +46,9 @@ impl Default for Config { | |||||||
|             read_from_client_file: false, |             read_from_client_file: false, | ||||||
|             target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature, |             target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature, | ||||||
|             multi_client: true, |             multi_client: true, | ||||||
|  |             use_move: false, | ||||||
|             num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT, |             num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT, | ||||||
|             target_slots_per_epoch: 0, |             target_slots_per_epoch: 0, | ||||||
|             target_node: None, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -112,19 +109,16 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> { | |||||||
|                 .long("sustained") |                 .long("sustained") | ||||||
|                 .help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."), |                 .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( | ||||||
|             Arg::with_name("no-multi-client") |             Arg::with_name("no-multi-client") | ||||||
|                 .long("no-multi-client") |                 .long("no-multi-client") | ||||||
|                 .help("Disable multi-client support, only transact with the entrypoint."), |                 .help("Disable multi-client support, only transact with the entrypoint."), | ||||||
|         ) |         ) | ||||||
|         .arg( |  | ||||||
|             Arg::with_name("target_node") |  | ||||||
|                 .long("target-node") |  | ||||||
|                 .requires("no-multi-client") |  | ||||||
|                 .takes_value(true) |  | ||||||
|                 .value_name("PUBKEY") |  | ||||||
|                 .help("Specify an exact node to send transactions to."), |  | ||||||
|         ) |  | ||||||
|         .arg( |         .arg( | ||||||
|             Arg::with_name("tx_count") |             Arg::with_name("tx_count") | ||||||
|                 .long("tx_count") |                 .long("tx_count") | ||||||
| @@ -269,10 +263,8 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config { | |||||||
|         args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports"); |         args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     args.use_move = matches.is_present("use-move"); | ||||||
|     args.multi_client = !matches.is_present("no-multi-client"); |     args.multi_client = !matches.is_present("no-multi-client"); | ||||||
|     args.target_node = matches |  | ||||||
|         .value_of("target_node") |  | ||||||
|         .map(|target_str| target_str.parse().unwrap()); |  | ||||||
|  |  | ||||||
|     if let Some(v) = matches.value_of("num_lamports_per_account") { |     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.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports"); | ||||||
|   | |||||||
| @@ -29,9 +29,9 @@ fn main() { | |||||||
|         write_to_client_file, |         write_to_client_file, | ||||||
|         read_from_client_file, |         read_from_client_file, | ||||||
|         target_lamports_per_signature, |         target_lamports_per_signature, | ||||||
|  |         use_move, | ||||||
|         multi_client, |         multi_client, | ||||||
|         num_lamports_per_account, |         num_lamports_per_account, | ||||||
|         target_node, |  | ||||||
|         .. |         .. | ||||||
|     } = &cli_config; |     } = &cli_config; | ||||||
|  |  | ||||||
| @@ -82,24 +82,11 @@ fn main() { | |||||||
|             exit(1); |             exit(1); | ||||||
|         } |         } | ||||||
|         Arc::new(client) |         Arc::new(client) | ||||||
|     } else if let Some(target_node) = target_node { |  | ||||||
|         info!("Searching for target_node: {:?}", target_node); |  | ||||||
|         let mut target_client = None; |  | ||||||
|         for node in nodes { |  | ||||||
|             if node.id == *target_node { |  | ||||||
|                 target_client = Some(Arc::new(get_client(&[node]))); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         target_client.unwrap_or_else(|| { |  | ||||||
|             eprintln!("Target node {} not found", target_node); |  | ||||||
|             exit(1); |  | ||||||
|         }) |  | ||||||
|     } else { |     } else { | ||||||
|         Arc::new(get_client(&nodes)) |         Arc::new(get_client(&nodes)) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let keypairs = if *read_from_client_file { |     let (keypairs, move_keypairs) = if *read_from_client_file && !use_move { | ||||||
|         let path = Path::new(&client_ids_and_stake_file); |         let path = Path::new(&client_ids_and_stake_file); | ||||||
|         let file = File::open(path).unwrap(); |         let file = File::open(path).unwrap(); | ||||||
|  |  | ||||||
| @@ -128,8 +115,8 @@ fn main() { | |||||||
|         // Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run. |         // 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 |         // This prevents the amount of storage needed for bench-tps accounts from creeping up | ||||||
|         // across multiple runs. |         // across multiple runs. | ||||||
|         keypairs.sort_by_key(|x| x.pubkey().to_string()); |         keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string())); | ||||||
|         keypairs |         (keypairs, None) | ||||||
|     } else { |     } else { | ||||||
|         generate_and_fund_keypairs( |         generate_and_fund_keypairs( | ||||||
|             client.clone(), |             client.clone(), | ||||||
| @@ -137,6 +124,7 @@ fn main() { | |||||||
|             &id, |             &id, | ||||||
|             keypair_count, |             keypair_count, | ||||||
|             *num_lamports_per_account, |             *num_lamports_per_account, | ||||||
|  |             *use_move, | ||||||
|         ) |         ) | ||||||
|         .unwrap_or_else(|e| { |         .unwrap_or_else(|e| { | ||||||
|             eprintln!("Error could not fund keys: {:?}", e); |             eprintln!("Error could not fund keys: {:?}", e); | ||||||
| @@ -144,5 +132,5 @@ fn main() { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     do_bench_tps(client, cli_config, keypairs); |     do_bench_tps(client, cli_config, keypairs, move_keypairs); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,11 +6,17 @@ use solana_core::cluster_info::VALIDATOR_PORT_RANGE; | |||||||
| use solana_core::validator::ValidatorConfig; | use solana_core::validator::ValidatorConfig; | ||||||
| use solana_faucet::faucet::run_local_faucet; | use solana_faucet::faucet::run_local_faucet; | ||||||
| use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster}; | use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster}; | ||||||
|  | #[cfg(feature = "move")] | ||||||
|  | use solana_sdk::move_loader::solana_move_loader_program; | ||||||
| use solana_sdk::signature::{Keypair, Signer}; | use solana_sdk::signature::{Keypair, Signer}; | ||||||
| use std::sync::{mpsc::channel, Arc}; | use std::sync::{mpsc::channel, Arc}; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| fn test_bench_tps_local_cluster(config: Config) { | fn test_bench_tps_local_cluster(config: Config) { | ||||||
|  |     #[cfg(feature = "move")] | ||||||
|  |     let native_instruction_processors = vec![solana_move_loader_program()]; | ||||||
|  |  | ||||||
|  |     #[cfg(not(feature = "move"))] | ||||||
|     let native_instruction_processors = vec![]; |     let native_instruction_processors = vec![]; | ||||||
|  |  | ||||||
|     solana_logger::setup(); |     solana_logger::setup(); | ||||||
| @@ -42,16 +48,17 @@ fn test_bench_tps_local_cluster(config: Config) { | |||||||
|     let lamports_per_account = 100; |     let lamports_per_account = 100; | ||||||
|  |  | ||||||
|     let keypair_count = config.tx_count * config.keypair_multiplier; |     let keypair_count = config.tx_count * config.keypair_multiplier; | ||||||
|     let keypairs = generate_and_fund_keypairs( |     let (keypairs, move_keypairs) = generate_and_fund_keypairs( | ||||||
|         client.clone(), |         client.clone(), | ||||||
|         Some(faucet_addr), |         Some(faucet_addr), | ||||||
|         &config.id, |         &config.id, | ||||||
|         keypair_count, |         keypair_count, | ||||||
|         lamports_per_account, |         lamports_per_account, | ||||||
|  |         config.use_move, | ||||||
|     ) |     ) | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  |  | ||||||
|     let _total = do_bench_tps(client, config, keypairs); |     let _total = do_bench_tps(client, config, keypairs, move_keypairs); | ||||||
|  |  | ||||||
|     #[cfg(not(debug_assertions))] |     #[cfg(not(debug_assertions))] | ||||||
|     assert!(_total > 100); |     assert!(_total > 100); | ||||||
| @@ -66,3 +73,14 @@ fn test_bench_tps_local_cluster_solana() { | |||||||
|  |  | ||||||
|     test_bench_tps_local_cluster(config); |     test_bench_tps_local_cluster(config); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  | fn test_bench_tps_local_cluster_move() { | ||||||
|  |     let mut config = Config::default(); | ||||||
|  |     config.tx_count = 100; | ||||||
|  |     config.duration = Duration::from_secs(10); | ||||||
|  |     config.use_move = true; | ||||||
|  |  | ||||||
|  |     test_bench_tps_local_cluster(config); | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								cargo
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								cargo
									
									
									
									
									
								
							| @@ -1,31 +0,0 @@ | |||||||
| #!/usr/bin/env bash |  | ||||||
|  |  | ||||||
| # shellcheck source=ci/rust-version.sh |  | ||||||
| here=$(dirname "$0") |  | ||||||
|  |  | ||||||
| source "${here}"/ci/rust-version.sh all |  | ||||||
|  |  | ||||||
| toolchain= |  | ||||||
| case "$1" in |  | ||||||
|   stable) |  | ||||||
|     # shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh |  | ||||||
|     toolchain="$rust_stable" |  | ||||||
|     shift |  | ||||||
|     ;; |  | ||||||
|   nightly) |  | ||||||
|     # shellcheck disable=SC2054 # rust_nightly is sourced from rust-version.sh |  | ||||||
|     toolchain="$rust_nightly" |  | ||||||
|     shift |  | ||||||
|     ;; |  | ||||||
|   +*) |  | ||||||
|     toolchain="${1#+}" |  | ||||||
|     shift |  | ||||||
|     ;; |  | ||||||
|   *) |  | ||||||
|     # shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh |  | ||||||
|     toolchain="$rust_stable" |  | ||||||
|     ;; |  | ||||||
| esac |  | ||||||
|  |  | ||||||
| set -x |  | ||||||
| exec cargo "+${toolchain}" "${@}" |  | ||||||
							
								
								
									
										43
									
								
								ci/affects-files.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								ci/affects-files.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | # | ||||||
|  | # Checks if a CI build affects one or more path patterns.  Each command-line | ||||||
|  | # argument is checked in series. | ||||||
|  | # | ||||||
|  | # Bash regular expressions are permitted in the pattern: | ||||||
|  | #     ./affects-files.sh .rs$    -- any file or directory ending in .rs | ||||||
|  | #     ./affects-files.sh .rs     -- also matches foo.rs.bar | ||||||
|  | #     ./affects-files.sh ^snap/  -- anything under the snap/ subdirectory | ||||||
|  | #     ./affects-files.sh snap/   -- also matches foo/snap/ | ||||||
|  | # Any pattern starting with the ! character will be negated: | ||||||
|  | #     ./affects-files.sh !^docs/  -- anything *not* under the docs/ subdirectory | ||||||
|  | # | ||||||
|  | set -e | ||||||
|  | cd "$(dirname "$0")"/.. | ||||||
|  |  | ||||||
|  | if [[ -n $CI_PULL_REQUEST ]]; then | ||||||
|  |   affectedFiles="$(buildkite-agent meta-data get affected_files)" | ||||||
|  |   echo "Affected files in this PR: $affectedFiles" | ||||||
|  |  | ||||||
|  |   IFS=':' read -ra files <<< "$affectedFiles" | ||||||
|  |   for pattern in "$@"; do | ||||||
|  |     if [[ ${pattern:0:1} = "!" ]]; then | ||||||
|  |       for file in "${files[@]}"; do | ||||||
|  |         if [[ ! $file =~ ${pattern:1} ]]; then | ||||||
|  |           exit 0 | ||||||
|  |         fi | ||||||
|  |       done | ||||||
|  |     else | ||||||
|  |       for file in "${files[@]}"; do | ||||||
|  |         if [[ $file =~ $pattern ]]; then | ||||||
|  |           exit 0 | ||||||
|  |         fi | ||||||
|  |       done | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  |  | ||||||
|  |   exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # affected_files metadata is not currently available for non-PR builds, so assume | ||||||
|  | # the worse (affected) | ||||||
|  | exit 0 | ||||||
| @@ -207,11 +207,16 @@ pull_or_push_steps() { | |||||||
|  |  | ||||||
|   # Run the full test suite by default, skipping only if modifications are local |   # Run the full test suite by default, skipping only if modifications are local | ||||||
|   # to some particular areas of the tree |   # to some particular areas of the tree | ||||||
|   if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then |   if affects_other_than ^.buildkite ^.travis .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then | ||||||
|     all_test_steps |     all_test_steps | ||||||
|   fi |   fi | ||||||
|  |  | ||||||
|   # web3.js, explorer and docs changes run on Travis... |   # doc/ changes: | ||||||
|  |   if affects ^docs/; then | ||||||
|  |     command_step docs ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image docs/build.sh" 5 | ||||||
|  |   fi | ||||||
|  |  | ||||||
|  |   # web3.js and explorer changes run on Travis... | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								ci/buildkite-release.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								ci/buildkite-release.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # Build steps that run on a release tag | ||||||
|  | # | ||||||
|  | # All the steps in `buildkite.yml` are skipped and we jump directly to the | ||||||
|  | # secondary build steps since it's assumed the commit that was tagged is known | ||||||
|  | # to be good so there's no need to rebuild and retest it. | ||||||
|  | steps: | ||||||
|  |   - trigger: "solana-secondary" | ||||||
|  |     branches: "!pull/*" | ||||||
|  |     async: true | ||||||
|  |     build: | ||||||
|  |       message: "${BUILDKITE_MESSAGE}" | ||||||
|  |       commit: "${BUILDKITE_COMMIT}" | ||||||
|  |       branch: "${BUILDKITE_BRANCH}" | ||||||
|  |       env: | ||||||
|  |         TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}" | ||||||
| @@ -5,6 +5,9 @@ steps: | |||||||
|   - command: "ci/publish-tarball.sh" |   - command: "ci/publish-tarball.sh" | ||||||
|     timeout_in_minutes: 60 |     timeout_in_minutes: 60 | ||||||
|     name: "publish tarball" |     name: "publish tarball" | ||||||
|  |   - command: "ci/publish-docs.sh" | ||||||
|  |     timeout_in_minutes: 15 | ||||||
|  |     name: "publish docs" | ||||||
|   - command: "ci/publish-bpf-sdk.sh" |   - command: "ci/publish-bpf-sdk.sh" | ||||||
|     timeout_in_minutes: 5 |     timeout_in_minutes: 5 | ||||||
|     name: "publish bpf sdk" |     name: "publish bpf sdk" | ||||||
| @@ -16,3 +19,6 @@ steps: | |||||||
|     timeout_in_minutes: 240 |     timeout_in_minutes: 240 | ||||||
|     name: "publish crate" |     name: "publish crate" | ||||||
|     branches: "!master" |     branches: "!master" | ||||||
|  |     #  - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh" | ||||||
|  |     #    name: "move" | ||||||
|  |     #    timeout_in_minutes: 20 | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								ci/buildkite-tests.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								ci/buildkite-tests.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | # These steps are conditionally triggered by ci/buildkite.yml when files | ||||||
|  | # other than those in docs/ are modified | ||||||
|  |  | ||||||
|  | steps: | ||||||
|  |   - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh" | ||||||
|  |     name: "coverage" | ||||||
|  |     timeout_in_minutes: 30 | ||||||
|  |   - wait | ||||||
|  |   - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh" | ||||||
|  |     name: "stable" | ||||||
|  |     timeout_in_minutes: 60 | ||||||
|  |     artifact_paths: "log-*.txt" | ||||||
|  |   - wait | ||||||
|  |   - command: "ci/test-stable-perf.sh" | ||||||
|  |     name: "stable-perf" | ||||||
|  |     timeout_in_minutes: 40 | ||||||
|  |     artifact_paths: "log-*.txt" | ||||||
|  |     agents: | ||||||
|  |       - "queue=cuda" | ||||||
|  |   - command: "ci/test-bench.sh" | ||||||
|  |     name: "bench" | ||||||
|  |     timeout_in_minutes: 30 | ||||||
|  |   - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh" | ||||||
|  |     name: "local-cluster" | ||||||
|  |     timeout_in_minutes: 45 | ||||||
|  |     artifact_paths: "log-*.txt" | ||||||
							
								
								
									
										41
									
								
								ci/buildkite.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								ci/buildkite.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | # Build steps that run on pushes and pull requests. | ||||||
|  | # If files other than those in docs/ were modified, this will be followed up by | ||||||
|  | # ci/buildkite-tests.yml | ||||||
|  | # | ||||||
|  | # Release tags use buildkite-release.yml instead | ||||||
|  |  | ||||||
|  | steps: | ||||||
|  |   - command: "ci/test-sanity.sh" | ||||||
|  |     name: "sanity" | ||||||
|  |     timeout_in_minutes: 5 | ||||||
|  |   - command: "ci/dependabot-pr.sh" | ||||||
|  |     name: "dependabot" | ||||||
|  |     timeout_in_minutes: 5 | ||||||
|  |     if: build.env("GITHUB_USER") == "dependabot-preview[bot]" | ||||||
|  |  | ||||||
|  |   - wait | ||||||
|  |  | ||||||
|  |   - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh" | ||||||
|  |     name: "checks" | ||||||
|  |     timeout_in_minutes: 20 | ||||||
|  |   - command: "ci/shellcheck.sh" | ||||||
|  |     name: "shellcheck" | ||||||
|  |     timeout_in_minutes: 5 | ||||||
|  |  | ||||||
|  |   - wait | ||||||
|  |  | ||||||
|  |   - command: "ci/maybe-trigger-tests.sh" | ||||||
|  |     name: "maybe-trigger-tests" | ||||||
|  |     timeout_in_minutes: 2 | ||||||
|  |  | ||||||
|  |   - wait | ||||||
|  |  | ||||||
|  |   - trigger: "solana-secondary" | ||||||
|  |     branches: "!pull/*" | ||||||
|  |     async: true | ||||||
|  |     build: | ||||||
|  |       message: "${BUILDKITE_MESSAGE}" | ||||||
|  |       commit: "${BUILDKITE_COMMIT}" | ||||||
|  |       branch: "${BUILDKITE_BRANCH}" | ||||||
|  |       env: | ||||||
|  |         TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}" | ||||||
| @@ -89,17 +89,11 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag} | |||||||
| STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag} | STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag} | ||||||
|  |  | ||||||
|  |  | ||||||
| if [[ -n $CI_BASE_BRANCH ]]; then | if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then | ||||||
|   BRANCH="$CI_BASE_BRANCH" |  | ||||||
| elif [[ -n $CI_BRANCH ]]; then |  | ||||||
|   BRANCH="$CI_BRANCH" |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if [[ $BRANCH = "$STABLE_CHANNEL" ]]; then |  | ||||||
|   CHANNEL=stable |   CHANNEL=stable | ||||||
| elif [[ $BRANCH = "$EDGE_CHANNEL" ]]; then | elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then | ||||||
|   CHANNEL=edge |   CHANNEL=edge | ||||||
| elif [[ $BRANCH = "$BETA_CHANNEL" ]]; then | elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then | ||||||
|   CHANNEL=beta |   CHANNEL=beta | ||||||
| fi | fi | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,14 +7,14 @@ source ci/_ | |||||||
| commit_range="$(git merge-base HEAD origin/master)..HEAD" | commit_range="$(git merge-base HEAD origin/master)..HEAD" | ||||||
| parsed_update_args="$( | parsed_update_args="$( | ||||||
|   git log "$commit_range" --author "dependabot-preview" --oneline -n1 | |   git log "$commit_range" --author "dependabot-preview" --oneline -n1 | | ||||||
|     grep -o '[Bb]ump.*$' | |     grep -o 'Bump.*$' | | ||||||
|     sed -r 's/[Bb]ump ([^ ]+) from ([^ ]+) to ([^ ]+)/-p \1:\2 --precise \3/' |     sed -r 's/Bump ([^ ]+) from ([^ ]+) to ([^ ]+)/-p \1:\2 --precise \3/' | ||||||
| )" | )" | ||||||
| # relaxed_parsed_update_args is temporal measure... | # relaxed_parsed_update_args is temporal measure... | ||||||
| relaxed_parsed_update_args="$( | relaxed_parsed_update_args="$( | ||||||
|   git log "$commit_range" --author "dependabot-preview" --oneline -n1 | |   git log "$commit_range" --author "dependabot-preview" --oneline -n1 | | ||||||
|     grep -o '[Bb]ump.*$' | |     grep -o 'Bump.*$' | | ||||||
|     sed -r 's/[Bb]ump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/' |     sed -r 's/Bump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/' | ||||||
| )" | )" | ||||||
| package=$(echo "$parsed_update_args" | awk '{print $2}' | grep -o "^[^:]*") | package=$(echo "$parsed_update_args" | awk '{print $2}' | grep -o "^[^:]*") | ||||||
| if [[ -n $parsed_update_args ]]; then | if [[ -n $parsed_update_args ]]; then | ||||||
|   | |||||||
| @@ -60,12 +60,6 @@ if [[ -z "$SOLANA_DOCKER_RUN_NOSETUID" ]]; then | |||||||
|   ARGS+=(--user "$(id -u):$(id -g)") |   ARGS+=(--user "$(id -u):$(id -g)") | ||||||
| fi | fi | ||||||
|  |  | ||||||
| if [[ -n $SOLANA_ALLOCATE_TTY ]]; then |  | ||||||
|   # Colored output, progress bar and Ctrl-C: |  | ||||||
|   # https://stackoverflow.com/a/41099052/10242004 |  | ||||||
|   ARGS+=(--interactive --tty) |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # Environment variables to propagate into the container | # Environment variables to propagate into the container | ||||||
| ARGS+=( | ARGS+=( | ||||||
|   --env BUILDKITE |   --env BUILDKITE | ||||||
|   | |||||||
| @@ -1,10 +1,9 @@ | |||||||
| FROM solanalabs/rust:1.45.1 | FROM solanalabs/rust:1.43.0 | ||||||
| ARG date | ARG date | ||||||
|  |  | ||||||
| RUN set -x \ | RUN set -x \ | ||||||
|  && rustup install nightly-$date \ |  && rustup install nightly-$date \ | ||||||
|  && rustup component add clippy --toolchain=nightly-$date \ |  && rustup component add clippy --toolchain=nightly-$date \ | ||||||
|  && rustup component add rustfmt --toolchain=nightly-$date \ |  | ||||||
|  && rustup show \ |  && rustup show \ | ||||||
|  && rustc --version \ |  && rustc --version \ | ||||||
|  && cargo --version \ |  && cargo --version \ | ||||||
|   | |||||||
| @@ -2,27 +2,23 @@ Docker image containing rust nightly and some preinstalled crates used in CI. | |||||||
|  |  | ||||||
| This image may be manually updated by running `CI=true ./build.sh` if you are a member | This image may be manually updated by running `CI=true ./build.sh` if you are a member | ||||||
| of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub | of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub | ||||||
| organization. | organization, but it is also automatically updated periodically by | ||||||
|  | [this automation](https://buildkite.com/solana-labs/solana-ci-docker-rust-nightly). | ||||||
|  |  | ||||||
| ## Moving to a newer nightly | ## Moving to a newer nightly | ||||||
|  |  | ||||||
| NOTE: Follow instructions in docker-rust/README.md before this when updating the stable |  | ||||||
| rust version as well. |  | ||||||
|  |  | ||||||
| We pin the version of nightly (see the `ARG nightly=xyz` line in `Dockerfile`) | We pin the version of nightly (see the `ARG nightly=xyz` line in `Dockerfile`) | ||||||
| to avoid the build breaking at unexpected times, as occasionally nightly will | to avoid the build breaking at unexpected times, as occasionally nightly will | ||||||
| introduce breaking changes. | introduce breaking changes. | ||||||
|  |  | ||||||
| To update the pinned version: | To update the pinned version: | ||||||
| 1. Edit `Dockerfile` to match the desired stable rust version to base on if needed. |  | ||||||
| 1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally, | 1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally, | ||||||
|    or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a |    or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a | ||||||
|    specific YYYY-MM-DD that is desired (default is today's build). |    specific YYYY-MM-DD that is desired (default is today's build). | ||||||
|    Check https://rust-lang.github.io/rustup-components-history/ for build |    Check https://rust-lang.github.io/rustup-components-history/ for build | ||||||
|    status |    status | ||||||
| 1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD` | 1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD` | ||||||
| 1. Run `SOLANA_ALLOCATE_TTY=1 SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-checks.sh` | 1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh` | ||||||
|    and `SOLANA_ALLOCATE_TTY=1 SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh [args]...` |  | ||||||
|    to confirm the new nightly image builds.  Fix any issues as needed |    to confirm the new nightly image builds.  Fix any issues as needed | ||||||
| 1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized. | 1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized. | ||||||
| 1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com. | 1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com. | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| # Note: when the rust version is changed also modify | # Note: when the rust version is changed also modify | ||||||
| # ci/rust-version.sh to pick up the new image tag | # ci/rust-version.sh to pick up the new image tag | ||||||
| FROM rust:1.45.1 | FROM rust:1.43.0 | ||||||
|  |  | ||||||
| # Add Google Protocol Buffers for Libra's metrics library. | # Add Google Protocol Buffers for Libra's metrics library. | ||||||
| ENV PROTOC_VERSION 3.8.0 | ENV PROTOC_VERSION 3.8.0 | ||||||
|   | |||||||
| @@ -1,11 +1,7 @@ | |||||||
| Docker image containing rust and some preinstalled packages used in CI. | Docker image containing rust and some preinstalled packages used in CI. | ||||||
|  |  | ||||||
| NOTE: Recreate rust-nightly docker image after this when updating the stable rust |  | ||||||
| version! Both of docker images must be updated in tandem. |  | ||||||
|  |  | ||||||
| This image manually maintained: | This image manually maintained: | ||||||
| 1. Edit `Dockerfile` to match the desired rust version | 1. Edit `Dockerfile` to match the desired rust version | ||||||
| 1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized. | 2. Run `./build.sh` to publish the new image, if you are a member of the [Solana | ||||||
| 1. Run `./build.sh` to publish the new image, if you are a member of the [Solana |  | ||||||
|    Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization. |    Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ if [[ -n $CI ]]; then | |||||||
|     export CI_BUILD_ID=$TRAVIS_BUILD_ID |     export CI_BUILD_ID=$TRAVIS_BUILD_ID | ||||||
|     export CI_COMMIT=$TRAVIS_COMMIT |     export CI_COMMIT=$TRAVIS_COMMIT | ||||||
|     export CI_JOB_ID=$TRAVIS_JOB_ID |     export CI_JOB_ID=$TRAVIS_JOB_ID | ||||||
|     if [[ $TRAVIS_PULL_REQUEST != false ]]; then |     if $TRAVIS_PULL_REQUEST; then | ||||||
|       export CI_PULL_REQUEST=true |       export CI_PULL_REQUEST=true | ||||||
|     else |     else | ||||||
|       export CI_PULL_REQUEST= |       export CI_PULL_REQUEST= | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								ci/maybe-trigger-tests.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								ci/maybe-trigger-tests.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | set -e | ||||||
|  | cd "$(dirname "$0")/.." | ||||||
|  |  | ||||||
|  | annotate() { | ||||||
|  |   ${BUILDKITE:-false} && { | ||||||
|  |     buildkite-agent annotate "$@" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Skip if only the docs have been modified | ||||||
|  | ci/affects-files.sh \ | ||||||
|  |   \!^docs/ \ | ||||||
|  | || { | ||||||
|  |   annotate --style info \ | ||||||
|  |     "Skipping all further tests as only docs/ files were modified" | ||||||
|  |   exit 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | annotate --style info "Triggering tests" | ||||||
|  | buildkite-agent pipeline upload ci/buildkite-tests.yml | ||||||
| @@ -26,7 +26,6 @@ declare print_free_tree=( | |||||||
|   ':runtime/src/**.rs' |   ':runtime/src/**.rs' | ||||||
|   ':sdk/bpf/rust/rust-utils/**.rs' |   ':sdk/bpf/rust/rust-utils/**.rs' | ||||||
|   ':sdk/**.rs' |   ':sdk/**.rs' | ||||||
|   ':^sdk/src/program_option.rs' |  | ||||||
|   ':programs/**.rs' |   ':programs/**.rs' | ||||||
|   ':^**bin**.rs' |   ':^**bin**.rs' | ||||||
|   ':^**bench**.rs' |   ':^**bench**.rs' | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| #!/usr/bin/env python3 | #!/usr/bin/env python2.7 | ||||||
| # | # | ||||||
| # This script figures the order in which workspace crates must be published to | # This script figures the order in which workspace crates must be published to | ||||||
| # crates.io.  Along the way it also ensures there are no circular dependencies | # crates.io.  Along the way it also ensures there are no circular dependencies | ||||||
| @@ -45,27 +45,21 @@ def get_packages(): | |||||||
|     sorted_dependency_graph = [] |     sorted_dependency_graph = [] | ||||||
|     max_iterations = pow(len(dependency_graph),2) |     max_iterations = pow(len(dependency_graph),2) | ||||||
|     while dependency_graph: |     while dependency_graph: | ||||||
|         deleted_packages = [] |  | ||||||
|         if max_iterations == 0: |         if max_iterations == 0: | ||||||
|             # One day be more helpful and find the actual cycle for the user... |             # One day be more helpful and find the actual cycle for the user... | ||||||
|             sys.exit('Error: Circular dependency suspected between these packages: \n {}\n'.format('\n '.join(dependency_graph.keys()))) |             sys.exit('Error: Circular dependency suspected between these packages: \n {}\n'.format('\n '.join(dependency_graph.keys()))) | ||||||
|  |  | ||||||
|         max_iterations -= 1 |         max_iterations -= 1 | ||||||
|  |  | ||||||
|         for package, dependencies in dependency_graph.items(): |         for package, dependencies in dependency_graph.items(): | ||||||
|             if package in deleted_packages: |  | ||||||
|                 continue |  | ||||||
|             for dependency in dependencies: |             for dependency in dependencies: | ||||||
|                 if dependency in dependency_graph: |                 if dependency in dependency_graph: | ||||||
|                     break |                     break | ||||||
|             else: |             else: | ||||||
|                 deleted_packages.append(package) |                 del dependency_graph[package] | ||||||
|                 sorted_dependency_graph.append((package, manifest_path[package])) |                 sorted_dependency_graph.append((package, manifest_path[package])) | ||||||
|  |  | ||||||
|         dependency_graph = {p: d for p, d in dependency_graph.items() if not p in deleted_packages } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     return sorted_dependency_graph |     return sorted_dependency_graph | ||||||
|  |  | ||||||
| for package, manifest in get_packages(): | for package, manifest in get_packages(): | ||||||
|     print(os.path.relpath(manifest)) |     print os.path.relpath(manifest) | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								ci/publish-docs.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								ci/publish-docs.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | cd "$(dirname "$0")/.." | ||||||
|  |  | ||||||
|  | echo --- build docs | ||||||
|  | ( | ||||||
|  |   set -x | ||||||
|  |   . ci/rust-version.sh stable | ||||||
|  |   ci/docker-run.sh "$rust_stable_docker_image" docs/build.sh | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | echo --- update gitbook-cage | ||||||
|  | if [[ -n $CI_BRANCH ]]; then | ||||||
|  |   ( | ||||||
|  |     # make a local commit for the svgs and generated/updated markdown | ||||||
|  |     set -x | ||||||
|  |     git add -f docs/src | ||||||
|  |     if ! git diff-index --quiet HEAD; then | ||||||
|  |       git config user.email maintainers@solana.com | ||||||
|  |       git config user.name "$(basename "$0")" | ||||||
|  |       git commit -m "gitbook-cage update $(date -Is)" | ||||||
|  |       git push -f git@github.com:solana-labs/solana-gitbook-cage.git HEAD:refs/heads/"$CI_BRANCH" | ||||||
|  |       # pop off the local commit | ||||||
|  |       git reset --hard HEAD~ | ||||||
|  |     fi | ||||||
|  |   ) | ||||||
|  | else | ||||||
|  |   echo CI_BRANCH not set | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
| @@ -45,16 +45,7 @@ linux) | |||||||
|   TARGET=x86_64-unknown-linux-gnu |   TARGET=x86_64-unknown-linux-gnu | ||||||
|   ;; |   ;; | ||||||
| windows) | windows) | ||||||
|   TARGET=x86_64-pc-windows-msvc |   TARGET=x86_64-pc-windows-gnu | ||||||
|   # Enable symlinks used by some build.rs files |  | ||||||
|   # source: https://stackoverflow.com/a/52097145/10242004 |  | ||||||
|   ( |  | ||||||
|     set -x |  | ||||||
|     git --version |  | ||||||
|     git config core.symlinks true |  | ||||||
|     find . -type l -delete |  | ||||||
|     git reset --hard |  | ||||||
|   ) |  | ||||||
|   ;; |   ;; | ||||||
| *) | *) | ||||||
|   echo CI_OS_NAME unset |   echo CI_OS_NAME unset | ||||||
| @@ -62,14 +53,11 @@ windows) | |||||||
|   ;; |   ;; | ||||||
| esac | esac | ||||||
|  |  | ||||||
| RELEASE_BASENAME="${RELEASE_BASENAME:=solana-release}" |  | ||||||
| TARBALL_BASENAME="${TARBALL_BASENAME:="$RELEASE_BASENAME"}" |  | ||||||
|  |  | ||||||
| echo --- Creating release tarball | echo --- Creating release tarball | ||||||
| ( | ( | ||||||
|   set -x |   set -x | ||||||
|   rm -rf "${RELEASE_BASENAME:?}"/ |   rm -rf solana-release/ | ||||||
|   mkdir "${RELEASE_BASENAME}"/ |   mkdir solana-release/ | ||||||
|  |  | ||||||
|   COMMIT="$(git rev-parse HEAD)" |   COMMIT="$(git rev-parse HEAD)" | ||||||
|  |  | ||||||
| @@ -77,34 +65,36 @@ echo --- Creating release tarball | |||||||
|     echo "channel: $CHANNEL_OR_TAG" |     echo "channel: $CHANNEL_OR_TAG" | ||||||
|     echo "commit: $COMMIT" |     echo "commit: $COMMIT" | ||||||
|     echo "target: $TARGET" |     echo "target: $TARGET" | ||||||
|   ) > "${RELEASE_BASENAME}"/version.yml |   ) > solana-release/version.yml | ||||||
|  |  | ||||||
|   # Make CHANNEL available to include in the software version information |   # Make CHANNEL available to include in the software version information | ||||||
|   export CHANNEL |   export CHANNEL | ||||||
|  |  | ||||||
|   source ci/rust-version.sh stable |   source ci/rust-version.sh stable | ||||||
|   scripts/cargo-install-all.sh +"$rust_stable" "${RELEASE_BASENAME}" |   scripts/cargo-install-all.sh +"$rust_stable" solana-release | ||||||
|  |  | ||||||
|   tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}" |   tar cvf solana-release-$TARGET.tar solana-release | ||||||
|   bzip2 "${TARBALL_BASENAME}"-$TARGET.tar |   bzip2 solana-release-$TARGET.tar | ||||||
|   cp "${RELEASE_BASENAME}"/bin/solana-install-init solana-install-init-$TARGET |   cp solana-release/bin/solana-install-init solana-install-init-$TARGET | ||||||
|   cp "${RELEASE_BASENAME}"/version.yml "${TARBALL_BASENAME}"-$TARGET.yml |   cp solana-release/version.yml solana-release-$TARGET.yml | ||||||
| ) | ) | ||||||
|  |  | ||||||
| # Maybe tarballs are platform agnostic, only publish them from the Linux build | # Metrics tarball is platform agnostic, only publish it from Linux | ||||||
| MAYBE_TARBALLS= | MAYBE_TARBALLS= | ||||||
| if [[ "$CI_OS_NAME" = linux ]]; then | if [[ "$CI_OS_NAME" = linux ]]; then | ||||||
|  |   metrics/create-metrics-tarball.sh | ||||||
|   ( |   ( | ||||||
|     set -x |     set -x | ||||||
|     sdk/bpf/scripts/package.sh |     sdk/bpf/scripts/package.sh | ||||||
|     [[ -f bpf-sdk.tar.bz2 ]] |     [[ -f bpf-sdk.tar.bz2 ]] | ||||||
|  |  | ||||||
|   ) |   ) | ||||||
|   MAYBE_TARBALLS="bpf-sdk.tar.bz2" |   MAYBE_TARBALLS="bpf-sdk.tar.bz2 solana-metrics.tar.bz2" | ||||||
| fi | fi | ||||||
|  |  | ||||||
| source ci/upload-ci-artifact.sh | source ci/upload-ci-artifact.sh | ||||||
|  |  | ||||||
| for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do | for file in solana-release-$TARGET.tar.bz2 solana-release-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do | ||||||
|   if [[ -n $DO_NOT_PUBLISH_TAR ]]; then |   if [[ -n $DO_NOT_PUBLISH_TAR ]]; then | ||||||
|     upload-ci-artifact "$file" |     upload-ci-artifact "$file" | ||||||
|     echo "Skipped $file due to DO_NOT_PUBLISH_TAR" |     echo "Skipped $file due to DO_NOT_PUBLISH_TAR" | ||||||
| @@ -124,7 +114,7 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET. | |||||||
|         /usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file" |         /usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file" | ||||||
|  |  | ||||||
|       echo Published to: |       echo Published to: | ||||||
|       $DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/"$file" |       $DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     if [[ -n $TAG ]]; then |     if [[ -n $TAG ]]; then | ||||||
| @@ -147,30 +137,4 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET. | |||||||
|   fi |   fi | ||||||
| done | done | ||||||
|  |  | ||||||
|  |  | ||||||
| # Create install wrapper for release.solana.com |  | ||||||
| if [[ -n $BUILDKITE ]]; then |  | ||||||
|   cat > release.solana.com-install <<EOF |  | ||||||
| SOLANA_RELEASE=$CHANNEL_OR_TAG |  | ||||||
| SOLANA_INSTALL_INIT_ARGS=$CHANNEL_OR_TAG |  | ||||||
| SOLANA_DOWNLOAD_ROOT=http://release.solana.com |  | ||||||
| EOF |  | ||||||
|   cat install/solana-install-init.sh >> release.solana.com-install |  | ||||||
|  |  | ||||||
|   echo --- AWS S3 Store: "install" |  | ||||||
|   ( |  | ||||||
|     set -x |  | ||||||
|     $DRYRUN docker run \ |  | ||||||
|       --rm \ |  | ||||||
|       --env AWS_ACCESS_KEY_ID \ |  | ||||||
|       --env AWS_SECRET_ACCESS_KEY \ |  | ||||||
|       --volume "$PWD:/solana" \ |  | ||||||
|       eremite/aws-cli:2018.12.18 \ |  | ||||||
|       /usr/bin/s3cmd --acl-public put /solana/release.solana.com-install s3://release.solana.com/"$CHANNEL_OR_TAG"/install |  | ||||||
|  |  | ||||||
|     echo Published to: |  | ||||||
|     $DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/install |  | ||||||
|   ) |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| echo --- ok | echo --- ok | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ source multinode-demo/common.sh | |||||||
|  |  | ||||||
| rm -rf config/run/init-completed config/ledger config/snapshot-ledger | rm -rf config/run/init-completed config/ledger config/snapshot-ledger | ||||||
|  |  | ||||||
| timeout 120 ./run.sh & | timeout 15 ./run.sh & | ||||||
| pid=$! | pid=$! | ||||||
|  |  | ||||||
| attempts=20 | attempts=20 | ||||||
|   | |||||||
| @@ -18,13 +18,13 @@ | |||||||
| if [[ -n $RUST_STABLE_VERSION ]]; then | if [[ -n $RUST_STABLE_VERSION ]]; then | ||||||
|   stable_version="$RUST_STABLE_VERSION" |   stable_version="$RUST_STABLE_VERSION" | ||||||
| else | else | ||||||
|   stable_version=1.45.1 |   stable_version=1.43.0 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| if [[ -n $RUST_NIGHTLY_VERSION ]]; then | if [[ -n $RUST_NIGHTLY_VERSION ]]; then | ||||||
|   nightly_version="$RUST_NIGHTLY_VERSION" |   nightly_version="$RUST_NIGHTLY_VERSION" | ||||||
| else | else | ||||||
|   nightly_version=2020-07-27 |   nightly_version=2020-04-23 | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -38,8 +38,7 @@ export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version" | |||||||
|  |  | ||||||
|   rustup_install() { |   rustup_install() { | ||||||
|     declare toolchain=$1 |     declare toolchain=$1 | ||||||
|     if ! cargo +"$toolchain" -V > /dev/null; then |     if ! cargo +"$toolchain" -V; then | ||||||
|       echo "$0: Missing toolchain? Installing...: $toolchain" >&2 |  | ||||||
|       rustup install "$toolchain" |       rustup install "$toolchain" | ||||||
|       cargo +"$toolchain" -V |       cargo +"$toolchain" -V | ||||||
|     fi |     fi | ||||||
| @@ -59,7 +58,7 @@ export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version" | |||||||
|      rustup_install "$rust_nightly" |      rustup_install "$rust_nightly" | ||||||
|     ;; |     ;; | ||||||
|   *) |   *) | ||||||
|     echo "$0: Note: ignoring unknown argument: $1" >&2 |     echo "Note: ignoring unknown argument: $1" | ||||||
|     ;; |     ;; | ||||||
|   esac |   esac | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -27,5 +27,5 @@ Alternatively, you can source it from within a script: | |||||||
|     local PATCH=0   |     local PATCH=0   | ||||||
|     local SPECIAL="" |     local SPECIAL="" | ||||||
|      |      | ||||||
|     semverParseInto "1.2.3" MAJOR MINOR PATCH SPECIAL   |     semverParseInto "1.2.7" MAJOR MINOR PATCH SPECIAL   | ||||||
|     semverParseInto "3.2.1" MAJOR MINOR PATCH SPECIAL   |     semverParseInto "3.2.1" MAJOR MINOR PATCH SPECIAL   | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE | |||||||
| TimeoutStartSec=10 | TimeoutStartSec=10 | ||||||
| TimeoutStopSec=0 | TimeoutStopSec=0 | ||||||
| KillMode=process | KillMode=process | ||||||
| LimitNOFILE=500000 | LimitNOFILE=65536 | ||||||
|  |  | ||||||
| [Install] | [Install] | ||||||
| WantedBy=multi-user.target | WantedBy=multi-user.target | ||||||
|   | |||||||
| @@ -8,5 +8,5 @@ source "$HERE"/utils.sh | |||||||
| ensure_env || exit 1 | ensure_env || exit 1 | ||||||
|  |  | ||||||
| # Allow more files to be opened by a user | # Allow more files to be opened by a user | ||||||
| echo "* - nofile 500000" > /etc/security/limits.d/90-solana-nofiles.conf | sed -i 's/^\(# End of file\)/* soft nofile 65535\n\n\1/' /etc/security/limits.conf | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,25 @@ | |||||||
| set -e | set -e | ||||||
| cd "$(dirname "$0")/.." | cd "$(dirname "$0")/.." | ||||||
|  |  | ||||||
|  | annotate() { | ||||||
|  |   ${BUILDKITE:-false} && { | ||||||
|  |     buildkite-agent annotate "$@" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ci/affects-files.sh \ | ||||||
|  |   .rs$ \ | ||||||
|  |   Cargo.lock$ \ | ||||||
|  |   Cargo.toml$ \ | ||||||
|  |   ^ci/rust-version.sh \ | ||||||
|  |   ^ci/test-bench.sh \ | ||||||
|  | || { | ||||||
|  |   annotate --style info --context test-bench \ | ||||||
|  |     "Bench skipped as no .rs files were modified" | ||||||
|  |   exit 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| source ci/_ | source ci/_ | ||||||
| source ci/upload-ci-artifact.sh | source ci/upload-ci-artifact.sh | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
|  |  | ||||||
| set -e | set -e | ||||||
|  |  | ||||||
| cd "$(dirname "$0")/.." | cd "$(dirname "$0")/.." | ||||||
| @@ -9,73 +8,33 @@ source ci/rust-version.sh stable | |||||||
| source ci/rust-version.sh nightly | source ci/rust-version.sh nightly | ||||||
| eval "$(ci/channel-info.sh)" | eval "$(ci/channel-info.sh)" | ||||||
|  |  | ||||||
| scripts/increment-cargo-version.sh check |  | ||||||
|  |  | ||||||
| echo --- build environment |  | ||||||
| ( |  | ||||||
|   set -x |  | ||||||
|  |  | ||||||
|   rustup run "$rust_stable" rustc --version --verbose |  | ||||||
|   rustup run "$rust_nightly" rustc --version --verbose |  | ||||||
|  |  | ||||||
|   cargo +"$rust_stable" --version --verbose |  | ||||||
|   cargo +"$rust_nightly" --version --verbose |  | ||||||
|  |  | ||||||
|   cargo +"$rust_stable" clippy --version --verbose |  | ||||||
|   cargo +"$rust_nightly" clippy --version --verbose |  | ||||||
|  |  | ||||||
|   # audit is done only with stable |  | ||||||
|   cargo +"$rust_stable" audit --version |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| export RUST_BACKTRACE=1 | export RUST_BACKTRACE=1 | ||||||
| export RUSTFLAGS="-D warnings -A incomplete_features" | export RUSTFLAGS="-D warnings" | ||||||
|  |  | ||||||
|  |  | ||||||
| # Only force up-to-date lock files on edge | # Only force up-to-date lock files on edge | ||||||
| if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then | if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then | ||||||
|   # Exclude --benches as it's not available in rust stable yet |   if _ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets; then | ||||||
|   if _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" check --locked --tests --bins --examples; then |  | ||||||
|     true |     true | ||||||
|   else |   else | ||||||
|     check_status=$? |     check_status=$? | ||||||
|     echo "$0: Some Cargo.lock might be outdated; sync them (or just be a compilation error?)" >&2 |     echo "Some Cargo.lock is outdated; please update them as well" | ||||||
|     echo "$0: protip: $ ./scripts/cargo-for-all-lock-files.sh [--ignore-exit-code] ... \\" >&2 |     echo "protip: you can use ./scripts/cargo-for-all-lock-files.sh update ..." | ||||||
|     echo "$0:   [tree (for outdated Cargo.lock sync)|check (for compilation error)|update -p foo --precise x.y.z (for your Cargo.toml update)] ..." >&2 |  | ||||||
|     exit "$check_status" |     exit "$check_status" | ||||||
|   fi |   fi | ||||||
|  |  | ||||||
|   # Ensure nightly and --benches |  | ||||||
|   _ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets |  | ||||||
|  |  | ||||||
| else | else | ||||||
|   echo "Note: cargo-for-all-lock-files.sh skipped because $CI_BASE_BRANCH != $EDGE_CHANNEL" |   echo "Note: cargo-for-all-lock-files.sh skipped because $CI_BASE_BRANCH != $EDGE_CHANNEL" | ||||||
| fi | fi | ||||||
|  |  | ||||||
| _ ci/order-crates-for-publishing.py |  | ||||||
| _ cargo +"$rust_stable" fmt --all -- --check | _ cargo +"$rust_stable" fmt --all -- --check | ||||||
|  |  | ||||||
| # -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612 | _ cargo +"$rust_stable" clippy --version | ||||||
| # run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there | _ cargo +"$rust_stable" clippy --workspace -- --deny=warnings | ||||||
| _ cargo +"$rust_nightly" clippy -Zunstable-options --workspace --all-targets -- --deny=warnings |  | ||||||
|  |  | ||||||
|  | _ cargo +"$rust_stable" audit --version | ||||||
| cargo_audit_ignores=( | _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008 | ||||||
|   # failure is officially deprecated/unmaintained | _ ci/order-crates-for-publishing.py | ||||||
|   # | _ docs/build.sh | ||||||
|   # Blocked on multiple upstream crates removing their `failure` dependency. |  | ||||||
|   --ignore RUSTSEC-2020-0036 |  | ||||||
|  |  | ||||||
|   # `net2` crate has been deprecated; use `socket2` instead |  | ||||||
|   # |  | ||||||
|   # Blocked on https://github.com/paritytech/jsonrpc/issues/575 |  | ||||||
|   --ignore RUSTSEC-2020-0016 |  | ||||||
|  |  | ||||||
|   # stdweb is unmaintained |  | ||||||
|   # |  | ||||||
|   # Blocked on multiple upstream crates removing their `stdweb` dependency. |  | ||||||
|   --ignore RUSTSEC-2020-0056 |  | ||||||
| ) |  | ||||||
| _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}" |  | ||||||
|  |  | ||||||
| { | { | ||||||
|   cd programs/bpf |   cd programs/bpf | ||||||
| @@ -86,6 +45,7 @@ _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignor | |||||||
|       cd "$project" |       cd "$project" | ||||||
|       _ cargo +"$rust_stable" fmt -- --check |       _ cargo +"$rust_stable" fmt -- --check | ||||||
|       _ cargo +"$rust_nightly" test |       _ cargo +"$rust_nightly" test | ||||||
|  |       _ cargo +"$rust_nightly" clippy --version | ||||||
|       _ cargo +"$rust_nightly" clippy -- --deny=warnings --allow=clippy::missing_safety_doc |       _ cargo +"$rust_nightly" clippy -- --deny=warnings --allow=clippy::missing_safety_doc | ||||||
|     ) |     ) | ||||||
|   done |   done | ||||||
|   | |||||||
| @@ -8,14 +8,23 @@ annotate() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ci/affects-files.sh \ | ||||||
|  |   .rs$ \ | ||||||
|  |   Cargo.lock$ \ | ||||||
|  |   Cargo.toml$ \ | ||||||
|  |   ^ci/rust-version.sh \ | ||||||
|  |   ^ci/test-coverage.sh \ | ||||||
|  |   ^scripts/coverage.sh \ | ||||||
|  | || { | ||||||
|  |   annotate --style info --context test-coverage \ | ||||||
|  |     "Coverage skipped as no .rs files were modified" | ||||||
|  |   exit 0 | ||||||
|  | } | ||||||
|  |  | ||||||
| source ci/upload-ci-artifact.sh | source ci/upload-ci-artifact.sh | ||||||
| source scripts/ulimit-n.sh | source scripts/ulimit-n.sh | ||||||
|  |  | ||||||
| scripts/coverage.sh "$@" | scripts/coverage.sh | ||||||
|  |  | ||||||
| if [[ -z $CI ]]; then |  | ||||||
|   exit |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| report=coverage-"${CI_COMMIT:0:9}".tar.gz | report=coverage-"${CI_COMMIT:0:9}".tar.gz | ||||||
| mv target/cov/report.tar.gz "$report" | mv target/cov/report.tar.gz "$report" | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								ci/test-move.sh
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								ci/test-move.sh
									
									
									
									
									
										Symbolic link
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | test-stable.sh | ||||||
| @@ -13,6 +13,15 @@ annotate() { | |||||||
| # Run the appropriate test based on entrypoint | # Run the appropriate test based on entrypoint | ||||||
| testName=$(basename "$0" .sh) | testName=$(basename "$0" .sh) | ||||||
|  |  | ||||||
|  | # Skip if only the docs have been modified | ||||||
|  | ci/affects-files.sh \ | ||||||
|  |   \!^docs/ \ | ||||||
|  | || { | ||||||
|  |   annotate --style info \ | ||||||
|  |     "Skipped $testName as only docs/ files were modified" | ||||||
|  |   exit 0 | ||||||
|  | } | ||||||
|  |  | ||||||
| source ci/rust-version.sh stable | source ci/rust-version.sh stable | ||||||
|  |  | ||||||
| export RUST_BACKTRACE=1 | export RUST_BACKTRACE=1 | ||||||
| @@ -38,8 +47,27 @@ echo "Executing $testName" | |||||||
| case $testName in | case $testName in | ||||||
| test-stable) | test-stable) | ||||||
|   _ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture |   _ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture | ||||||
|  |   _ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture | ||||||
|   ;; |   ;; | ||||||
| test-stable-perf) | test-stable-perf) | ||||||
|  |   ci/affects-files.sh \ | ||||||
|  |     .rs$ \ | ||||||
|  |     Cargo.lock$ \ | ||||||
|  |     Cargo.toml$ \ | ||||||
|  |     ^ci/rust-version.sh \ | ||||||
|  |     ^ci/test-stable-perf.sh \ | ||||||
|  |     ^ci/test-stable.sh \ | ||||||
|  |     ^ci/test-local-cluster.sh \ | ||||||
|  |     ^core/build.rs \ | ||||||
|  |     ^fetch-perf-libs.sh \ | ||||||
|  |     ^programs/ \ | ||||||
|  |     ^sdk/ \ | ||||||
|  |   || { | ||||||
|  |     annotate --style info \ | ||||||
|  |       "Skipped $testName as no relevant files were modified" | ||||||
|  |     exit 0 | ||||||
|  |   } | ||||||
|  |  | ||||||
|   # BPF program tests |   # BPF program tests | ||||||
|   _ make -C programs/bpf/c tests |   _ make -C programs/bpf/c tests | ||||||
|   _ cargo +"$rust_stable" test \ |   _ cargo +"$rust_stable" test \ | ||||||
| @@ -64,7 +92,27 @@ test-stable-perf) | |||||||
|  |  | ||||||
|   _ cargo +"$rust_stable" build --bins ${V:+--verbose} |   _ cargo +"$rust_stable" build --bins ${V:+--verbose} | ||||||
|   _ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture |   _ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture | ||||||
|   _ cargo +"$rust_stable" run --manifest-path poh-bench/Cargo.toml ${V:+--verbose} -- --hashes-per-tick 10 |   ;; | ||||||
|  | test-move) | ||||||
|  |   ci/affects-files.sh \ | ||||||
|  |     Cargo.lock$ \ | ||||||
|  |     Cargo.toml$ \ | ||||||
|  |     ^ci/rust-version.sh \ | ||||||
|  |     ^ci/test-stable.sh \ | ||||||
|  |     ^ci/test-move.sh \ | ||||||
|  |     ^programs/move_loader \ | ||||||
|  |     ^programs/librapay \ | ||||||
|  |     ^logger/ \ | ||||||
|  |     ^runtime/ \ | ||||||
|  |     ^sdk/ \ | ||||||
|  |   || { | ||||||
|  |     annotate --style info \ | ||||||
|  |       "Skipped $testName as no relevant files were modified" | ||||||
|  |     exit 0 | ||||||
|  |   } | ||||||
|  |   _ cargo +"$rust_stable" test --manifest-path programs/move_loader/Cargo.toml ${V:+--verbose} -- --nocapture | ||||||
|  |   _ cargo +"$rust_stable" test --manifest-path programs/librapay/Cargo.toml ${V:+--verbose} -- --nocapture | ||||||
|  |   exit 0 | ||||||
|   ;; |   ;; | ||||||
| test-local-cluster) | test-local-cluster) | ||||||
|   _ cargo +"$rust_stable" build --release --bins ${V:+--verbose} |   _ cargo +"$rust_stable" build --release --bins ${V:+--verbose} | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| [package] | [package] | ||||||
| name = "solana-clap-utils" | name = "solana-clap-utils" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| description = "Solana utilities for the clap" | description = "Solana utilities for the clap" | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
| @@ -11,9 +11,9 @@ edition = "2018" | |||||||
| [dependencies] | [dependencies] | ||||||
| clap = "2.33.0" | clap = "2.33.0" | ||||||
| rpassword = "4.0" | rpassword = "4.0" | ||||||
| solana-remote-wallet = { path = "../remote-wallet", version = "1.3.21" } | solana-remote-wallet = { path = "../remote-wallet", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
| thiserror = "1.0.20" | thiserror = "1.0.11" | ||||||
| tiny-bip39 = "0.7.0" | tiny-bip39 = "0.7.0" | ||||||
| url = "2.1.0" | url = "2.1.0" | ||||||
| chrono = "0.4" | chrono = "0.4" | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<' | |||||||
|     Arg::with_name(COMMITMENT_ARG.name) |     Arg::with_name(COMMITMENT_ARG.name) | ||||||
|         .long(COMMITMENT_ARG.long) |         .long(COMMITMENT_ARG.long) | ||||||
|         .takes_value(true) |         .takes_value(true) | ||||||
|         .possible_values(&["recent", "single", "singleGossip", "root", "max"]) |         .possible_values(&["recent", "single", "root", "max"]) | ||||||
|         .default_value(default_value) |         .default_value(default_value) | ||||||
|         .value_name("COMMITMENT_LEVEL") |         .value_name("COMMITMENT_LEVEL") | ||||||
|         .help(COMMITMENT_ARG.help) |         .help(COMMITMENT_ARG.help) | ||||||
|   | |||||||
| @@ -1,19 +0,0 @@ | |||||||
| use crate::{input_validators, ArgConstant}; |  | ||||||
| use clap::Arg; |  | ||||||
|  |  | ||||||
| pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant { |  | ||||||
|     name: "fee_payer", |  | ||||||
|     long: "fee-payer", |  | ||||||
|     help: "Specify the fee-payer account. This may be a keypair file, the ASK keyword \n\ |  | ||||||
|            or the pubkey of an offline signer, provided an appropriate --signer argument \n\ |  | ||||||
|            is also passed. Defaults to the client keypair.", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(FEE_PAYER_ARG.name) |  | ||||||
|         .long(FEE_PAYER_ARG.long) |  | ||||||
|         .takes_value(true) |  | ||||||
|         .value_name("KEYPAIR") |  | ||||||
|         .validator(input_validators::is_valid_signer) |  | ||||||
|         .help(FEE_PAYER_ARG.help) |  | ||||||
| } |  | ||||||
| @@ -8,7 +8,6 @@ use solana_remote_wallet::remote_wallet::RemoteWalletManager; | |||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     clock::UnixTimestamp, |     clock::UnixTimestamp, | ||||||
|     commitment_config::CommitmentConfig, |     commitment_config::CommitmentConfig, | ||||||
|     genesis_config::ClusterType, |  | ||||||
|     native_token::sol_to_lamports, |     native_token::sol_to_lamports, | ||||||
|     pubkey::Pubkey, |     pubkey::Pubkey, | ||||||
|     signature::{read_keypair_file, Keypair, Signature, Signer}, |     signature::{read_keypair_file, Keypair, Signature, Signer}, | ||||||
| @@ -179,10 +178,6 @@ pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> { | |||||||
|     value_of(matches, name).map(sol_to_lamports) |     value_of(matches, name).map(sol_to_lamports) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterType> { |  | ||||||
|     value_of(matches, name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> { | pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> { | ||||||
|     matches.value_of(name).map(|value| match value { |     matches.value_of(name).map(|value| match value { | ||||||
|         "max" => CommitmentConfig::max(), |         "max" => CommitmentConfig::max(), | ||||||
| @@ -228,8 +223,8 @@ mod tests { | |||||||
|         assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39])); |         assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39])); | ||||||
|         assert_eq!(values_of::<u64>(&matches, "single"), None); |         assert_eq!(values_of::<u64>(&matches, "single"), None); | ||||||
|  |  | ||||||
|         let pubkey0 = solana_sdk::pubkey::new_rand(); |         let pubkey0 = Pubkey::new_rand(); | ||||||
|         let pubkey1 = solana_sdk::pubkey::new_rand(); |         let pubkey1 = Pubkey::new_rand(); | ||||||
|         let matches = app().clone().get_matches_from(vec![ |         let matches = app().clone().get_matches_from(vec![ | ||||||
|             "test", |             "test", | ||||||
|             "--multiple", |             "--multiple", | ||||||
| @@ -251,7 +246,7 @@ mod tests { | |||||||
|         assert_eq!(value_of(&matches, "single"), Some(50)); |         assert_eq!(value_of(&matches, "single"), Some(50)); | ||||||
|         assert_eq!(value_of::<u64>(&matches, "multiple"), None); |         assert_eq!(value_of::<u64>(&matches, "multiple"), None); | ||||||
|  |  | ||||||
|         let pubkey = solana_sdk::pubkey::new_rand(); |         let pubkey = Pubkey::new_rand(); | ||||||
|         let matches = app() |         let matches = app() | ||||||
|             .clone() |             .clone() | ||||||
|             .get_matches_from(vec!["test", "--single", &pubkey.to_string()]); |             .get_matches_from(vec!["test", "--single", &pubkey.to_string()]); | ||||||
| @@ -331,8 +326,8 @@ mod tests { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_pubkeys_sigs_of() { |     fn test_pubkeys_sigs_of() { | ||||||
|         let key1 = solana_sdk::pubkey::new_rand(); |         let key1 = Pubkey::new_rand(); | ||||||
|         let key2 = solana_sdk::pubkey::new_rand(); |         let key2 = Pubkey::new_rand(); | ||||||
|         let sig1 = Keypair::new().sign_message(&[0u8]); |         let sig1 = Keypair::new().sign_message(&[0u8]); | ||||||
|         let sig2 = Keypair::new().sign_message(&[1u8]); |         let sig2 = Keypair::new().sign_message(&[1u8]); | ||||||
|         let signer1 = format!("{}={}", key1, sig1); |         let signer1 = format!("{}={}", key1, sig1); | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ use solana_remote_wallet::{ | |||||||
|     remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager}, |     remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager}, | ||||||
| }; | }; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     hash::Hash, |  | ||||||
|     pubkey::Pubkey, |     pubkey::Pubkey, | ||||||
|     signature::{ |     signature::{ | ||||||
|         keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair, |         keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair, | ||||||
| @@ -26,81 +25,6 @@ use std::{ | |||||||
|     sync::Arc, |     sync::Arc, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub struct SignOnly { |  | ||||||
|     pub blockhash: Hash, |  | ||||||
|     pub present_signers: Vec<(Pubkey, Signature)>, |  | ||||||
|     pub absent_signers: Vec<Pubkey>, |  | ||||||
|     pub bad_signers: Vec<Pubkey>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl SignOnly { |  | ||||||
|     pub fn has_all_signers(&self) -> bool { |  | ||||||
|         self.absent_signers.is_empty() && self.bad_signers.is_empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> { |  | ||||||
|         presigner_from_pubkey_sigs(pubkey, &self.present_signers) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| pub type CliSigners = Vec<Box<dyn Signer>>; |  | ||||||
| pub type SignerIndex = usize; |  | ||||||
| pub struct CliSignerInfo { |  | ||||||
|     pub signers: CliSigners, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl CliSignerInfo { |  | ||||||
|     pub fn index_of(&self, pubkey: Option<Pubkey>) -> Option<usize> { |  | ||||||
|         if let Some(pubkey) = pubkey { |  | ||||||
|             self.signers |  | ||||||
|                 .iter() |  | ||||||
|                 .position(|signer| signer.pubkey() == pubkey) |  | ||||||
|         } else { |  | ||||||
|             Some(0) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub struct DefaultSigner { |  | ||||||
|     pub arg_name: String, |  | ||||||
|     pub path: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl DefaultSigner { |  | ||||||
|     pub fn generate_unique_signers( |  | ||||||
|         &self, |  | ||||||
|         bulk_signers: Vec<Option<Box<dyn Signer>>>, |  | ||||||
|         matches: &ArgMatches<'_>, |  | ||||||
|         wallet_manager: &mut Option<Arc<RemoteWalletManager>>, |  | ||||||
|     ) -> Result<CliSignerInfo, Box<dyn error::Error>> { |  | ||||||
|         let mut unique_signers = vec![]; |  | ||||||
|  |  | ||||||
|         // Determine if the default signer is needed |  | ||||||
|         if bulk_signers.iter().any(|signer| signer.is_none()) { |  | ||||||
|             let default_signer = self.signer_from_path(matches, wallet_manager)?; |  | ||||||
|             unique_signers.push(default_signer); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for signer in bulk_signers.into_iter() { |  | ||||||
|             if let Some(signer) = signer { |  | ||||||
|                 if !unique_signers.iter().any(|s| s == &signer) { |  | ||||||
|                     unique_signers.push(signer); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Ok(CliSignerInfo { |  | ||||||
|             signers: unique_signers, |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn signer_from_path( |  | ||||||
|         &self, |  | ||||||
|         matches: &ArgMatches, |  | ||||||
|         wallet_manager: &mut Option<Arc<RemoteWalletManager>>, |  | ||||||
|     ) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> { |  | ||||||
|         signer_from_path(matches, &self.path, &self.arg_name, wallet_manager) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub enum KeypairUrl { | pub enum KeypairUrl { | ||||||
|     Ask, |     Ask, | ||||||
|     Filepath(String), |     Filepath(String), | ||||||
| @@ -154,7 +78,7 @@ pub fn signer_from_path( | |||||||
|         KeypairUrl::Filepath(path) => match read_keypair_file(&path) { |         KeypairUrl::Filepath(path) => match read_keypair_file(&path) { | ||||||
|             Err(e) => Err(std::io::Error::new( |             Err(e) => Err(std::io::Error::new( | ||||||
|                 std::io::ErrorKind::Other, |                 std::io::ErrorKind::Other, | ||||||
|                 format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e), |                 format!("could not find keypair file: {} error: {}", path, e), | ||||||
|             ) |             ) | ||||||
|             .into()), |             .into()), | ||||||
|             Ok(file) => Ok(Box::new(file)), |             Ok(file) => Ok(Box::new(file)), | ||||||
| @@ -225,7 +149,7 @@ pub fn resolve_signer_from_path( | |||||||
|         KeypairUrl::Filepath(path) => match read_keypair_file(&path) { |         KeypairUrl::Filepath(path) => match read_keypair_file(&path) { | ||||||
|             Err(e) => Err(std::io::Error::new( |             Err(e) => Err(std::io::Error::new( | ||||||
|                 std::io::ErrorKind::Other, |                 std::io::ErrorKind::Other, | ||||||
|                 format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e), |                 format!("could not find keypair file: {} error: {}", path, e), | ||||||
|             ) |             ) | ||||||
|             .into()), |             .into()), | ||||||
|             Ok(_) => Ok(Some(path.to_string())), |             Ok(_) => Ok(Some(path.to_string())), | ||||||
| @@ -298,24 +222,7 @@ pub fn keypair_from_seed_phrase( | |||||||
|         keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)? |         keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)? | ||||||
|     } else { |     } else { | ||||||
|         let sanitized = sanitize_seed_phrase(seed_phrase); |         let sanitized = sanitize_seed_phrase(seed_phrase); | ||||||
|         let parse_language_fn = || { |         let mnemonic = Mnemonic::from_phrase(&sanitized, Language::English)?; | ||||||
|             for language in &[ |  | ||||||
|                 Language::English, |  | ||||||
|                 Language::ChineseSimplified, |  | ||||||
|                 Language::ChineseTraditional, |  | ||||||
|                 Language::Japanese, |  | ||||||
|                 Language::Spanish, |  | ||||||
|                 Language::Korean, |  | ||||||
|                 Language::French, |  | ||||||
|                 Language::Italian, |  | ||||||
|             ] { |  | ||||||
|                 if let Ok(mnemonic) = Mnemonic::from_phrase(&sanitized, *language) { |  | ||||||
|                     return Ok(mnemonic); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Err("Can't get mnemonic from seed phrases") |  | ||||||
|         }; |  | ||||||
|         let mnemonic = parse_language_fn()?; |  | ||||||
|         let passphrase = prompt_passphrase(&passphrase_prompt)?; |         let passphrase = prompt_passphrase(&passphrase_prompt)?; | ||||||
|         let seed = Seed::new(&mnemonic, &passphrase); |         let seed = Seed::new(&mnemonic, &passphrase); | ||||||
|         keypair_from_seed(seed.as_bytes())? |         keypair_from_seed(seed.as_bytes())? | ||||||
|   | |||||||
| @@ -24,9 +24,7 @@ impl std::fmt::Debug for DisplayError { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub mod commitment; | pub mod commitment; | ||||||
| pub mod fee_payer; |  | ||||||
| pub mod input_parsers; | pub mod input_parsers; | ||||||
| pub mod input_validators; | pub mod input_validators; | ||||||
| pub mod keypair; | pub mod keypair; | ||||||
| pub mod nonce; |  | ||||||
| pub mod offline; | pub mod offline; | ||||||
|   | |||||||
| @@ -1,50 +0,0 @@ | |||||||
| use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant}; |  | ||||||
| use clap::{App, Arg}; |  | ||||||
|  |  | ||||||
| pub const NONCE_ARG: ArgConstant<'static> = ArgConstant { |  | ||||||
|     name: "nonce", |  | ||||||
|     long: "nonce", |  | ||||||
|     help: "Provide the nonce account to use when creating a nonced \n\ |  | ||||||
|            transaction. Nonced transactions are useful when a transaction \n\ |  | ||||||
|            requires a lengthy signing process. Learn more about nonced \n\ |  | ||||||
|            transactions at https://docs.solana.com/offline-signing/durable-nonce", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { |  | ||||||
|     name: "nonce_authority", |  | ||||||
|     long: "nonce-authority", |  | ||||||
|     help: "Provide the nonce authority keypair to use when signing a nonced transaction", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(NONCE_ARG.name) |  | ||||||
|         .long(NONCE_ARG.long) |  | ||||||
|         .takes_value(true) |  | ||||||
|         .value_name("PUBKEY") |  | ||||||
|         .requires(BLOCKHASH_ARG.name) |  | ||||||
|         .validator(is_valid_pubkey) |  | ||||||
|         .help(NONCE_ARG.help) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(NONCE_AUTHORITY_ARG.name) |  | ||||||
|         .long(NONCE_AUTHORITY_ARG.long) |  | ||||||
|         .takes_value(true) |  | ||||||
|         .value_name("KEYPAIR") |  | ||||||
|         .validator(is_valid_signer) |  | ||||||
|         .help(NONCE_AUTHORITY_ARG.help) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub trait NonceArgs { |  | ||||||
|     fn nonce_args(self, global: bool) -> Self; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl NonceArgs for App<'_, '_> { |  | ||||||
|     fn nonce_args(self, global: bool) -> Self { |  | ||||||
|         self.arg(nonce_arg().global(global)).arg( |  | ||||||
|             nonce_authority_arg() |  | ||||||
|                 .requires(NONCE_ARG.name) |  | ||||||
|                 .global(global), |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| use crate::{input_validators::*, ArgConstant}; | use crate::ArgConstant; | ||||||
| use clap::{App, Arg}; |  | ||||||
|  |  | ||||||
| pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { | pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { | ||||||
|     name: "blockhash", |     name: "blockhash", | ||||||
| @@ -18,43 +17,3 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant { | |||||||
|     long: "signer", |     long: "signer", | ||||||
|     help: "Provide a public-key/signature pair for the transaction", |     help: "Provide a public-key/signature pair for the transaction", | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(BLOCKHASH_ARG.name) |  | ||||||
|         .long(BLOCKHASH_ARG.long) |  | ||||||
|         .takes_value(true) |  | ||||||
|         .value_name("BLOCKHASH") |  | ||||||
|         .validator(is_hash) |  | ||||||
|         .help(BLOCKHASH_ARG.help) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(SIGN_ONLY_ARG.name) |  | ||||||
|         .long(SIGN_ONLY_ARG.long) |  | ||||||
|         .takes_value(false) |  | ||||||
|         .requires(BLOCKHASH_ARG.name) |  | ||||||
|         .help(SIGN_ONLY_ARG.help) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn signer_arg<'a, 'b>() -> Arg<'a, 'b> { |  | ||||||
|     Arg::with_name(SIGNER_ARG.name) |  | ||||||
|         .long(SIGNER_ARG.long) |  | ||||||
|         .takes_value(true) |  | ||||||
|         .value_name("PUBKEY=SIGNATURE") |  | ||||||
|         .validator(is_pubkey_sig) |  | ||||||
|         .requires(BLOCKHASH_ARG.name) |  | ||||||
|         .multiple(true) |  | ||||||
|         .help(SIGNER_ARG.help) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub trait OfflineArgs { |  | ||||||
|     fn offline_args(self, global: bool) -> Self; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl OfflineArgs for App<'_, '_> { |  | ||||||
|     fn offline_args(self, global: bool) -> Self { |  | ||||||
|         self.arg(blockhash_arg().global(global)) |  | ||||||
|             .arg(sign_only_arg().global(global)) |  | ||||||
|             .arg(signer_arg().global(global)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-cli-config" | name = "solana-cli-config" | ||||||
| description = "Blockchain, Rebuilt for Scale" | description = "Blockchain, Rebuilt for Scale" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| dirs-next = "2.0.0" | dirs = "2.0.2" | ||||||
| lazy_static = "1.4.0" | lazy_static = "1.4.0" | ||||||
| serde = "1.0.112" | serde = "1.0.110" | ||||||
| serde_derive = "1.0.103" | serde_derive = "1.0.103" | ||||||
| serde_yaml = "0.8.13" | serde_yaml = "0.8.12" | ||||||
| url = "2.1.1" | url = "2.1.1" | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| // Wallet settings that can be configured for long-term use | // Wallet settings that can be configured for long-term use | ||||||
| use serde_derive::{Deserialize, Serialize}; | use serde_derive::{Deserialize, Serialize}; | ||||||
| use std::{collections::HashMap, io, path::Path}; | use std::{collections::HashMap, io}; | ||||||
| use url::Url; | use url::Url; | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     pub static ref CONFIG_FILE: Option<String> = { |     pub static ref CONFIG_FILE: Option<String> = { | ||||||
|         dirs_next::home_dir().map(|mut path| { |         dirs::home_dir().map(|mut path| { | ||||||
|             path.extend(&[".config", "solana", "cli", "config.yml"]); |             path.extend(&[".config", "solana", "cli", "config.yml"]); | ||||||
|             path.to_str().unwrap().to_string() |             path.to_str().unwrap().to_string() | ||||||
|         }) |         }) | ||||||
| @@ -17,7 +17,6 @@ pub struct Config { | |||||||
|     pub json_rpc_url: String, |     pub json_rpc_url: String, | ||||||
|     pub websocket_url: String, |     pub websocket_url: String, | ||||||
|     pub keypair_path: String, |     pub keypair_path: String, | ||||||
|  |  | ||||||
|     #[serde(default)] |     #[serde(default)] | ||||||
|     pub address_labels: HashMap<String, String>, |     pub address_labels: HashMap<String, String>, | ||||||
| } | } | ||||||
| @@ -25,7 +24,7 @@ pub struct Config { | |||||||
| impl Default for Config { | impl Default for Config { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         let keypair_path = { |         let keypair_path = { | ||||||
|             let mut keypair_path = dirs_next::home_dir().expect("home directory"); |             let mut keypair_path = dirs::home_dir().expect("home directory"); | ||||||
|             keypair_path.extend(&[".config", "solana", "id.json"]); |             keypair_path.extend(&[".config", "solana", "id.json"]); | ||||||
|             keypair_path.to_str().unwrap().to_string() |             keypair_path.to_str().unwrap().to_string() | ||||||
|         }; |         }; | ||||||
| @@ -35,17 +34,11 @@ impl Default for Config { | |||||||
|         // `Config::compute_websocket_url(&json_rpc_url)` |         // `Config::compute_websocket_url(&json_rpc_url)` | ||||||
|         let websocket_url = "".to_string(); |         let websocket_url = "".to_string(); | ||||||
|  |  | ||||||
|         let mut address_labels = HashMap::new(); |  | ||||||
|         address_labels.insert( |  | ||||||
|             "11111111111111111111111111111111".to_string(), |  | ||||||
|             "System Program".to_string(), |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         Self { |         Self { | ||||||
|             json_rpc_url, |             json_rpc_url, | ||||||
|             websocket_url, |             websocket_url, | ||||||
|             keypair_path, |             keypair_path, | ||||||
|             address_labels, |             address_labels: HashMap::new(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -75,35 +68,6 @@ impl Config { | |||||||
|         } |         } | ||||||
|         ws_url.to_string() |         ws_url.to_string() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn compute_rpc_banks_url(json_rpc_url: &str) -> String { |  | ||||||
|         let json_rpc_url: Option<Url> = json_rpc_url.parse().ok(); |  | ||||||
|         if json_rpc_url.is_none() { |  | ||||||
|             return "".to_string(); |  | ||||||
|         } |  | ||||||
|         let mut url = json_rpc_url.unwrap(); |  | ||||||
|         let port = url.port().unwrap_or(8899); |  | ||||||
|         url.set_port(Some(port + 3)).expect("unable to set port"); |  | ||||||
|         url.to_string() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn import_address_labels<P>(&mut self, filename: P) -> Result<(), io::Error> |  | ||||||
|     where |  | ||||||
|         P: AsRef<Path>, |  | ||||||
|     { |  | ||||||
|         let imports: HashMap<String, String> = crate::load_config_file(filename)?; |  | ||||||
|         for (address, label) in imports.into_iter() { |  | ||||||
|             self.address_labels.insert(address, label); |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn export_address_labels<P>(&self, filename: P) -> Result<(), io::Error> |  | ||||||
|     where |  | ||||||
|         P: AsRef<Path>, |  | ||||||
|     { |  | ||||||
|         crate::save_config_file(&self.address_labels, filename) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @@ -133,28 +97,4 @@ mod test { | |||||||
|  |  | ||||||
|         assert_eq!(Config::compute_websocket_url(&"garbage"), String::new()); |         assert_eq!(Config::compute_websocket_url(&"garbage"), String::new()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn compute_rpc_banks_url() { |  | ||||||
|         assert_eq!( |  | ||||||
|             Config::compute_rpc_banks_url(&"http://devnet.solana.com"), |  | ||||||
|             "http://devnet.solana.com:8902/".to_string() |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         assert_eq!( |  | ||||||
|             Config::compute_rpc_banks_url(&"https://devnet.solana.com"), |  | ||||||
|             "https://devnet.solana.com:8902/".to_string() |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         assert_eq!( |  | ||||||
|             Config::compute_rpc_banks_url(&"http://example.com:8899"), |  | ||||||
|             "http://example.com:8902/".to_string() |  | ||||||
|         ); |  | ||||||
|         assert_eq!( |  | ||||||
|             Config::compute_rpc_banks_url(&"https://example.com:1234"), |  | ||||||
|             "https://example.com:1237/".to_string() |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         assert_eq!(Config::compute_rpc_banks_url(&"garbage"), String::new()); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| [package] |  | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] |  | ||||||
| edition = "2018" |  | ||||||
| name = "solana-cli-output" |  | ||||||
| description = "Blockchain, Rebuilt for Scale" |  | ||||||
| version = "1.3.21" |  | ||||||
| repository = "https://github.com/solana-labs/solana" |  | ||||||
| license = "Apache-2.0" |  | ||||||
| homepage = "https://solana.com/" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| chrono = { version = "0.4.11", features = ["serde"] } |  | ||||||
| console = "0.11.3" |  | ||||||
| humantime = "2.0.1" |  | ||||||
| Inflector = "0.11.4" |  | ||||||
| indicatif = "0.15.0" |  | ||||||
| serde = "1.0.112" |  | ||||||
| serde_derive = "1.0.103" |  | ||||||
| serde_json = "1.0.56" |  | ||||||
| solana-account-decoder = { path = "../account-decoder", version = "1.3.21" } |  | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } |  | ||||||
| solana-client = { path = "../client", version = "1.3.21" } |  | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } |  | ||||||
| solana-stake-program = { path = "../programs/stake", version = "1.3.21" } |  | ||||||
| solana-transaction-status = { path = "../transaction-status", version = "1.3.21" } |  | ||||||
| solana-vote-program = { path = "../programs/vote", version = "1.3.21" } |  | ||||||
|  |  | ||||||
| [package.metadata.docs.rs] |  | ||||||
| targets = ["x86_64-unknown-linux-gnu"] |  | ||||||
| @@ -1,15 +0,0 @@ | |||||||
| mod cli_output; |  | ||||||
| pub mod display; |  | ||||||
| pub use cli_output::*; |  | ||||||
|  |  | ||||||
| pub trait QuietDisplay: std::fmt::Display { |  | ||||||
|     fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result { |  | ||||||
|         write!(w, "{}", self) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub trait VerboseDisplay: std::fmt::Display { |  | ||||||
|     fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result { |  | ||||||
|         write!(w, "{}", self) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,59 +1,54 @@ | |||||||
| [package] | [package] | ||||||
| authors = ["Solana Maintainers <maintainers@solana.foundation>"] | authors = ["Solana Maintainers <maintainers@solana.com>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
| name = "solana-cli" | name = "solana-cli" | ||||||
| description = "Blockchain, Rebuilt for Scale" | description = "Blockchain, Rebuilt for Scale" | ||||||
| version = "1.3.21" | version = "1.2.7" | ||||||
| repository = "https://github.com/solana-labs/solana" | repository = "https://github.com/solana-labs/solana" | ||||||
| license = "Apache-2.0" | license = "Apache-2.0" | ||||||
| homepage = "https://solana.com/" | homepage = "https://solana.com/" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| bincode = "1.3.1" | bincode = "1.2.1" | ||||||
| bs58 = "0.3.1" | bs58 = "0.3.1" | ||||||
| chrono = { version = "0.4.11", features = ["serde"] } | chrono = { version = "0.4.11", features = ["serde"] } | ||||||
| clap = "2.33.1" | clap = "2.33.1" | ||||||
| criterion-stats = "0.3.0" | criterion-stats = "0.3.0" | ||||||
| ctrlc = { version = "3.1.5", features = ["termination"] } | ctrlc = { version = "3.1.4", features = ["termination"] } | ||||||
| console = "0.11.3" | console = "0.10.1" | ||||||
| dirs-next = "2.0.0" | dirs = "2.0.2" | ||||||
| log = "0.4.8" | log = "0.4.8" | ||||||
| Inflector = "0.11.4" | Inflector = "0.11.4" | ||||||
| indicatif = "0.15.0" | indicatif = "0.14.0" | ||||||
| humantime = "2.0.1" | humantime = "2.0.0" | ||||||
| num-traits = "0.2" | num-traits = "0.2" | ||||||
| pretty-hex = "0.1.1" | pretty-hex = "0.1.1" | ||||||
| reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] } | reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] } | ||||||
| serde = "1.0.112" | serde = "1.0.110" | ||||||
| serde_derive = "1.0.103" | serde_derive = "1.0.103" | ||||||
| serde_json = "1.0.56" | serde_json = "1.0.53" | ||||||
| solana-account-decoder = { path = "../account-decoder", version = "1.3.21" } | solana-budget-program = { path = "../programs/budget", version = "1.2.7" } | ||||||
| solana-budget-program = { path = "../programs/budget", version = "1.3.21" } | solana-clap-utils = { path = "../clap-utils", version = "1.2.7" } | ||||||
| solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.3.21" } | solana-cli-config = { path = "../cli-config", version = "1.2.7" } | ||||||
| solana-clap-utils = { path = "../clap-utils", version = "1.3.21" } | solana-client = { path = "../client", version = "1.2.7" } | ||||||
| solana-cli-config = { path = "../cli-config", version = "1.3.21" } | solana-config-program = { path = "../programs/config", version = "1.2.7" } | ||||||
| solana-cli-output = { path = "../cli-output", version = "1.3.21" } | solana-faucet = { path = "../faucet", version = "1.2.7" } | ||||||
| solana-client = { path = "../client", version = "1.3.21" } | solana-logger = { path = "../logger", version = "1.2.7" } | ||||||
| solana-config-program = { path = "../programs/config", version = "1.3.21" } | solana-net-utils = { path = "../net-utils", version = "1.2.7" } | ||||||
| solana-faucet = { path = "../faucet", version = "1.3.21" } | solana-remote-wallet = { path = "../remote-wallet", version = "1.2.7" } | ||||||
| solana-logger = { path = "../logger", version = "1.3.21" } | solana-runtime = { path = "../runtime", version = "1.2.7" } | ||||||
| solana-net-utils = { path = "../net-utils", version = "1.3.21" } | solana-sdk = { path = "../sdk", version = "1.2.7" } | ||||||
| solana_rbpf = "=0.1.32" | solana-stake-program = { path = "../programs/stake", version = "1.2.7" } | ||||||
| solana-remote-wallet = { path = "../remote-wallet", version = "1.3.21" } | solana-transaction-status = { path = "../transaction-status", version = "1.2.7" } | ||||||
| solana-runtime = { path = "../runtime", version = "1.3.21" } | solana-version = { path = "../version", version = "1.2.7" } | ||||||
| solana-sdk = { path = "../sdk", version = "1.3.21" } | solana-vote-program = { path = "../programs/vote", version = "1.2.7" } | ||||||
| solana-stake-program = { path = "../programs/stake", version = "1.3.21" } | solana-vote-signer = { path = "../vote-signer", version = "1.2.7" } | ||||||
| solana-transaction-status = { path = "../transaction-status", version = "1.3.21" } | thiserror = "1.0.19" | ||||||
| solana-version = { path = "../version", version = "1.3.21" } |  | ||||||
| solana-vote-program = { path = "../programs/vote", version = "1.3.21" } |  | ||||||
| solana-vote-signer = { path = "../vote-signer", version = "1.3.21" } |  | ||||||
| thiserror = "1.0.20" |  | ||||||
| tiny-bip39 = "0.7.0" |  | ||||||
| url = "2.1.1" | url = "2.1.1" | ||||||
|  |  | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| solana-core = { path = "../core", version = "1.3.21" } | solana-core = { path = "../core", version = "1.2.7" } | ||||||
| solana-budget-program = { path = "../programs/budget", version = "1.3.21" } | solana-budget-program = { path = "../programs/budget", version = "1.2.7" } | ||||||
| tempfile = "3.1.0" | tempfile = "3.1.0" | ||||||
|  |  | ||||||
| [[bin]] | [[bin]] | ||||||
|   | |||||||
| @@ -4,8 +4,7 @@ use solana_client::{ | |||||||
|     rpc_client::RpcClient, |     rpc_client::RpcClient, | ||||||
| }; | }; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, message::Message, |     fee_calculator::FeeCalculator, message::Message, native_token::lamports_to_sol, pubkey::Pubkey, | ||||||
|     native_token::lamports_to_sol, pubkey::Pubkey, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub fn check_account_for_fee( | pub fn check_account_for_fee( | ||||||
| @@ -17,79 +16,17 @@ pub fn check_account_for_fee( | |||||||
|     check_account_for_multiple_fees(rpc_client, account_pubkey, fee_calculator, &[message]) |     check_account_for_multiple_fees(rpc_client, account_pubkey, fee_calculator, &[message]) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn check_account_for_fee_with_commitment( |  | ||||||
|     rpc_client: &RpcClient, |  | ||||||
|     account_pubkey: &Pubkey, |  | ||||||
|     fee_calculator: &FeeCalculator, |  | ||||||
|     message: &Message, |  | ||||||
|     commitment: CommitmentConfig, |  | ||||||
| ) -> Result<(), CliError> { |  | ||||||
|     check_account_for_multiple_fees_with_commitment( |  | ||||||
|         rpc_client, |  | ||||||
|         account_pubkey, |  | ||||||
|         fee_calculator, |  | ||||||
|         &[message], |  | ||||||
|         commitment, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn check_account_for_multiple_fees( | pub fn check_account_for_multiple_fees( | ||||||
|     rpc_client: &RpcClient, |     rpc_client: &RpcClient, | ||||||
|     account_pubkey: &Pubkey, |     account_pubkey: &Pubkey, | ||||||
|     fee_calculator: &FeeCalculator, |     fee_calculator: &FeeCalculator, | ||||||
|     messages: &[&Message], |     messages: &[&Message], | ||||||
| ) -> Result<(), CliError> { |  | ||||||
|     check_account_for_multiple_fees_with_commitment( |  | ||||||
|         rpc_client, |  | ||||||
|         account_pubkey, |  | ||||||
|         fee_calculator, |  | ||||||
|         messages, |  | ||||||
|         CommitmentConfig::default(), |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn check_account_for_multiple_fees_with_commitment( |  | ||||||
|     rpc_client: &RpcClient, |  | ||||||
|     account_pubkey: &Pubkey, |  | ||||||
|     fee_calculator: &FeeCalculator, |  | ||||||
|     messages: &[&Message], |  | ||||||
|     commitment: CommitmentConfig, |  | ||||||
| ) -> Result<(), CliError> { |  | ||||||
|     check_account_for_spend_multiple_fees_with_commitment( |  | ||||||
|         rpc_client, |  | ||||||
|         account_pubkey, |  | ||||||
|         0, |  | ||||||
|         fee_calculator, |  | ||||||
|         messages, |  | ||||||
|         commitment, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn check_account_for_spend_multiple_fees_with_commitment( |  | ||||||
|     rpc_client: &RpcClient, |  | ||||||
|     account_pubkey: &Pubkey, |  | ||||||
|     balance: u64, |  | ||||||
|     fee_calculator: &FeeCalculator, |  | ||||||
|     messages: &[&Message], |  | ||||||
|     commitment: CommitmentConfig, |  | ||||||
| ) -> Result<(), CliError> { | ) -> Result<(), CliError> { | ||||||
|     let fee = calculate_fee(fee_calculator, messages); |     let fee = calculate_fee(fee_calculator, messages); | ||||||
|     if !check_account_for_balance_with_commitment( |     if !check_account_for_balance(rpc_client, account_pubkey, fee) | ||||||
|         rpc_client, |         .map_err(Into::<ClientError>::into)? | ||||||
|         account_pubkey, |  | ||||||
|         balance + fee, |  | ||||||
|         commitment, |  | ||||||
|     ) |  | ||||||
|     .map_err(Into::<ClientError>::into)? |  | ||||||
|     { |     { | ||||||
|         if balance > 0 { |         return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee))); | ||||||
|             return Err(CliError::InsufficientFundsForSpendAndFee( |  | ||||||
|                 lamports_to_sol(balance), |  | ||||||
|                 lamports_to_sol(fee), |  | ||||||
|             )); |  | ||||||
|         } else { |  | ||||||
|             return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee))); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| @@ -106,23 +43,7 @@ pub fn check_account_for_balance( | |||||||
|     account_pubkey: &Pubkey, |     account_pubkey: &Pubkey, | ||||||
|     balance: u64, |     balance: u64, | ||||||
| ) -> ClientResult<bool> { | ) -> ClientResult<bool> { | ||||||
|     check_account_for_balance_with_commitment( |     let lamports = rpc_client.get_balance(account_pubkey)?; | ||||||
|         rpc_client, |  | ||||||
|         account_pubkey, |  | ||||||
|         balance, |  | ||||||
|         CommitmentConfig::default(), |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn check_account_for_balance_with_commitment( |  | ||||||
|     rpc_client: &RpcClient, |  | ||||||
|     account_pubkey: &Pubkey, |  | ||||||
|     balance: u64, |  | ||||||
|     commitment: CommitmentConfig, |  | ||||||
| ) -> ClientResult<bool> { |  | ||||||
|     let lamports = rpc_client |  | ||||||
|         .get_balance_with_commitment(account_pubkey, commitment)? |  | ||||||
|         .value; |  | ||||||
|     if lamports != 0 && lamports >= balance { |     if lamports != 0 && lamports >= balance { | ||||||
|         return Ok(true); |         return Ok(true); | ||||||
|     } |     } | ||||||
| @@ -161,7 +82,7 @@ mod tests { | |||||||
|             context: RpcResponseContext { slot: 1 }, |             context: RpcResponseContext { slot: 1 }, | ||||||
|             value: json!(account_balance), |             value: json!(account_balance), | ||||||
|         }); |         }); | ||||||
|         let pubkey = solana_sdk::pubkey::new_rand(); |         let pubkey = Pubkey::new_rand(); | ||||||
|         let fee_calculator = FeeCalculator::new(1); |         let fee_calculator = FeeCalculator::new(1); | ||||||
|  |  | ||||||
|         let pubkey0 = Pubkey::new(&[0; 32]); |         let pubkey0 = Pubkey::new(&[0; 32]); | ||||||
| @@ -221,7 +142,7 @@ mod tests { | |||||||
|             context: RpcResponseContext { slot: 1 }, |             context: RpcResponseContext { slot: 1 }, | ||||||
|             value: json!(account_balance), |             value: json!(account_balance), | ||||||
|         }); |         }); | ||||||
|         let pubkey = solana_sdk::pubkey::new_rand(); |         let pubkey = Pubkey::new_rand(); | ||||||
|  |  | ||||||
|         let mut mocks = HashMap::new(); |         let mut mocks = HashMap::new(); | ||||||
|         mocks.insert(RpcRequest::GetBalance, account_balance_response); |         mocks.insert(RpcRequest::GetBalance, account_balance_response); | ||||||
| @@ -267,9 +188,9 @@ mod tests { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_check_unique_pubkeys() { |     fn test_check_unique_pubkeys() { | ||||||
|         let pubkey0 = solana_sdk::pubkey::new_rand(); |         let pubkey0 = Pubkey::new_rand(); | ||||||
|         let pubkey_clone = pubkey0; |         let pubkey_clone = pubkey0; | ||||||
|         let pubkey1 = solana_sdk::pubkey::new_rand(); |         let pubkey1 = Pubkey::new_rand(); | ||||||
|  |  | ||||||
|         check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "bar".to_string())) |         check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "bar".to_string())) | ||||||
|             .expect("unexpected result"); |             .expect("unexpected result"); | ||||||
|   | |||||||
							
								
								
									
										1452
									
								
								cli/src/cli.rs
									
									
									
									
									
								
							
							
						
						
									
										1452
									
								
								cli/src/cli.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,38 +1,24 @@ | |||||||
| use crate::{ | use crate::{cli::build_balance_message, display::writeln_name_value}; | ||||||
|     display::{build_balance_message, format_labeled_address, writeln_name_value}, |  | ||||||
|     QuietDisplay, VerboseDisplay, |  | ||||||
| }; |  | ||||||
| use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc}; | use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc}; | ||||||
| use console::{style, Emoji}; | use console::{style, Emoji}; | ||||||
| use inflector::cases::titlecase::to_title_case; | use inflector::cases::titlecase::to_title_case; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::Serialize; | ||||||
| use serde_json::{Map, Value}; | use serde_json::{Map, Value}; | ||||||
| use solana_account_decoder::parse_token::UiTokenAccount; |  | ||||||
| use solana_clap_utils::keypair::SignOnly; |  | ||||||
| use solana_client::rpc_response::{ | use solana_client::rpc_response::{ | ||||||
|     RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo, |     RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo, | ||||||
| }; | }; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     clock::{self, Epoch, Slot, UnixTimestamp}, |     clock::{self, Epoch, Slot, UnixTimestamp}, | ||||||
|     epoch_info::EpochInfo, |     epoch_info::EpochInfo, | ||||||
|     hash::Hash, |  | ||||||
|     native_token::lamports_to_sol, |     native_token::lamports_to_sol, | ||||||
|     pubkey::Pubkey, |  | ||||||
|     signature::Signature, |  | ||||||
|     stake_history::StakeHistoryEntry, |     stake_history::StakeHistoryEntry, | ||||||
|     transaction::Transaction, |  | ||||||
| }; | }; | ||||||
| use solana_stake_program::stake_state::{Authorized, Lockup}; | use solana_stake_program::stake_state::{Authorized, Lockup}; | ||||||
| use solana_vote_program::{ | use solana_vote_program::{ | ||||||
|     authorized_voters::AuthorizedVoters, |     authorized_voters::AuthorizedVoters, | ||||||
|     vote_state::{BlockTimestamp, Lockout}, |     vote_state::{BlockTimestamp, Lockout}, | ||||||
| }; | }; | ||||||
| use std::{ | use std::{collections::BTreeMap, fmt, time::Duration}; | ||||||
|     collections::{BTreeMap, HashMap}, |  | ||||||
|     fmt, |  | ||||||
|     str::FromStr, |  | ||||||
|     time::Duration, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| static WARNING: Emoji = Emoji("⚠️", "!"); | static WARNING: Emoji = Emoji("⚠️", "!"); | ||||||
| 
 | 
 | ||||||
| @@ -41,27 +27,15 @@ pub enum OutputFormat { | |||||||
|     Display, |     Display, | ||||||
|     Json, |     Json, | ||||||
|     JsonCompact, |     JsonCompact, | ||||||
|     DisplayQuiet, |  | ||||||
|     DisplayVerbose, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl OutputFormat { | impl OutputFormat { | ||||||
|     pub fn formatted_string<T>(&self, item: &T) -> String |     pub fn formatted_string<T>(&self, item: &T) -> String | ||||||
|     where |     where | ||||||
|         T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay, |         T: Serialize + fmt::Display, | ||||||
|     { |     { | ||||||
|         match self { |         match self { | ||||||
|             OutputFormat::Display => format!("{}", item), |             OutputFormat::Display => format!("{}", item), | ||||||
|             OutputFormat::DisplayQuiet => { |  | ||||||
|                 let mut s = String::new(); |  | ||||||
|                 QuietDisplay::write_str(item, &mut s).unwrap(); |  | ||||||
|                 s |  | ||||||
|             } |  | ||||||
|             OutputFormat::DisplayVerbose => { |  | ||||||
|                 let mut s = String::new(); |  | ||||||
|                 VerboseDisplay::write_str(item, &mut s).unwrap(); |  | ||||||
|                 s |  | ||||||
|             } |  | ||||||
|             OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(), |             OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(), | ||||||
|             OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(), |             OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(), | ||||||
|         } |         } | ||||||
| @@ -76,9 +50,6 @@ pub struct CliAccount { | |||||||
|     pub use_lamports_unit: bool, |     pub use_lamports_unit: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliAccount {} |  | ||||||
| impl VerboseDisplay for CliAccount {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliAccount { | impl fmt::Display for CliAccount { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
| @@ -121,9 +92,6 @@ pub struct CliBlockProduction { | |||||||
|     pub verbose: bool, |     pub verbose: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliBlockProduction {} |  | ||||||
| impl VerboseDisplay for CliBlockProduction {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliBlockProduction { | impl fmt::Display for CliBlockProduction { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
| @@ -228,17 +196,9 @@ impl From<EpochInfo> for CliEpochInfo { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliEpochInfo {} |  | ||||||
| impl VerboseDisplay for CliEpochInfo {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliEpochInfo { | impl fmt::Display for CliEpochInfo { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
|         writeln_name_value( |  | ||||||
|             f, |  | ||||||
|             "Block height:", |  | ||||||
|             &self.epoch_info.block_height.to_string(), |  | ||||||
|         )?; |  | ||||||
|         writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?; |         writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?; | ||||||
|         writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?; |         writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?; | ||||||
|         let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index; |         let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index; | ||||||
| @@ -287,31 +247,18 @@ fn slot_to_human_time(slot: Slot) -> String { | |||||||
|     .to_string() |     .to_string() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Default)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct CliValidatorsStakeByVersion { |  | ||||||
|     pub current_validators: usize, |  | ||||||
|     pub delinquent_validators: usize, |  | ||||||
|     pub current_active_stake: u64, |  | ||||||
|     pub delinquent_active_stake: u64, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Serialize, Deserialize)] | #[derive(Serialize, Deserialize)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct CliValidators { | pub struct CliValidators { | ||||||
|     pub total_active_stake: u64, |     pub total_active_stake: u64, | ||||||
|     pub total_current_stake: u64, |     pub total_current_stake: u64, | ||||||
|     pub total_delinquent_stake: u64, |     pub total_deliquent_stake: u64, | ||||||
|     pub current_validators: Vec<CliValidator>, |     pub current_validators: Vec<CliValidator>, | ||||||
|     pub delinquent_validators: Vec<CliValidator>, |     pub delinquent_validators: Vec<CliValidator>, | ||||||
|     pub stake_by_version: BTreeMap<String, CliValidatorsStakeByVersion>, |  | ||||||
|     #[serde(skip_serializing)] |     #[serde(skip_serializing)] | ||||||
|     pub use_lamports_unit: bool, |     pub use_lamports_unit: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliValidators {} |  | ||||||
| impl VerboseDisplay for CliValidators {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliValidators { | impl fmt::Display for CliValidators { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         fn write_vote_account( |         fn write_vote_account( | ||||||
| @@ -331,7 +278,7 @@ impl fmt::Display for CliValidators { | |||||||
| 
 | 
 | ||||||
|             writeln!( |             writeln!( | ||||||
|                 f, |                 f, | ||||||
|                 "{} {:<44}  {:<44}  {:>3}%   {:>8}  {:>10}  {:>10}  {:>8}  {}", |                 "{} {:<44}  {:<44}  {:>9}%   {:>8}  {:>10}  {:>7}  {}", | ||||||
|                 if delinquent { |                 if delinquent { | ||||||
|                     WARNING.to_string() |                     WARNING.to_string() | ||||||
|                 } else { |                 } else { | ||||||
| @@ -343,12 +290,11 @@ impl fmt::Display for CliValidators { | |||||||
|                 non_zero_or_dash(validator.last_vote), |                 non_zero_or_dash(validator.last_vote), | ||||||
|                 non_zero_or_dash(validator.root_slot), |                 non_zero_or_dash(validator.root_slot), | ||||||
|                 validator.credits, |                 validator.credits, | ||||||
|                 validator.version, |  | ||||||
|                 if validator.activated_stake > 0 { |                 if validator.activated_stake > 0 { | ||||||
|                     format!( |                     format!( | ||||||
|                         "{} ({:.2}%)", |                         "{} ({:.2}%)", | ||||||
|                         build_balance_message(validator.activated_stake, use_lamports_unit, true), |                         build_balance_message(validator.activated_stake, use_lamports_unit, true), | ||||||
|                         100. * validator.activated_stake as f64 / total_active_stake as f64, |                         100. * validator.activated_stake as f64 / total_active_stake as f64 | ||||||
|                     ) |                     ) | ||||||
|                 } else { |                 } else { | ||||||
|                     "-".into() |                     "-".into() | ||||||
| @@ -360,7 +306,7 @@ impl fmt::Display for CliValidators { | |||||||
|             "Active Stake:", |             "Active Stake:", | ||||||
|             &build_balance_message(self.total_active_stake, self.use_lamports_unit, true), |             &build_balance_message(self.total_active_stake, self.use_lamports_unit, true), | ||||||
|         )?; |         )?; | ||||||
|         if self.total_delinquent_stake > 0 { |         if self.total_deliquent_stake > 0 { | ||||||
|             writeln_name_value( |             writeln_name_value( | ||||||
|                 f, |                 f, | ||||||
|                 "Current Stake:", |                 "Current Stake:", | ||||||
| @@ -376,49 +322,26 @@ impl fmt::Display for CliValidators { | |||||||
|                 &format!( |                 &format!( | ||||||
|                     "{} ({:0.2}%)", |                     "{} ({:0.2}%)", | ||||||
|                     &build_balance_message( |                     &build_balance_message( | ||||||
|                         self.total_delinquent_stake, |                         self.total_deliquent_stake, | ||||||
|                         self.use_lamports_unit, |                         self.use_lamports_unit, | ||||||
|                         true |                         true | ||||||
|                     ), |                     ), | ||||||
|                     100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64 |                     100. * self.total_deliquent_stake as f64 / self.total_active_stake as f64 | ||||||
|                 ), |                 ), | ||||||
|             )?; |             )?; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         writeln!(f)?; |  | ||||||
|         writeln!(f, "{}", style("Stake By Version:").bold())?; |  | ||||||
|         for (version, info) in self.stake_by_version.iter() { |  | ||||||
|             writeln!( |  | ||||||
|                 f, |  | ||||||
|                 "{:<8} - {:3} current validators ({:>5.2}%){}", |  | ||||||
|                 version, |  | ||||||
|                 info.current_validators, |  | ||||||
|                 100. * info.current_active_stake as f64 / self.total_active_stake as f64, |  | ||||||
|                 if info.delinquent_validators > 0 { |  | ||||||
|                     format!( |  | ||||||
|                         ", {:3} delinquent validators ({:>5.2}%)", |  | ||||||
|                         info.delinquent_validators, |  | ||||||
|                         100. * info.delinquent_active_stake as f64 / self.total_active_stake as f64 |  | ||||||
|                     ) |  | ||||||
|                 } else { |  | ||||||
|                     "".to_string() |  | ||||||
|                 }, |  | ||||||
|             )?; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
|         writeln!( |         writeln!( | ||||||
|             f, |             f, | ||||||
|             "{}", |             "{}", | ||||||
|             style(format!( |             style(format!( | ||||||
|                 "  {:<44}  {:<38}  {}  {}  {}  {:>10}  {:^8}  {}", |                 "  {:<44}  {:<44}  {}  {}  {}  {:>7}  {}", | ||||||
|                 "Identity Pubkey", |                 "Identity Pubkey", | ||||||
|                 "Vote Account Pubkey", |                 "Vote Account Pubkey", | ||||||
|                 "Commission", |                 "Commission", | ||||||
|                 "Last Vote", |                 "Last Vote", | ||||||
|                 "Root Block", |                 "Root Block", | ||||||
|                 "Credits", |                 "Credits", | ||||||
|                 "Version", |  | ||||||
|                 "Active Stake", |                 "Active Stake", | ||||||
|             )) |             )) | ||||||
|             .bold() |             .bold() | ||||||
| @@ -455,19 +378,13 @@ pub struct CliValidator { | |||||||
|     pub root_slot: u64, |     pub root_slot: u64, | ||||||
|     pub credits: u64, |     pub credits: u64, | ||||||
|     pub activated_stake: u64, |     pub activated_stake: u64, | ||||||
|     pub version: String, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl CliValidator { | impl CliValidator { | ||||||
|     pub fn new( |     pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch) -> Self { | ||||||
|         vote_account: &RpcVoteAccountInfo, |  | ||||||
|         current_epoch: Epoch, |  | ||||||
|         version: String, |  | ||||||
|         address_labels: &HashMap<String, String>, |  | ||||||
|     ) -> Self { |  | ||||||
|         Self { |         Self { | ||||||
|             identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels), |             identity_pubkey: vote_account.node_pubkey.to_string(), | ||||||
|             vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels), |             vote_account_pubkey: vote_account.vote_pubkey.to_string(), | ||||||
|             commission: vote_account.commission, |             commission: vote_account.commission, | ||||||
|             last_vote: vote_account.last_vote, |             last_vote: vote_account.last_vote, | ||||||
|             root_slot: vote_account.root_slot, |             root_slot: vote_account.root_slot, | ||||||
| @@ -483,7 +400,6 @@ impl CliValidator { | |||||||
|                 }) |                 }) | ||||||
|                 .unwrap_or(0), |                 .unwrap_or(0), | ||||||
|             activated_stake: vote_account.activated_stake, |             activated_stake: vote_account.activated_stake, | ||||||
|             version, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -500,9 +416,6 @@ pub struct CliNonceAccount { | |||||||
|     pub use_lamports_unit: bool, |     pub use_lamports_unit: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliNonceAccount {} |  | ||||||
| impl VerboseDisplay for CliNonceAccount {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliNonceAccount { | impl fmt::Display for CliNonceAccount { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!( |         writeln!( | ||||||
| @@ -540,9 +453,6 @@ impl CliStakeVec { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliStakeVec {} |  | ||||||
| impl VerboseDisplay for CliStakeVec {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliStakeVec { | impl fmt::Display for CliStakeVec { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         for state in &self.0 { |         for state in &self.0 { | ||||||
| @@ -561,9 +471,6 @@ pub struct CliKeyedStakeState { | |||||||
|     pub stake_state: CliStakeState, |     pub stake_state: CliStakeState, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliKeyedStakeState {} |  | ||||||
| impl VerboseDisplay for CliKeyedStakeState {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliKeyedStakeState { | impl fmt::Display for CliKeyedStakeState { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?; |         writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?; | ||||||
| @@ -571,48 +478,6 @@ impl fmt::Display for CliKeyedStakeState { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct CliEpochReward { |  | ||||||
|     pub epoch: Epoch, |  | ||||||
|     pub effective_slot: Slot, |  | ||||||
|     pub amount: u64,       // lamports
 |  | ||||||
|     pub post_balance: u64, // lamports
 |  | ||||||
|     pub percent_change: f64, |  | ||||||
|     pub apr: f64, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn show_epoch_rewards( |  | ||||||
|     f: &mut fmt::Formatter, |  | ||||||
|     epoch_rewards: &Option<Vec<CliEpochReward>>, |  | ||||||
| ) -> fmt::Result { |  | ||||||
|     if let Some(epoch_rewards) = epoch_rewards { |  | ||||||
|         if epoch_rewards.is_empty() { |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         writeln!(f, "Epoch Rewards:")?; |  | ||||||
|         writeln!( |  | ||||||
|             f, |  | ||||||
|             "  {:<8}  {:<11}  {:<15}  {:<15}  {:>14}  {:>14}", |  | ||||||
|             "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR" |  | ||||||
|         )?; |  | ||||||
|         for reward in epoch_rewards { |  | ||||||
|             writeln!( |  | ||||||
|                 f, |  | ||||||
|                 "  {:<8}  {:<11}  ◎{:<14.9}  ◎{:<14.9}  {:>13.9}%  {:>13.9}%", |  | ||||||
|                 reward.epoch, |  | ||||||
|                 reward.effective_slot, |  | ||||||
|                 lamports_to_sol(reward.amount), |  | ||||||
|                 lamports_to_sol(reward.post_balance), |  | ||||||
|                 reward.percent_change, |  | ||||||
|                 reward.apr, |  | ||||||
|             )?; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Default, Serialize, Deserialize)] | #[derive(Default, Serialize, Deserialize)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct CliStakeState { | pub struct CliStakeState { | ||||||
| @@ -642,13 +507,8 @@ pub struct CliStakeState { | |||||||
|     pub activating_stake: Option<u64>, |     pub activating_stake: Option<u64>, | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     pub deactivating_stake: Option<u64>, |     pub deactivating_stake: Option<u64>, | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub epoch_rewards: Option<Vec<CliEpochReward>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliStakeState {} |  | ||||||
| impl VerboseDisplay for CliStakeState {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliStakeState { | impl fmt::Display for CliStakeState { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result { |         fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result { | ||||||
| @@ -656,20 +516,19 @@ impl fmt::Display for CliStakeState { | |||||||
|             writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?; |             writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?; | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         } |         } | ||||||
|         fn show_lockup(f: &mut fmt::Formatter, lockup: Option<&CliLockup>) -> fmt::Result { |         fn show_lockup(f: &mut fmt::Formatter, lockup: &CliLockup) -> fmt::Result { | ||||||
|             if let Some(lockup) = lockup { |             writeln!( | ||||||
|                 if lockup.unix_timestamp != UnixTimestamp::default() { |                 f, | ||||||
|                     writeln!( |                 "Lockup Timestamp: {} (UnixTimestamp: {})", | ||||||
|                         f, |                 DateTime::<Utc>::from_utc( | ||||||
|                         "Lockup Timestamp: {}", |                     NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0), | ||||||
|                         unix_timestamp_to_string(lockup.unix_timestamp) |                     Utc | ||||||
|                     )?; |                 ) | ||||||
|                 } |                 .to_rfc3339_opts(SecondsFormat::Secs, true), | ||||||
|                 if lockup.epoch != Epoch::default() { |                 lockup.unix_timestamp | ||||||
|                     writeln!(f, "Lockup Epoch: {}", lockup.epoch)?; |             )?; | ||||||
|                 } |             writeln!(f, "Lockup Epoch: {}", lockup.epoch)?; | ||||||
|                 writeln!(f, "Lockup Custodian: {}", lockup.custodian)?; |             writeln!(f, "Lockup Custodian: {}", lockup.custodian)?; | ||||||
|             } |  | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -693,7 +552,7 @@ impl fmt::Display for CliStakeState { | |||||||
|             CliStakeType::Initialized => { |             CliStakeType::Initialized => { | ||||||
|                 writeln!(f, "Stake account is undelegated")?; |                 writeln!(f, "Stake account is undelegated")?; | ||||||
|                 show_authorized(f, self.authorized.as_ref().unwrap())?; |                 show_authorized(f, self.authorized.as_ref().unwrap())?; | ||||||
|                 show_lockup(f, self.lockup.as_ref())?; |                 show_lockup(f, self.lockup.as_ref().unwrap())?; | ||||||
|             } |             } | ||||||
|             CliStakeType::Stake => { |             CliStakeType::Stake => { | ||||||
|                 let show_delegation = { |                 let show_delegation = { | ||||||
| @@ -791,15 +650,14 @@ impl fmt::Display for CliStakeState { | |||||||
|                     writeln!(f, "Stake account is undelegated")?; |                     writeln!(f, "Stake account is undelegated")?; | ||||||
|                 } |                 } | ||||||
|                 show_authorized(f, self.authorized.as_ref().unwrap())?; |                 show_authorized(f, self.authorized.as_ref().unwrap())?; | ||||||
|                 show_lockup(f, self.lockup.as_ref())?; |                 show_lockup(f, self.lockup.as_ref().unwrap())?; | ||||||
|                 show_epoch_rewards(f, &self.epoch_rewards)? |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, PartialEq)] | #[derive(Serialize, Deserialize)] | ||||||
| pub enum CliStakeType { | pub enum CliStakeType { | ||||||
|     Stake, |     Stake, | ||||||
|     RewardsPool, |     RewardsPool, | ||||||
| @@ -821,9 +679,6 @@ pub struct CliStakeHistory { | |||||||
|     pub use_lamports_unit: bool, |     pub use_lamports_unit: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliStakeHistory {} |  | ||||||
| impl VerboseDisplay for CliStakeHistory {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliStakeHistory { | impl fmt::Display for CliStakeHistory { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
| @@ -918,9 +773,6 @@ impl CliValidatorInfoVec { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliValidatorInfoVec {} |  | ||||||
| impl VerboseDisplay for CliValidatorInfoVec {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliValidatorInfoVec { | impl fmt::Display for CliValidatorInfoVec { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         if self.0.is_empty() { |         if self.0.is_empty() { | ||||||
| @@ -942,9 +794,6 @@ pub struct CliValidatorInfo { | |||||||
|     pub info: Map<String, Value>, |     pub info: Map<String, Value>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliValidatorInfo {} |  | ||||||
| impl VerboseDisplay for CliValidatorInfo {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliValidatorInfo { | impl fmt::Display for CliValidatorInfo { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?; |         writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?; | ||||||
| @@ -976,13 +825,8 @@ pub struct CliVoteAccount { | |||||||
|     pub epoch_voting_history: Vec<CliEpochVotingHistory>, |     pub epoch_voting_history: Vec<CliEpochVotingHistory>, | ||||||
|     #[serde(skip_serializing)] |     #[serde(skip_serializing)] | ||||||
|     pub use_lamports_unit: bool, |     pub use_lamports_unit: bool, | ||||||
|     #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|     pub epoch_rewards: Option<Vec<CliEpochReward>>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliVoteAccount {} |  | ||||||
| impl VerboseDisplay for CliVoteAccount {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliVoteAccount { | impl fmt::Display for CliVoteAccount { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!( |         writeln!( | ||||||
| @@ -1003,12 +847,7 @@ impl fmt::Display for CliVoteAccount { | |||||||
|                 None => "~".to_string(), |                 None => "~".to_string(), | ||||||
|             } |             } | ||||||
|         )?; |         )?; | ||||||
|         writeln!( |         writeln!(f, "Recent Timestamp: {:?}", self.recent_timestamp)?; | ||||||
|             f, |  | ||||||
|             "Recent Timestamp: {} from slot {}", |  | ||||||
|             unix_timestamp_to_string(self.recent_timestamp.timestamp), |  | ||||||
|             self.recent_timestamp.slot |  | ||||||
|         )?; |  | ||||||
|         if !self.votes.is_empty() { |         if !self.votes.is_empty() { | ||||||
|             writeln!(f, "Recent Votes:")?; |             writeln!(f, "Recent Votes:")?; | ||||||
|             for vote in &self.votes { |             for vote in &self.votes { | ||||||
| @@ -1027,7 +866,6 @@ impl fmt::Display for CliVoteAccount { | |||||||
|                 )?; |                 )?; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         show_epoch_rewards(f, &self.epoch_rewards)?; |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1038,9 +876,6 @@ pub struct CliAuthorizedVoters { | |||||||
|     authorized_voters: BTreeMap<Epoch, String>, |     authorized_voters: BTreeMap<Epoch, String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliAuthorizedVoters {} |  | ||||||
| impl VerboseDisplay for CliAuthorizedVoters {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliAuthorizedVoters { | impl fmt::Display for CliAuthorizedVoters { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         write!(f, "{:?}", self.authorized_voters) |         write!(f, "{:?}", self.authorized_voters) | ||||||
| @@ -1090,25 +925,19 @@ pub struct CliBlockTime { | |||||||
|     pub timestamp: UnixTimestamp, |     pub timestamp: UnixTimestamp, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliBlockTime {} |  | ||||||
| impl VerboseDisplay for CliBlockTime {} |  | ||||||
| 
 |  | ||||||
| fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String { |  | ||||||
|     format!( |  | ||||||
|         "{} (UnixTimestamp: {})", |  | ||||||
|         match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) { |  | ||||||
|             Some(ndt) => |  | ||||||
|                 DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true), |  | ||||||
|             None => "unknown".to_string(), |  | ||||||
|         }, |  | ||||||
|         unix_timestamp, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliBlockTime { | impl fmt::Display for CliBlockTime { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln_name_value(f, "Block:", &self.slot.to_string())?; |         writeln_name_value(f, "Block:", &self.slot.to_string())?; | ||||||
|         writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp)) |         writeln_name_value( | ||||||
|  |             f, | ||||||
|  |             "Date:", | ||||||
|  |             &format!( | ||||||
|  |                 "{} (UnixTimestamp: {})", | ||||||
|  |                 DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.timestamp, 0), Utc) | ||||||
|  |                     .to_rfc3339_opts(SecondsFormat::Secs, true), | ||||||
|  |                 self.timestamp | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -1124,9 +953,6 @@ pub struct CliSignOnlyData { | |||||||
|     pub bad_sig: Vec<String>, |     pub bad_sig: Vec<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliSignOnlyData {} |  | ||||||
| impl VerboseDisplay for CliSignOnlyData {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliSignOnlyData { | impl fmt::Display for CliSignOnlyData { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
| @@ -1159,9 +985,6 @@ pub struct CliSignature { | |||||||
|     pub signature: String, |     pub signature: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliSignature {} |  | ||||||
| impl VerboseDisplay for CliSignature {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliSignature { | impl fmt::Display for CliSignature { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!(f)?; |         writeln!(f)?; | ||||||
| @@ -1176,9 +999,6 @@ pub struct CliAccountBalances { | |||||||
|     pub accounts: Vec<RpcAccountBalance>, |     pub accounts: Vec<RpcAccountBalance>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliAccountBalances {} |  | ||||||
| impl VerboseDisplay for CliAccountBalances {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliAccountBalances { | impl fmt::Display for CliAccountBalances { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln!( |         writeln!( | ||||||
| @@ -1221,9 +1041,6 @@ impl From<RpcSupply> for CliSupply { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliSupply {} |  | ||||||
| impl VerboseDisplay for CliSupply {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliSupply { | impl fmt::Display for CliSupply { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?; |         writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?; | ||||||
| @@ -1257,9 +1074,6 @@ pub struct CliFees { | |||||||
|     pub last_valid_slot: Slot, |     pub last_valid_slot: Slot, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl QuietDisplay for CliFees {} |  | ||||||
| impl VerboseDisplay for CliFees {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliFees { | impl fmt::Display for CliFees { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         writeln_name_value(f, "Blockhash:", &self.blockhash)?; |         writeln_name_value(f, "Blockhash:", &self.blockhash)?; | ||||||
| @@ -1272,240 +1086,3 @@ impl fmt::Display for CliFees { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| #[derive(Serialize, Deserialize)] |  | ||||||
| #[serde(rename_all = "camelCase")] |  | ||||||
| pub struct CliTokenAccount { |  | ||||||
|     pub address: String, |  | ||||||
|     #[serde(flatten)] |  | ||||||
|     pub token_account: UiTokenAccount, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl QuietDisplay for CliTokenAccount {} |  | ||||||
| impl VerboseDisplay for CliTokenAccount {} |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for CliTokenAccount { |  | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         writeln!(f)?; |  | ||||||
|         writeln_name_value(f, "Address:", &self.address)?; |  | ||||||
|         let account = &self.token_account; |  | ||||||
|         writeln_name_value( |  | ||||||
|             f, |  | ||||||
|             "Balance:", |  | ||||||
|             &account.token_amount.real_number_string_trimmed(), |  | ||||||
|         )?; |  | ||||||
|         let mint = format!( |  | ||||||
|             "{}{}", |  | ||||||
|             account.mint, |  | ||||||
|             if account.is_native { " (native)" } else { "" } |  | ||||||
|         ); |  | ||||||
|         writeln_name_value(f, "Mint:", &mint)?; |  | ||||||
|         writeln_name_value(f, "Owner:", &account.owner)?; |  | ||||||
|         writeln_name_value(f, "State:", &format!("{:?}", account.state))?; |  | ||||||
|         if let Some(delegate) = &account.delegate { |  | ||||||
|             writeln!(f, "Delegation:")?; |  | ||||||
|             writeln_name_value(f, "  Delegate:", delegate)?; |  | ||||||
|             let allowance = account.delegated_amount.as_ref().unwrap(); |  | ||||||
|             writeln_name_value(f, "  Allowance:", &allowance.real_number_string_trimmed())?; |  | ||||||
|         } |  | ||||||
|         writeln_name_value( |  | ||||||
|             f, |  | ||||||
|             "Close authority:", |  | ||||||
|             &account.close_authority.as_ref().unwrap_or(&String::new()), |  | ||||||
|         )?; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn return_signers( |  | ||||||
|     tx: &Transaction, |  | ||||||
|     output_format: &OutputFormat, |  | ||||||
| ) -> Result<String, Box<dyn std::error::Error>> { |  | ||||||
|     let verify_results = tx.verify_with_results(); |  | ||||||
|     let mut signers = Vec::new(); |  | ||||||
|     let mut absent = Vec::new(); |  | ||||||
|     let mut bad_sig = Vec::new(); |  | ||||||
|     tx.signatures |  | ||||||
|         .iter() |  | ||||||
|         .zip(tx.message.account_keys.iter()) |  | ||||||
|         .zip(verify_results.into_iter()) |  | ||||||
|         .for_each(|((sig, key), res)| { |  | ||||||
|             if res { |  | ||||||
|                 signers.push(format!("{}={}", key, sig)) |  | ||||||
|             } else if *sig == Signature::default() { |  | ||||||
|                 absent.push(key.to_string()); |  | ||||||
|             } else { |  | ||||||
|                 bad_sig.push(key.to_string()); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     let cli_command = CliSignOnlyData { |  | ||||||
|         blockhash: tx.message.recent_blockhash.to_string(), |  | ||||||
|         signers, |  | ||||||
|         absent, |  | ||||||
|         bad_sig, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     Ok(output_format.formatted_string(&cli_command)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly { |  | ||||||
|     let object: Value = serde_json::from_str(&reply).unwrap(); |  | ||||||
|     let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap(); |  | ||||||
|     let blockhash = blockhash_str.parse::<Hash>().unwrap(); |  | ||||||
|     let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new(); |  | ||||||
|     let signer_strings = object.get("signers"); |  | ||||||
|     if let Some(sig_strings) = signer_strings { |  | ||||||
|         present_signers = sig_strings |  | ||||||
|             .as_array() |  | ||||||
|             .unwrap() |  | ||||||
|             .iter() |  | ||||||
|             .map(|signer_string| { |  | ||||||
|                 let mut signer = signer_string.as_str().unwrap().split('='); |  | ||||||
|                 let key = Pubkey::from_str(signer.next().unwrap()).unwrap(); |  | ||||||
|                 let sig = Signature::from_str(signer.next().unwrap()).unwrap(); |  | ||||||
|                 (key, sig) |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|     } |  | ||||||
|     let mut absent_signers: Vec<Pubkey> = Vec::new(); |  | ||||||
|     let signer_strings = object.get("absent"); |  | ||||||
|     if let Some(sig_strings) = signer_strings { |  | ||||||
|         absent_signers = sig_strings |  | ||||||
|             .as_array() |  | ||||||
|             .unwrap() |  | ||||||
|             .iter() |  | ||||||
|             .map(|val| { |  | ||||||
|                 let s = val.as_str().unwrap(); |  | ||||||
|                 Pubkey::from_str(s).unwrap() |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|     } |  | ||||||
|     let mut bad_signers: Vec<Pubkey> = Vec::new(); |  | ||||||
|     let signer_strings = object.get("badSig"); |  | ||||||
|     if let Some(sig_strings) = signer_strings { |  | ||||||
|         bad_signers = sig_strings |  | ||||||
|             .as_array() |  | ||||||
|             .unwrap() |  | ||||||
|             .iter() |  | ||||||
|             .map(|val| { |  | ||||||
|                 let s = val.as_str().unwrap(); |  | ||||||
|                 Pubkey::from_str(s).unwrap() |  | ||||||
|             }) |  | ||||||
|             .collect(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     SignOnly { |  | ||||||
|         blockhash, |  | ||||||
|         present_signers, |  | ||||||
|         absent_signers, |  | ||||||
|         bad_signers, |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::{ |  | ||||||
|         message::Message, |  | ||||||
|         pubkey::Pubkey, |  | ||||||
|         signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError}, |  | ||||||
|         system_instruction, |  | ||||||
|         transaction::Transaction, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn test_return_signers() { |  | ||||||
|         struct BadSigner { |  | ||||||
|             pubkey: Pubkey, |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         impl BadSigner { |  | ||||||
|             pub fn new(pubkey: Pubkey) -> Self { |  | ||||||
|                 Self { pubkey } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         impl Signer for BadSigner { |  | ||||||
|             fn try_pubkey(&self) -> Result<Pubkey, SignerError> { |  | ||||||
|                 Ok(self.pubkey) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> { |  | ||||||
|                 Ok(Signature::new(&[1u8; 64])) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap()); |  | ||||||
|         let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32]))); |  | ||||||
|         let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32]))); |  | ||||||
|         let to = Pubkey::new(&[5u8; 32]); |  | ||||||
|         let nonce = Pubkey::new(&[6u8; 32]); |  | ||||||
|         let from = present.pubkey(); |  | ||||||
|         let fee_payer = absent.pubkey(); |  | ||||||
|         let nonce_auth = bad.pubkey(); |  | ||||||
|         let mut tx = Transaction::new_unsigned(Message::new_with_nonce( |  | ||||||
|             vec![system_instruction::transfer(&from, &to, 42)], |  | ||||||
|             Some(&fee_payer), |  | ||||||
|             &nonce, |  | ||||||
|             &nonce_auth, |  | ||||||
|         )); |  | ||||||
| 
 |  | ||||||
|         let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()]; |  | ||||||
|         let blockhash = Hash::new(&[7u8; 32]); |  | ||||||
|         tx.try_partial_sign(&signers, blockhash).unwrap(); |  | ||||||
|         let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap(); |  | ||||||
|         let sign_only = parse_sign_only_reply_string(&res); |  | ||||||
|         assert_eq!(sign_only.blockhash, blockhash); |  | ||||||
|         assert_eq!(sign_only.present_signers[0].0, present.pubkey()); |  | ||||||
|         assert_eq!(sign_only.absent_signers[0], absent.pubkey()); |  | ||||||
|         assert_eq!(sign_only.bad_signers[0], bad.pubkey()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn test_verbose_quiet_output_formats() { |  | ||||||
|         #[derive(Deserialize, Serialize)] |  | ||||||
|         struct FallbackToDisplay {} |  | ||||||
|         impl std::fmt::Display for FallbackToDisplay { |  | ||||||
|             fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |  | ||||||
|                 write!(f, "display") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         impl QuietDisplay for FallbackToDisplay {} |  | ||||||
|         impl VerboseDisplay for FallbackToDisplay {} |  | ||||||
| 
 |  | ||||||
|         let f = FallbackToDisplay {}; |  | ||||||
|         assert_eq!(&OutputFormat::Display.formatted_string(&f), "display"); |  | ||||||
|         assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display"); |  | ||||||
|         assert_eq!( |  | ||||||
|             &OutputFormat::DisplayVerbose.formatted_string(&f), |  | ||||||
|             "display" |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         #[derive(Deserialize, Serialize)] |  | ||||||
|         struct DiscreteVerbosityDisplay {} |  | ||||||
|         impl std::fmt::Display for DiscreteVerbosityDisplay { |  | ||||||
|             fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |  | ||||||
|                 write!(f, "display") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         impl QuietDisplay for DiscreteVerbosityDisplay { |  | ||||||
|             fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result { |  | ||||||
|                 write!(w, "quiet") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         impl VerboseDisplay for DiscreteVerbosityDisplay { |  | ||||||
|             fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result { |  | ||||||
|                 write!(w, "verbose") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let f = DiscreteVerbosityDisplay {}; |  | ||||||
|         assert_eq!(&OutputFormat::Display.formatted_string(&f), "display"); |  | ||||||
|         assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet"); |  | ||||||
|         assert_eq!( |  | ||||||
|             &OutputFormat::DisplayVerbose.formatted_string(&f), |  | ||||||
|             "verbose" |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,29 +1,12 @@ | |||||||
|  | use crate::cli::SettingType; | ||||||
| use console::style; | use console::style; | ||||||
| use indicatif::{ProgressBar, ProgressStyle}; | use indicatif::{ProgressBar, ProgressStyle}; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
|     hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize, |     hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize, | ||||||
|     transaction::Transaction, |     transaction::Transaction, | ||||||
| }; | }; | ||||||
| use solana_transaction_status::UiTransactionStatusMeta; | use solana_transaction_status::RpcTransactionStatusMeta; | ||||||
| use std::{collections::HashMap, fmt, io}; | use std::{fmt, io}; | ||||||
| 
 |  | ||||||
| pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String { |  | ||||||
|     if use_lamports_unit { |  | ||||||
|         let ess = if lamports == 1 { "" } else { "s" }; |  | ||||||
|         let unit = if show_unit { |  | ||||||
|             format!(" lamport{}", ess) |  | ||||||
|         } else { |  | ||||||
|             "".to_string() |  | ||||||
|         }; |  | ||||||
|         format!("{:?}{}", lamports, unit) |  | ||||||
|     } else { |  | ||||||
|         let sol = lamports_to_sol(lamports); |  | ||||||
|         let sol_str = format!("{:.9}", sol); |  | ||||||
|         let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.'); |  | ||||||
|         let unit = if show_unit { " SOL" } else { "" }; |  | ||||||
|         format!("{}{}", pretty_sol, unit) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // Pretty print a "name value"
 | // Pretty print a "name value"
 | ||||||
| pub fn println_name_value(name: &str, value: &str) { | pub fn println_name_value(name: &str, value: &str) { | ||||||
| @@ -44,17 +27,19 @@ pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fm | |||||||
|     writeln!(f, "{} {}", style(name).bold(), styled_value) |     writeln!(f, "{} {}", style(name).bold(), styled_value) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String { | pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) { | ||||||
|     let label = address_labels.get(pubkey); |     let description = match setting_type { | ||||||
|     match label { |         SettingType::Explicit => "", | ||||||
|         Some(label) => format!( |         SettingType::Computed => "(computed)", | ||||||
|             "{:.31} ({:.4}..{})", |         SettingType::SystemDefault => "(default)", | ||||||
|             label, |     }; | ||||||
|             pubkey, | 
 | ||||||
|             pubkey.split_at(pubkey.len() - 4).1 |     println!( | ||||||
|         ), |         "{} {} {}", | ||||||
|         None => pubkey.to_string(), |         style(name).bold(), | ||||||
|     } |         style(value), | ||||||
|  |         style(description).italic(), | ||||||
|  |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn println_signers( | pub fn println_signers( | ||||||
| @@ -83,7 +68,7 @@ pub fn println_signers( | |||||||
| pub fn write_transaction<W: io::Write>( | pub fn write_transaction<W: io::Write>( | ||||||
|     w: &mut W, |     w: &mut W, | ||||||
|     transaction: &Transaction, |     transaction: &Transaction, | ||||||
|     transaction_status: &Option<UiTransactionStatusMeta>, |     transaction_status: &Option<RpcTransactionStatusMeta>, | ||||||
|     prefix: &str, |     prefix: &str, | ||||||
| ) -> io::Result<()> { | ) -> io::Result<()> { | ||||||
|     let message = &transaction.message; |     let message = &transaction.message; | ||||||
| @@ -164,7 +149,7 @@ pub fn write_transaction<W: io::Write>( | |||||||
|         )?; |         )?; | ||||||
|         writeln!( |         writeln!( | ||||||
|             w, |             w, | ||||||
|             "{}  Fee: ◎{}", |             "{}  Fee: {} SOL", | ||||||
|             prefix, |             prefix, | ||||||
|             lamports_to_sol(transaction_status.fee) |             lamports_to_sol(transaction_status.fee) | ||||||
|         )?; |         )?; | ||||||
| @@ -181,7 +166,7 @@ pub fn write_transaction<W: io::Write>( | |||||||
|             if pre == post { |             if pre == post { | ||||||
|                 writeln!( |                 writeln!( | ||||||
|                     w, |                     w, | ||||||
|                     "{}  Account {} balance: ◎{}", |                     "{}  Account {} balance: {} SOL", | ||||||
|                     prefix, |                     prefix, | ||||||
|                     i, |                     i, | ||||||
|                     lamports_to_sol(*pre) |                     lamports_to_sol(*pre) | ||||||
| @@ -189,7 +174,7 @@ pub fn write_transaction<W: io::Write>( | |||||||
|             } else { |             } else { | ||||||
|                 writeln!( |                 writeln!( | ||||||
|                     w, |                     w, | ||||||
|                     "{}  Account {} balance: ◎{} -> ◎{}", |                     "{}  Account {} balance: {} SOL -> {} SOL", | ||||||
|                     prefix, |                     prefix, | ||||||
|                     i, |                     i, | ||||||
|                     lamports_to_sol(*pre), |                     lamports_to_sol(*pre), | ||||||
| @@ -197,15 +182,6 @@ pub fn write_transaction<W: io::Write>( | |||||||
|                 )?; |                 )?; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         if let Some(log_messages) = &transaction_status.log_messages { |  | ||||||
|             if !log_messages.is_empty() { |  | ||||||
|                 writeln!(w, "{}Log Messages:", prefix,)?; |  | ||||||
|                 for log_message in log_messages { |  | ||||||
|                     writeln!(w, "{}  {}", prefix, log_message,)?; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } else { |     } else { | ||||||
|         writeln!(w, "{}Status: Unavailable", prefix)?; |         writeln!(w, "{}Status: Unavailable", prefix)?; | ||||||
|     } |     } | ||||||
| @@ -215,7 +191,7 @@ pub fn write_transaction<W: io::Write>( | |||||||
| 
 | 
 | ||||||
| pub fn println_transaction( | pub fn println_transaction( | ||||||
|     transaction: &Transaction, |     transaction: &Transaction, | ||||||
|     transaction_status: &Option<UiTransactionStatusMeta>, |     transaction_status: &Option<RpcTransactionStatusMeta>, | ||||||
|     prefix: &str, |     prefix: &str, | ||||||
| ) { | ) { | ||||||
|     let mut w = Vec::new(); |     let mut w = Vec::new(); | ||||||
| @@ -234,32 +210,3 @@ pub fn new_spinner_progress_bar() -> ProgressBar { | |||||||
|     progress_bar.enable_steady_tick(100); |     progress_bar.enable_steady_tick(100); | ||||||
|     progress_bar |     progress_bar | ||||||
| } | } | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| mod test { |  | ||||||
|     use super::*; |  | ||||||
|     use solana_sdk::pubkey::Pubkey; |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn test_format_labeled_address() { |  | ||||||
|         let pubkey = Pubkey::default().to_string(); |  | ||||||
|         let mut address_labels = HashMap::new(); |  | ||||||
| 
 |  | ||||||
|         assert_eq!(format_labeled_address(&pubkey, &address_labels), pubkey); |  | ||||||
| 
 |  | ||||||
|         address_labels.insert(pubkey.to_string(), "Default Address".to_string()); |  | ||||||
|         assert_eq!( |  | ||||||
|             &format_labeled_address(&pubkey, &address_labels), |  | ||||||
|             "Default Address (1111..1111)" |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         address_labels.insert( |  | ||||||
|             pubkey.to_string(), |  | ||||||
|             "abcdefghijklmnopqrstuvwxyz1234567890".to_string(), |  | ||||||
|         ); |  | ||||||
|         assert_eq!( |  | ||||||
|             &format_labeled_address(&pubkey, &address_labels), |  | ||||||
|             "abcdefghijklmnopqrstuvwxyz12345 (1111..1111)" |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user