Compare commits

..

34 Commits

Author SHA1 Message Date
mergify[bot]
69e53ec92a Gossip log (#11555) (#11562)
Co-authored-by: Carl <carl@solana.com>
(cherry picked from commit 1b238dd63e)

Co-authored-by: carllin <wumu727@gmail.com>
2020-08-11 22:55:18 +00:00
mergify[bot]
b8ac76066c Move cluster slots update to separate thread (#11523) (#11558)
* Add cluster_slots_service

Co-authored-by: Carl <carl@solana.com>
(cherry picked from commit 7ef50a9352)

Co-authored-by: carllin <wumu727@gmail.com>
2020-08-11 21:24:34 +00:00
mergify[bot]
1a1d7744bd Add getTokenLargestAccounts to docs (#11560) (#11564)
(cherry picked from commit 697a0e2947)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-11 21:21:35 +00:00
mergify[bot]
61af485732 filter out old gossip pull requests (#11448) (#11553)
* init

* builds

* stats

* revert

* tests

* clippy

* add some jitter

* shorter jitter timer

* update

* fixup! update

* use saturating_sub

* fix filters

(cherry picked from commit 713851b68d)

Co-authored-by: anatoly yakovenko <anatoly@solana.com>
2020-08-11 19:42:32 +00:00
mergify[bot]
6a60d7bf8e Adapt RpcClient to recent token method changes (#11519) (#11548)
* Avoid skip_serializing_if since that breaks deserialization

* Adapt RpcClient to recent token method changes

(cherry picked from commit 17645ee20c)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-08-11 17:31:57 +00:00
mergify[bot]
53e917b54f Fix bad rent in Bank::deposit as if since epoch 0 (#10468) (#11539)
* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether

(cherry picked from commit 6c242f3fec)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-08-11 16:20:06 +00:00
mergify[bot]
4d49c99ac9 Fix simulateTransaction JSON-RPC docs (#11533) (#11535)
(cherry picked from commit f12fc66a69)

Co-authored-by: Justin Starry <justin@solana.com>
2020-08-11 10:26:20 +00:00
mergify[bot]
83597a5ce1 Fix solana CLI deploy (#11520) (#11530)
* Refresh blockhash for program writes and finalize transactions

* Refactor to use current api, eliminating an rpc call

* Review comment

(cherry picked from commit c0d6761f63)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-11 09:52:11 +00:00
mergify[bot]
1589a41178 Add config param to specify offset/length for single and program account info (bp #11515) (#11518)
* Add config param to specify offset/length for single and program account info (#11515)

* Add config param to specify dataSlice for account info and program accounts

* Use match instead of if

(cherry picked from commit 88ca04dbdb)

# Conflicts:
#	cli/src/cli.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-08-11 00:03:18 +00:00
mergify[bot]
23a381b995 Fix parsing of spl-token Mint (#11512) (#11514)
* Add failing test

* Fix jsonParsed mint

(cherry picked from commit da210ddd51)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-10 21:51:20 +00:00
mergify[bot]
eb7ac42b2e Return account data size with parsed accounts (#11506) (#11511)
(cherry picked from commit 1925b0bd0b)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-10 20:09:13 +00:00
mergify[bot]
88cf5e79f5 Unified signature for create_program_address (#11460) (#11509)
(cherry picked from commit 140b2392f6)

Co-authored-by: Jack May <jack@solana.com>
2020-08-10 18:31:51 +00:00
mergify[bot]
520453e1f3 Blockstore address signatures: handle slots that cross primary indexes, and refactor get_confirmed_signatures_for_address2 (#11497) (#11508)
* Freeze address-signature index in the middle of slot to show failure case

* Secondary filter on signature

* Use AddressSignatures iterator instead of manually decrementing slots

* Remove unused method

* Add metrics

* Add transaction-status-index doccumentation

(cherry picked from commit de5fb3ba0e)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-10 17:43:00 +00:00
mergify[bot]
27815555a1 Fallback to base64 account encoding if json parse fails (#11483) (#11488)
* Fallback to base64 account encoding if json parse fails

* Remove default binary conversion

(cherry picked from commit ebc45bd73f)

Co-authored-by: Justin Starry <justin@solana.com>
2020-08-09 16:15:09 +00:00
mergify[bot]
3e483314b6 Decode native-program and sysvar accounts (bp #11463) (#11485)
* Decode native-program and sysvar accounts (#11463)

* Pass pubkey in to account-decoder for sysvars

* Decode sysvar accounts

* Decode config accounts; move validator-info lower

* Decode stake accounts

* Review comments

* Stringify any account lamports and epochs that can be set to u64::MAX

(cherry picked from commit a9f76862fb)

# Conflicts:
#	Cargo.lock
#	account-decoder/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-08-09 09:18:17 +00:00
mergify[bot]
8a67504578 Add Binary64 option for account data (#11474) (#11481)
* Add Binary64 option for account data

* Decode into binary64

* Reword docs

(cherry picked from commit 068d23f298)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-08-09 06:50:24 +00:00
mergify[bot]
a1b238280b Return delegated amount as UiTokenAmount (#11475) (#11477)
* Return delegated amount as UiTokenAmount

* Omit delegate and delegatedAmount when none

(cherry picked from commit 88d8d3d02a)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-09 00:01:01 +00:00
Trent Nelson
f9e07f575e Bump version to v1.3.2 2020-08-08 06:26:20 +00:00
Trent Nelson
c8bad57455 Bump version to v1.3.1 2020-08-07 19:21:41 -06:00
mergify[bot]
71654c0457 Fix cbindgen compatibility (#11455) (#11459)
(cherry picked from commit 5a7e99f283)

Co-authored-by: Jack May <jack@solana.com>
2020-08-07 23:44:49 +00:00
mergify[bot]
f9d6fb48a4 Send votes from banking stage to vote listener (#11434) (#11454)
*  Send votes from banking stage to vote listener

Co-authored-by: Carl <carl@solana.com>
(cherry picked from commit 7e25130529)

Co-authored-by: carllin <wumu727@gmail.com>
2020-08-07 19:45:40 +00:00
mergify[bot]
fa9aa0a1d7 Token Accounts: return ui_amount, decimals with decoded account (#11407) (#11453)
* Return ui_amount, decimals from token client methods

* Return ui_amount, decimals in RPC jsonParsed token accounts

* Fixup docs

* Return ui_amount, decimals in pubsub jsonParsed token accounts

* Remove unnecessary duplicate struct

* StringAmount rename

(cherry picked from commit b7c2681903)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-07 19:05:35 +00:00
mergify[bot]
9758ebfc99 Only run web3.js/explorer CI when targeting master branch (#11440)
(cherry picked from commit a3165c6a61)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-08-07 06:24:42 +00:00
mergify[bot]
8be23a2bb2 Add address-based lower bound to get_confirmed_signatures_for_address2 loop (#11426) (#11432)
(cherry picked from commit 5530ee4c95)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-07 00:06:44 +00:00
mergify[bot]
4ff9a6910d Fix blockstore empty panic (#11423) (#11430)
* Add panicking test

* Add failing test: fresh transaction-status column shouldn't point at valid root 0

* Prevent transaction status match outside of primary-index bounds

* Initialize transaction-status and address-signature primer entries with Slot::MAX

* Revert "Add failing test: fresh transaction-status column shouldn't point at valid root 0"

This reverts commit cbad2a9fae.

* Revert "Initialize transaction-status and address-signature primer entries with Slot::MAX"

This reverts commit ffaeac0669.

(cherry picked from commit 1061b50665)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-08-06 23:39:01 +00:00
mergify[bot]
fd192e3641 Link fix (#11368) (#11425)
* fixes logo

* cleans up homepage on docs

* adds icon files and tightens margins

* cleans up sidenav, adds top nav items

* fixes a link

* removes icon files

Co-authored-by: Dan Albert <dan@solana.com>
(cherry picked from commit 14dcaaee6c)

Co-authored-by: Raj Gokal <rajivgokal@gmail.com>
2020-08-06 19:37:04 +00:00
mergify[bot]
a8de467ef8 Realloc not supported (#11418)
(cherry picked from commit bc4c5c5a97)

Co-authored-by: Jack May <jack@solana.com>
2020-08-06 16:24:14 +00:00
Michael Vines
1a186beb5c Update lib.rs
(cherry picked from commit 5a63c9d535)
2020-08-06 07:58:05 -07:00
Michael Vines
66a21ed65e Enable cross program support in mainnet-beta epoch 63
(cherry picked from commit c9b1d08218)
2020-08-06 07:58:05 -07:00
mergify[bot]
1a42a40492 RPC: Plug getConfirmedSignaturesForAddress2 into bigtable storage (bp #11395) (#11406)
* Plug getConfirmedSignaturesForAddress2 into bigtable storage

(cherry picked from commit 4222932e08)

* Upgrade help description

(cherry picked from commit 9abb7db5f8)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-08-06 07:14:46 +00:00
mergify[bot]
5841e4d665 Long-term ledger storage with BigTable (bp #11222) (#11392)
* ledger-storage-bigtable boilerplate

(cherry picked from commit 9d2293bb32)

* $ wget https://pki.goog/roots.pem -O pki-goog-roots.pem

(cherry picked from commit 1617a025ce)

* Add access_token module

(cherry picked from commit 59d266a111)

* Add root_ca_certificate

(cherry picked from commit faa016e4b7)

* Add build-proto

(cherry picked from commit c31e1f5bf0)

* UiTransactionEncoding is now copy

(cherry picked from commit 494968be66)

* Increase timeout

(cherry picked from commit 57dfebc5ba)

* Add build-proto/build.sh output

(cherry picked from commit 54dae6ba2c)

* Supress doctest errors

(cherry picked from commit 019c75797d)

* Add compression

(cherry picked from commit 243e05d59f)

* Add bigtable

(cherry picked from commit 6e0353965a)

* Add configuration info

(cherry picked from commit 98cca1e774)

* Add ledger-tool bigtable subcommands

(cherry picked from commit f9049d6ee4)

# Conflicts:
#	ledger-tool/Cargo.toml

* Make room for tokio 0.2

(cherry picked from commit b876fb84ba)

# Conflicts:
#	core/Cargo.toml

* Setup a tokio 0.2 runtime for RPC usage

(cherry picked from commit 0e02740565)

# Conflicts:
#	core/Cargo.toml

* Plumb Bigtable ledger storage into the RPC subsystem

(cherry picked from commit dfae9a9864)

# Conflicts:
#	core/Cargo.toml

* Add RPC transaction history design

(cherry picked from commit e56ea138c7)

* Simplify access token refreshing

(cherry picked from commit 1f7af14386)

* Report block status more frequently

(cherry picked from commit 22c46ebf96)

* after -> before

(cherry picked from commit 227ea934ff)

* Rebase

* Cargo.lock

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-08-06 04:06:44 +00:00
mergify[bot]
6542a04521 Mark token-specific rpcs as unstable (#11402)
(cherry picked from commit 7430896c79)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-08-06 04:00:18 +00:00
mergify[bot]
5c27009758 Force program address off the curve (#11323) (#11398)
(cherry picked from commit 03263c850a)

Co-authored-by: Jack May <jack@solana.com>
2020-08-06 01:00:42 +00:00
mergify[bot]
888f3522d8 Add getConfirmedSignaturesForAddress2 RPC method (bp #11259) (#11394)
* Add getConfirmedSignaturesForAddress2 RPC method

(cherry picked from commit 1b2276520b)

* Reimplement transaction-history command with getConfirmedSignaturesForAddress2

(cherry picked from commit 087fd32ce3)

* Rework get_confirmed_signatures_for_address2

(cherry picked from commit a11f137810)

* Rename startAfter to before

(cherry picked from commit 02c0981ecf)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-08-05 22:47:50 +00:00
1640 changed files with 103463 additions and 315269 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,7 +1,12 @@
{ {
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f", "_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
"_comment": "These credentials are encrypted and pose no risk",
"environment": { "environment": {
"CODECOV_TOKEN": "EJ[1:KToenD1Sr3w82lHGxz1n+j3hwNlLk/1pYrjZHlvY6kE=:hN1Q25omtJ+4yYVn+qzIsPLKT3O6J9XN:DMLNLXi/pkWgvwF6gNIcNF222sgsRR9LnwLZYj0P0wGj7q6w8YQnd1Rskj+sRroI/z5pQg==]" "CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]",
"CRATES_IO_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:GGRTYDjMXksevzR6kq4Jx+FaIQZz50RU:xkbwDxcgoCyU+aT2tiI9mymigrEl6YiOr3axe3aX70ELIBKbCdPGilXP/wixvKi94g2u]",
"GEOLOCATION_API_KEY": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:U2PZLi5MU3Ru/zK1SilianEeizcMvxml:AJKf2OAtDHmJh0KyXrBnNnistItZvVVP3cZ7ZLtrVupjmWN/PzmKwSsXeCNObWS+]",
"GITHUB_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:0NJNlpD/O19mvOakCGBYDhIDfySxWFSC:Dz4NXv9x6ncRQ1u9sVoWOcqmkg0sI09qmefghB0GXZgPcFGgn6T0mw7ynNnbUvjyH8dLruKHauk=]",
"INFLUX_DATABASE": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:SzwHIeOVpmbTcGQOGngoFgYumsLZJUGq:t7Rpk49njsWvoM+ztv5Uwuiz]",
"INFLUX_PASSWORD": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:/MUs+q7pdGrUjzwcq+6pgIFxur4hxdqu:am22z2E2dtmw1f1J1Mq5JLcUHZsrEjQAJ0pp21M4AZeJbNO6bVb44d9zSkHj7xdN6U+GNlCk+wU=]",
"INFLUX_USERNAME": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:XjghH20xGVWro9B+epGlJaJcW8Wze0Bi:ZIdOtXudTY5TqKseDU7gVvQXfmXV99Xh]"
} }
} }

View File

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

View File

@@ -11,32 +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
if [[ $BUILDKITE_LABEL = "stable-perf" ]]; then
rm -rf target/release
fi
) )

46
.github/stale.yml vendored
View File

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

6
.gitignore vendored
View File

@@ -1,3 +1,7 @@
/docs/html/
/docs/src/tests.ok
/docs/src/cli/usage.md
/docs/src/.gitbook/assets/*.svg
/farf/ /farf/
/solana-release/ /solana-release/
/solana-release.tar.bz2 /solana-release.tar.bz2
@@ -10,8 +14,6 @@
/config/ /config/
.cache
# log files # log files
*.log *.log
log-*.txt log-*.txt

View File

@@ -50,35 +50,27 @@ pull_request_rules:
label: label:
add: add:
- automerge - automerge
- name: v1.4 backport - name: v1.1 backport
conditions: conditions:
- label=v1.4 - label=v1.1
actions: actions:
backport: backport:
ignore_conflicts: true ignore_conflicts: true
branches: branches:
- v1.4 - v1.1
- name: v1.5 backport - name: v1.2 backport
conditions: conditions:
- label=v1.5 - label=v1.2
actions: actions:
backport: backport:
ignore_conflicts: true ignore_conflicts: true
branches: branches:
- v1.5 - v1.2
- name: v1.6 backport - name: v1.3 backport
conditions: conditions:
- label=v1.6 - label=v1.3
actions: actions:
backport: backport:
ignore_conflicts: true ignore_conflicts: true
branches: branches:
- v1.6 - v1.3
- name: v1.7 backport
conditions:
- label=v1.7
actions:
backport:
ignore_conflicts: true
branches:
- v1.7

View File

@@ -34,8 +34,6 @@ jobs:
- stable - stable
install: install:
- source ci/rust-version.sh - source ci/rust-version.sh
- PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
- readlink -f .
script: script:
- source ci/env.sh - source ci/env.sh
- ci/publish-tarball.sh - ci/publish-tarball.sh
@@ -73,7 +71,7 @@ jobs:
language: node_js language: node_js
node_js: node_js:
- "lts/*" - "node"
cache: cache:
directories: directories:
@@ -126,8 +124,6 @@ jobs:
- ~/.npm - ~/.npm
before_install: before_install:
- source ci/env.sh
- .travis/channel_restriction.sh edge beta || travis_terminate 0
- .travis/affects.sh docs/ .travis || travis_terminate 0 - .travis/affects.sh docs/ .travis || travis_terminate 0
- cd docs/ - cd docs/
- source .travis/before_install.sh - source .travis/before_install.sh

View File

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

View File

@@ -232,7 +232,7 @@ confused with 3-letter acronyms.
Solana's architecture is described by docs generated from markdown files in Solana's architecture is described by docs generated from markdown files in
the `docs/src/` directory, maintained by an *editor* (currently @garious). To the `docs/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to include it in the add a design proposal, you'll need to include it in the
[Accepted Design Proposals](https://docs.solana.com/proposals/accepted-design-proposals) [Accepted Design Proposals](https://docs.solana.com/proposals)
section of the Solana docs. Here's the full process: section of the Solana docs. Here's the full process:
1. Propose a design by creating a PR that adds a markdown document to the 1. Propose a design by creating a PR that adds a markdown document to the

3793
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,21 @@
[workspace] [workspace]
members = [ members = [
"accounts-cluster-bench",
"bench-exchange", "bench-exchange",
"bench-streamer", "bench-streamer",
"bench-tps", "bench-tps",
"accounts-bench", "accounts-bench",
"banking-bench", "banking-bench",
"banks-client",
"banks-interface",
"banks-server",
"clap-utils", "clap-utils",
"cli-config", "cli-config",
"cli-output",
"client", "client",
"core", "core",
"dos", "dos",
"download-utils", "download-utils",
"faucet", "faucet",
"frozen-abi",
"perf", "perf",
"validator", "validator",
"genesis", "genesis",
"genesis-utils", "genesis-programs",
"gossip", "gossip",
"install", "install",
"keygen", "keygen",
@@ -30,34 +24,29 @@ members = [
"local-cluster", "local-cluster",
"logger", "logger",
"log-analyzer", "log-analyzer",
"merkle-root-bench",
"merkle-tree", "merkle-tree",
"stake-o-matic",
"storage-bigtable", "storage-bigtable",
"storage-proto",
"streamer", "streamer",
"measure", "measure",
"metrics", "metrics",
"net-shaper", "net-shaper",
"notifier", "notifier",
"poh",
"poh-bench", "poh-bench",
"program-test",
"programs/secp256k1",
"programs/bpf_loader", "programs/bpf_loader",
"programs/budget",
"programs/config", "programs/config",
"programs/exchange", "programs/exchange",
"programs/failure", "programs/failure",
"programs/noop", "programs/noop",
"programs/ownable", "programs/ownable",
"programs/stake", "programs/stake",
"programs/vest",
"programs/vote", "programs/vote",
"remote-wallet", "remote-wallet",
"rpc", "ramp-tps",
"runtime", "runtime",
"runtime/store-tool",
"sdk", "sdk",
"sdk/cargo-build-bpf",
"sdk/cargo-test-bpf",
"scripts", "scripts",
"stake-accounts", "stake-accounts",
"stake-monitor", "stake-monitor",
@@ -68,6 +57,7 @@ members = [
"upload-perf", "upload-perf",
"net-utils", "net-utils",
"version", "version",
"vote-signer",
"cli", "cli",
"rayon-threadlimit", "rayon-threadlimit",
"watchtower", "watchtower",
@@ -76,6 +66,3 @@ members = [
exclude = [ exclude = [
"programs/bpf", "programs/bpf",
] ]
[profile.dev]
split-debuginfo = "unpacked"

View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://solana.com"> <a href="https://solana.com">
<img alt="Solana" src="https://i.imgur.com/uBVzyX3.png" width="250" /> <img alt="Solana" src="https://i.imgur.com/OMnvVEz.png" width="250" />
</a> </a>
</p> </p>
@@ -19,7 +19,7 @@ $ source $HOME/.cargo/env
$ rustup component add rustfmt $ rustup component add rustfmt
``` ```
Please sure you are always using the latest stable rust version by running: If your rustc version is lower than 1.39.0, please update it:
```bash ```bash
$ rustup update $ rustup update
@@ -29,13 +29,7 @@ On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc
```bash ```bash
$ sudo apt-get update $ sudo apt-get update
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang make $ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
```
On Mac M1s, make sure you set up your terminal & homebrew [to use](https://5balloons.info/correct-way-to-install-and-use-homebrew-on-m1-macs/) Rosetta. You can install it with:
```bash
$ softwareupdate --install-rosetta
``` ```
## **2. Download the source code.** ## **2. Download the source code.**
@@ -65,11 +59,10 @@ $ 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/cluster/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
@@ -113,41 +106,4 @@ send us that patch!
# Disclaimer # Disclaimer
All claims, content, designs, algorithms, estimates, roadmaps, All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
specifications, and performance measurements described in this project
are done with the Solana Foundation's ("SF") best efforts. It is up to
the reader to check and validate their accuracy and truthfulness.
Furthermore nothing in this project constitutes a solicitation for
investment.
Any content produced by SF or developer resources that SF provides, are
for educational and inspiration purposes only. SF does not encourage,
induce or sanction the deployment, integration or use of any such
applications (including the code comprising the Solana blockchain
protocol) in violation of applicable laws or regulations and hereby
prohibits any such deployment, integration or use. This includes use of
any such applications by the reader (a) in violation of export control
or sanctions laws of the United States or any other applicable
jurisdiction, (b) if the reader is located in or ordinarily resident in
a country or territory subject to comprehensive sanctions administered
by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the
reader is or is working on behalf of a Specially Designated National
(SDN) or a person subject to similar blocking or denied party
prohibitions.
The reader should be aware that U.S. export control and sanctions laws
prohibit U.S. persons (and other persons that are subject to such laws)
from transacting with persons in certain countries and territories or
that are on the SDN list. As a project based primarily on open-source
software, it is possible that such sanctioned persons may nevertheless
bypass prohibitions, obtain the code comprising the Solana blockchain
protocol (or other project code or applications) and deploy, integrate,
or otherwise use it. Accordingly, there is a risk to individuals that
other persons using the Solana blockchain protocol may be sanctioned
persons and that transactions with such persons would be a violation of
U.S. export controls and sanctions law. This risk applies to
individuals, organizations, and other ecosystem participants that
deploy, integrate, or use the Solana blockchain protocol code directly
(e.g., as a node operator), and individuals that transact on the Solana
blockchain through light clients, third party interfaces, and/or wallet
software.

View File

@@ -107,15 +107,11 @@ Alternatively use the Github UI.
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 version 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. Fill the release notes. 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. If this is a patch release, review all the commits since the previous release on this branch and add details as 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.
1. Ensure all desired commits (usually backports) are landed on the branch by now. 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. Ensure the release is marked **"This is a pre-release"**. This flag will need to be be removed manually after confirming the the Linux binary artifacts appear at a later step. 1. Go back into edit the release and click "Publish release" when ready.
1. Go back into edit the release and click "Publish release" while being marked as a pre-release.
1. Confirm there is new git tag with intended version number at the intended revision after running `git fetch` locally.
### Update release branch with the next patch version ### Update release branch with the next patch version
@@ -135,8 +131,7 @@ Alternatively use the Github UI.
1. Open a PR against origin/vX.Y and then merge the PR after passing CI. 1. Open a PR against origin/vX.Y and then merge the PR after passing CI.
### Prepare for the next release ### 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 notes. This allows people to incrementally add new release notes until it's time 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. Also, point the branch field to the same branch and mark the relese as **"This is a pre-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 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. unresolved issues still in the `X.Y.Z` milestone, then close the `X.Y.Z` milestone.
@@ -152,5 +147,5 @@ appearing. To check for progress:
[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. [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.
If this happens and the error is non-fatal, click "Retry" on the "publish crate" job If this happens and the error is non-fatal, click "Retry" on the "publish crate" job
### Update software on devnet.solana.com/testnet.solana.com/mainnet-beta.solana.com ### Update software on devnet.solana.com/testnet.solama.com/mainnet-beta.solana.com
See the documentation at https://github.com/solana-labs/cluster-ops/ See the documentation at https://github.com/solana-labs/cluster-ops/

View File

@@ -1,169 +0,0 @@
# Security Policy
1. [Reporting security problems](#reporting)
4. [Security Bug Bounties](#bounty)
2. [Incident Response Process](#process)
<a name="reporting"></a>
## Reporting security problems to Solana
**DO NOT CREATE AN ISSUE** to report a security problem. Instead, please send an
email to security@solana.com and provide your github username so we can add you
to a new draft security advisory for further discussion.
Expect a response as fast as possible, within one business day at the latest.
<a name="bounty"></a>
## Security Bug Bounties
We offer bounties for critical security issues. Please see below for more details.
Loss of Funds:
$500,000 USD in locked SOL tokens (locked for 12 months)
* Theft of funds without users signature from any account
* Theft of funds without users interaction in system, token, stake, vote programs
* Theft of funds that requires users signature - creating a vote program that drains the delegated stakes.
Consensus/Safety Violations:
$250,000 USD in locked SOL tokens (locked for 12 months)
* Consensus safety violation
* Tricking a validator to accept an optimistic confirmation or rooted slot without a double vote, etc..
Other Attacks:
$100,000 USD in locked SOL tokens (locked for 12 months)
* Protocol liveness attacks,
* Eclipse attacks,
* Remote attacks that partition the network,
DoS Attacks:
$25,000 USD in locked SOL tokens (locked for 12 months)
* Remote resource exaustion via Non-RPC protocols
RPC DoS/Crashes:
$5,000 USD in locked SOL tokens (locked for 12 months)
* RPC attacks
Out of Scope:
The following components are out of scope for the bounty program
* Metrics: `/metrics` in the monorepo as well as https://metrics.solana.com
* Explorer: `/explorer` in the monorepo as well as https://explorer.solana.com
* Any encrypted credentials, auth tokens, etc. checked into the repo
* Bugs in dependencies. Please take them upstream!
* Attacks that require social engineering
Eligibility:
* The participant submitting the bug report shall follow the process outlined within this document
* Valid exploits can be eligible even if they are not successfully executed on the cluster
* Multiple submissions for the same class of exploit are still eligible for compensation, though may be compensated at a lower rate, however these will be assessed on a case-by-case basis
* Participants must complete KYC and sign the participation agreement here when the registrations are open https://solana.com/validator-registration. Security exploits will still be assessed and open for submission at all times. This needs only be done prior to distribution of tokens.
Payment of Bug Bounties:
* Payments for eligible bug reports are distributed monthly.
* Bounties for all bug reports submitted in a given month are paid out in the middle of the
following month.
* The SOL/USD conversion rate used for payments is the market price at the end of
the last day of the month for the month in which the bug was submitted.
* The reference for this price is the Closing Price given by Coingecko.com on
that date given here:
https://www.coingecko.com/en/coins/solana/historical_data/usd#panel
* For example, for all bugs submitted in March 2021, the SOL/USD price for bug
payouts is the Close price on 2021-03-31 of $19.49. This applies to all bugs
submitted in March 2021, to be paid in mid-April 2021.
* Bug bounties are paid out in
[stake accounts](https://solana.com/staking) with a
[lockup](https://docs.solana.com/staking/stake-accounts#lockups)
expiring 12 months from the last day of the month in which the bug was submitted.
<a name="process"></a>
## Incident Response Process
In case an incident is discovered or reported, the following process will be
followed to contain, respond and remediate:
### 1. Establish a new draft security advisory
In response to an email to security@solana.com, a member of the `solana-labs/admins` group will
1. Create a new draft security advisory for the incident at https://github.com/solana-labs/solana/security/advisories
1. Add the reporter's github user and the `solana-labs/security-incident-response` group to the draft security advisory
1. Create a private fork of the repository (grey button towards the bottom of the page)
1. Respond to the reporter by email, sharing a link to the draft security advisory
### 2. Triage
Within the draft security advisory, discuss and determine the severity of the
issue. If necessary, members of the `solana-labs/security-incident-response`
group may add other github users to the advisory to assist.
If it is determined that this not a critical network issue then the advisory
should be closed and if more follow-up is required a normal Solana public github
issue should be created.
### 3. Prepare Fixes
For the affected branches, typically all three (edge, beta and stable), prepare
a fix for the issue and push them to the corresponding branch in the private
repository associated with the draft security advisory.
There is no CI available in the private repository so you must build from source
and manually verify fixes.
Code review from the reporter is ideal, as well as from multiple members of the
core development team.
### 4. Notify Security Group Validators
Once an ETA is available for the fix, a member of the
`solana-labs/security-incident-response` group should notify the validators so
they can prepare for an update using the "Solana Red Alert" notification system.
The teams are all over the world and it's critical to provide actionable
information at the right time. Don't be the person that wakes everybody up at
2am when a fix won't be available for hours.
### 5. Ship the patch
Once the fix is accepted, a member of the
`solana-labs/security-incident-response` group should prepare a single patch
file for each affected branch. The commit title for the patch should only
contain the advisory id, and not disclose any further details about the
incident.
Copy the patches to https://release.solana.com/ under a subdirectory named after
the advisory id (example:
https://release.solana.com/GHSA-hx59-f5g4-jghh/v1.4.patch). Contact a member of
the `solana-labs/admins` group if you require access to release.solana.com
Using the "Solana Red Alert" channel:
1. Notify validators that there's an issue and a patch will be provided in X minutes
2. If X minutes expires and there's no patch, notify of the delay and provide a
new ETA
3. Provide links to patches of https://release.solana.com/ for each affected branch
Validators can be expected to build the patch from source against the latest
release for the affected branch.
Since the software version will not change after the patch is applied, request
that each validator notify in the existing channel once they've updated. Manually
monitor the roll out until a sufficient amount of stake has updated - typically
at least 33.3% or 66.6% depending on the issue.
### 6. Public Disclosure and Release
Once the fix has been deployed to the security group validators, the patches from the security
advisory may be merged into the main source repository. A new official release
for each affected branch should be shipped and all validators requested to
upgrade as quickly as possible.
### 7. Security Advisory Bounty Accounting and Cleanup
If this issue is eligible for a bounty, prefix the title of the security
advisory with one of the following, depending on the severity:
* `[Bounty Category: Critical: Loss of Funds]`
* `[Bounty Category: Critical: Loss of Availability]`
* `[Bounty Category: Critical: DoS]`
* `[Bounty Category: Critical: Other]`
* `[Bounty Category: Non-critical]`
* `[Bounty Category: RPC]`
Confirm with the reporter that they agree with the severity assessment, and
discuss as required to reach a conclusion.
We currently do not use the Github workflow to publish security advisories.
Once the issue and fix have been disclosed, and a bounty category is assessed if
appropriate, the GitHub security advisory is no longer needed and can be closed.
Bounties are currently awarded once a quarter (TODO: link to this process, or
inline the workflow)

View File

@@ -1,11 +1,10 @@
[package] [package]
name = "solana-account-decoder" name = "solana-account-decoder"
version = "1.7.1" version = "1.3.2"
description = "Solana account decoder" description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana" repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/" homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-account-decoder"
license = "Apache-2.0" license = "Apache-2.0"
edition = "2018" edition = "2018"
@@ -16,16 +15,15 @@ bs58 = "0.3.1"
bv = "0.11.1" bv = "0.11.1"
Inflector = "0.11.4" Inflector = "0.11.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
serde = "1.0.122" solana-config-program = { path = "../programs/config", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-stake-program = { path = "../programs/stake", version = "1.3.2" }
solana-vote-program = { path = "../programs/vote", version = "1.3.2" }
spl-token-v1-0 = { package = "spl-token", version = "1.0.6", features = ["skip-no-mangle"] }
serde = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.56" serde_json = "1.0.56"
solana-config-program = { path = "../programs/config", version = "=1.7.1" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
solana-stake-program = { path = "../programs/stake", version = "=1.7.1" }
solana-vote-program = { path = "../programs/vote", version = "=1.7.1" }
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
thiserror = "1.0" thiserror = "1.0"
zstd = "0.5.1"
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,11 +1,9 @@
#![allow(clippy::integer_arithmetic)]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub mod parse_account_data; pub mod parse_account_data;
pub mod parse_bpf_loader;
pub mod parse_config; pub mod parse_config;
pub mod parse_nonce; pub mod parse_nonce;
pub mod parse_stake; pub mod parse_stake;
@@ -14,20 +12,11 @@ pub mod parse_token;
pub mod parse_vote; pub mod parse_vote;
pub mod validator_info; pub mod validator_info;
use { use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount};
crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount}, use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey};
solana_sdk::{ use std::str::FromStr;
account::ReadableAccount, account::WritableAccount, clock::Epoch,
fee_calculator::FeeCalculator, pubkey::Pubkey,
},
std::{
io::{Read, Write},
str::FromStr,
},
};
pub type StringAmount = String; pub type StringAmount = String;
pub type StringDecimals = String;
/// A duplicate representation of an Account for pretty JSON serialization /// A duplicate representation of an Account for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@@ -43,104 +32,67 @@ pub struct UiAccount {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)] #[serde(rename_all = "camelCase", untagged)]
pub enum UiAccountData { pub enum UiAccountData {
LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility Binary(String),
Json(ParsedAccount), Json(ParsedAccount),
Binary(String, UiAccountEncoding), Binary64(String),
} }
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum UiAccountEncoding { pub enum UiAccountEncoding {
Binary, // Legacy. Retained for RPC backwards compatibility Binary,
Base58,
Base64,
JsonParsed, JsonParsed,
#[serde(rename = "base64+zstd")] Binary64,
Base64Zstd,
} }
impl UiAccount { impl UiAccount {
pub fn encode<T: ReadableAccount>( pub fn encode(
pubkey: &Pubkey, pubkey: &Pubkey,
account: &T, account: Account,
encoding: UiAccountEncoding, encoding: UiAccountEncoding,
additional_data: Option<AccountAdditionalData>, additional_data: Option<AccountAdditionalData>,
data_slice_config: Option<UiDataSliceConfig>, data_slice_config: Option<UiDataSliceConfig>,
) -> Self { ) -> Self {
let data = match encoding { let data = match encoding {
UiAccountEncoding::Binary => UiAccountData::LegacyBinary( UiAccountEncoding::Binary => UiAccountData::Binary(
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(), bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
), ),
UiAccountEncoding::Base58 => UiAccountData::Binary( UiAccountEncoding::Binary64 => UiAccountData::Binary64(base64::encode(slice_data(
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(), &account.data,
encoding, data_slice_config,
), ))),
UiAccountEncoding::Base64 => UiAccountData::Binary(
base64::encode(slice_data(&account.data(), data_slice_config)),
encoding,
),
UiAccountEncoding::Base64Zstd => {
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
match encoder
.write_all(slice_data(&account.data(), data_slice_config))
.and_then(|()| encoder.finish())
{
Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
Err(_) => UiAccountData::Binary(
base64::encode(slice_data(&account.data(), data_slice_config)),
UiAccountEncoding::Base64,
),
}
}
UiAccountEncoding::JsonParsed => { UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) = if let Ok(parsed_data) =
parse_account_data(pubkey, &account.owner(), &account.data(), additional_data) parse_account_data(pubkey, &account.owner, &account.data, additional_data)
{ {
UiAccountData::Json(parsed_data) UiAccountData::Json(parsed_data)
} else { } else {
UiAccountData::Binary( UiAccountData::Binary64(base64::encode(&account.data))
base64::encode(&account.data()),
UiAccountEncoding::Base64,
)
} }
} }
}; };
UiAccount { UiAccount {
lamports: account.lamports(), lamports: account.lamports,
data, data,
owner: account.owner().to_string(), owner: account.owner.to_string(),
executable: account.executable(), executable: account.executable,
rent_epoch: account.rent_epoch(), rent_epoch: account.rent_epoch,
} }
} }
pub fn decode<T: WritableAccount>(&self) -> Option<T> { pub fn decode(&self) -> Option<Account> {
let data = match &self.data { let data = match &self.data {
UiAccountData::Json(_) => None, UiAccountData::Json(_) => None,
UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(), UiAccountData::Binary(blob) => bs58::decode(blob).into_vec().ok(),
UiAccountData::Binary(blob, encoding) => match encoding { UiAccountData::Binary64(blob) => base64::decode(blob).ok(),
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
UiAccountEncoding::Base64Zstd => base64::decode(blob)
.ok()
.map(|zstd_data| {
let mut data = vec![];
zstd::stream::read::Decoder::new(zstd_data.as_slice())
.and_then(|mut reader| reader.read_to_end(&mut data))
.map(|_| data)
.ok()
})
.flatten(),
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
},
}?; }?;
Some(T::create( Some(Account {
self.lamports, lamports: self.lamports,
data, data,
Pubkey::from_str(&self.owner).ok()?, owner: Pubkey::from_str(&self.owner).ok()?,
self.executable, executable: self.executable,
self.rent_epoch, rent_epoch: self.rent_epoch,
)) })
} }
} }
@@ -190,7 +142,6 @@ fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use solana_sdk::account::{Account, AccountSharedData};
#[test] #[test]
fn test_slice_data() { fn test_slice_data() {
@@ -219,27 +170,4 @@ mod test {
}); });
assert_eq!(slice_data(&data, slice_config), &[] as &[u8]); assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
} }
#[test]
fn test_base64_zstd() {
let encoded_account = UiAccount::encode(
&Pubkey::default(),
&AccountSharedData::from(Account {
data: vec![0; 1024],
..Account::default()
}),
UiAccountEncoding::Base64Zstd,
None,
None,
);
assert!(matches!(
encoded_account.data,
UiAccountData::Binary(_, UiAccountEncoding::Base64Zstd)
));
let decoded_account = encoded_account.decode::<Account>().unwrap();
assert_eq!(decoded_account.data(), &vec![0; 1024]);
let decoded_account = encoded_account.decode::<AccountSharedData>().unwrap();
assert_eq!(decoded_account.data(), &vec![0; 1024]);
}
} }

View File

@@ -1,10 +1,9 @@
use crate::{ use crate::{
parse_bpf_loader::parse_bpf_upgradeable_loader,
parse_config::parse_config, parse_config::parse_config,
parse_nonce::parse_nonce, parse_nonce::parse_nonce,
parse_stake::parse_stake, parse_stake::parse_stake,
parse_sysvar::parse_sysvar, parse_sysvar::parse_sysvar,
parse_token::{parse_token, spl_token_id_v2_0}, parse_token::{parse_token, spl_token_id_v1_0},
parse_vote::parse_vote, parse_vote::parse_vote,
}; };
use inflector::Inflector; use inflector::Inflector;
@@ -14,19 +13,14 @@ use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
lazy_static! { lazy_static! {
static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id(); static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id(); static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id(); static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = { pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new(); let mut m = HashMap::new();
m.insert(
*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
ParsableAccount::BpfUpgradeableLoader,
);
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config); m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce); m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken); m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
@@ -66,7 +60,6 @@ pub struct ParsedAccount {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum ParsableAccount { pub enum ParsableAccount {
BpfUpgradeableLoader,
Config, Config,
Nonce, Nonce,
SplToken, SplToken,
@@ -88,12 +81,9 @@ pub fn parse_account_data(
) -> Result<ParsedAccount, ParseAccountError> { ) -> Result<ParsedAccount, ParseAccountError> {
let program_name = PARSABLE_PROGRAM_IDS let program_name = PARSABLE_PROGRAM_IDS
.get(program_id) .get(program_id)
.ok_or(ParseAccountError::ProgramNotParsable)?; .ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let additional_data = additional_data.unwrap_or_default(); let additional_data = additional_data.unwrap_or_default();
let parsed_json = match program_name { let parsed_json = match program_name {
ParsableAccount::BpfUpgradeableLoader => {
serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
}
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?, ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?, ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::SplToken => { ParsableAccount::SplToken => {
@@ -121,14 +111,14 @@ mod test {
#[test] #[test]
fn test_parse_account_data() { fn test_parse_account_data() {
let account_pubkey = solana_sdk::pubkey::new_rand(); let account_pubkey = Pubkey::new_rand();
let other_program = solana_sdk::pubkey::new_rand(); let other_program = Pubkey::new_rand();
let data = vec![0; 4]; let data = vec![0; 4];
assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err()); assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
let vote_state = VoteState::default(); let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()]; let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::new_current(vote_state); let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap(); VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let parsed = parse_account_data( let parsed = parse_account_data(
&account_pubkey, &account_pubkey,

View File

@@ -1,181 +0,0 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
UiAccountData, UiAccountEncoding,
};
use bincode::{deserialize, serialized_size};
use solana_sdk::{bpf_loader_upgradeable::UpgradeableLoaderState, pubkey::Pubkey};
pub fn parse_bpf_upgradeable_loader(
data: &[u8],
) -> Result<BpfUpgradeableLoaderAccountType, ParseAccountError> {
let account_state: UpgradeableLoaderState = deserialize(data).map_err(|_| {
ParseAccountError::AccountNotParsable(ParsableAccount::BpfUpgradeableLoader)
})?;
let parsed_account = match account_state {
UpgradeableLoaderState::Uninitialized => BpfUpgradeableLoaderAccountType::Uninitialized,
UpgradeableLoaderState::Buffer { authority_address } => {
let offset = if authority_address.is_some() {
UpgradeableLoaderState::buffer_data_offset().unwrap()
} else {
// This case included for code completeness; in practice, a Buffer account will
// always have authority_address.is_some()
UpgradeableLoaderState::buffer_data_offset().unwrap()
- serialized_size(&Pubkey::default()).unwrap() as usize
};
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: authority_address.map(|pubkey| pubkey.to_string()),
data: UiAccountData::Binary(
base64::encode(&data[offset as usize..]),
UiAccountEncoding::Base64,
),
})
}
UpgradeableLoaderState::Program {
programdata_address,
} => BpfUpgradeableLoaderAccountType::Program(UiProgram {
program_data: programdata_address.to_string(),
}),
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} => {
let offset = if upgrade_authority_address.is_some() {
UpgradeableLoaderState::programdata_data_offset().unwrap()
} else {
UpgradeableLoaderState::programdata_data_offset().unwrap()
- serialized_size(&Pubkey::default()).unwrap() as usize
};
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: upgrade_authority_address.map(|pubkey| pubkey.to_string()),
data: UiAccountData::Binary(
base64::encode(&data[offset as usize..]),
UiAccountEncoding::Base64,
),
})
}
};
Ok(parsed_account)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum BpfUpgradeableLoaderAccountType {
Uninitialized,
Buffer(UiBuffer),
Program(UiProgram),
ProgramData(UiProgramData),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiBuffer {
pub authority: Option<String>,
pub data: UiAccountData,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiProgram {
pub program_data: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiProgramData {
pub slot: u64,
pub authority: Option<String>,
pub data: UiAccountData,
}
#[cfg(test)]
mod test {
use super::*;
use bincode::serialize;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_parse_bpf_upgradeable_loader_accounts() {
let bpf_loader_state = UpgradeableLoaderState::Uninitialized;
let account_data = serialize(&bpf_loader_state).unwrap();
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Uninitialized
);
let program = vec![7u8; 64]; // Arbitrary program data
let authority = Pubkey::new_unique();
let bpf_loader_state = UpgradeableLoaderState::Buffer {
authority_address: Some(authority),
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: Some(authority.to_string()),
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
// This case included for code completeness; in practice, a Buffer account will always have
// authority_address.is_some()
let bpf_loader_state = UpgradeableLoaderState::Buffer {
authority_address: None,
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
authority: None,
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
let programdata_address = Pubkey::new_unique();
let bpf_loader_state = UpgradeableLoaderState::Program {
programdata_address,
};
let account_data = serialize(&bpf_loader_state).unwrap();
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::Program(UiProgram {
program_data: programdata_address.to_string(),
})
);
let authority = Pubkey::new_unique();
let slot = 42;
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(authority),
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: Some(authority.to_string()),
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: None,
};
let mut account_data = serialize(&bpf_loader_state).unwrap();
account_data.extend_from_slice(&program);
assert_eq!(
parse_bpf_upgradeable_loader(&account_data).unwrap(),
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
slot,
authority: None,
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
})
);
}
}

View File

@@ -91,7 +91,6 @@ mod test {
use crate::validator_info::ValidatorInfo; use crate::validator_info::ValidatorInfo;
use serde_json::json; use serde_json::json;
use solana_config_program::create_config_account; use solana_config_program::create_config_account;
use solana_sdk::account::ReadableAccount;
#[test] #[test]
fn test_parse_config() { fn test_parse_config() {
@@ -102,7 +101,7 @@ mod test {
let stake_config_account = create_config_account(vec![], &stake_config, 10); let stake_config_account = create_config_account(vec![], &stake_config, 10);
assert_eq!( assert_eq!(
parse_config( parse_config(
&stake_config_account.data(), &stake_config_account.data,
&solana_stake_program::config::id() &solana_stake_program::config::id()
) )
.unwrap(), .unwrap(),
@@ -118,14 +117,14 @@ mod test {
})) }))
.unwrap(), .unwrap(),
}; };
let info_pubkey = solana_sdk::pubkey::new_rand(); let info_pubkey = Pubkey::new_rand();
let validator_info_config_account = create_config_account( let validator_info_config_account = create_config_account(
vec![(validator_info::id(), false), (info_pubkey, true)], vec![(validator_info::id(), false), (info_pubkey, true)],
&validator_info, &validator_info,
10, 10,
); );
assert_eq!( assert_eq!(
parse_config(&validator_info_config_account.data(), &info_pubkey).unwrap(), parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
ConfigAccountType::ValidatorInfo(UiConfig { ConfigAccountType::ValidatorInfo(UiConfig {
keys: vec![ keys: vec![
UiConfigKey { UiConfigKey {

View File

@@ -9,13 +9,7 @@ pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
.map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?; .map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
let nonce_state = nonce_state.convert_to_current(); let nonce_state = nonce_state.convert_to_current();
match nonce_state { match nonce_state {
// This prevents parsing an allocated System-owned account with empty data of any non-zero State::Uninitialized => Ok(UiNonceState::Uninitialized),
// length as `uninitialized` nonce. An empty account of the wrong length can never be
// initialized as a nonce account, and an empty account of the correct length may not be an
// uninitialized nonce account, since it can be assigned to another program.
State::Uninitialized => Err(ParseAccountError::from(
InstructionError::InvalidAccountData,
)),
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData { State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(), authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(), blockhash: data.blockhash.to_string(),

View File

@@ -134,6 +134,7 @@ impl From<Delegation> for UiDelegation {
mod test { mod test {
use super::*; use super::*;
use bincode::serialize; use bincode::serialize;
use solana_sdk::pubkey::Pubkey;
#[test] #[test]
fn test_parse_stake() { fn test_parse_stake() {
@@ -144,8 +145,8 @@ mod test {
StakeAccountType::Uninitialized StakeAccountType::Uninitialized
); );
let pubkey = solana_sdk::pubkey::new_rand(); let pubkey = Pubkey::new_rand();
let custodian = solana_sdk::pubkey::new_rand(); let custodian = Pubkey::new_rand();
let authorized = Authorized::auto(&pubkey); let authorized = Authorized::auto(&pubkey);
let lockup = Lockup { let lockup = Lockup {
unix_timestamp: 0, unix_timestamp: 0,
@@ -179,7 +180,7 @@ mod test {
}) })
); );
let voter_pubkey = solana_sdk::pubkey::new_rand(); let voter_pubkey = Pubkey::new_rand();
let stake = Stake { let stake = Stake {
delegation: Delegation { delegation: Delegation {
voter_pubkey, voter_pubkey,

View File

@@ -105,7 +105,6 @@ pub enum SysvarAccountType {
pub struct UiClock { pub struct UiClock {
pub slot: Slot, pub slot: Slot,
pub epoch: Epoch, pub epoch: Epoch,
pub epoch_start_timestamp: UnixTimestamp,
pub leader_schedule_epoch: Epoch, pub leader_schedule_epoch: Epoch,
pub unix_timestamp: UnixTimestamp, pub unix_timestamp: UnixTimestamp,
} }
@@ -115,7 +114,6 @@ impl From<Clock> for UiClock {
Self { Self {
slot: clock.slot, slot: clock.slot,
epoch: clock.epoch, epoch: clock.epoch,
epoch_start_timestamp: clock.epoch_start_timestamp,
leader_schedule_epoch: clock.leader_schedule_epoch, leader_schedule_epoch: clock.leader_schedule_epoch,
unix_timestamp: clock.unix_timestamp, unix_timestamp: clock.unix_timestamp,
} }
@@ -214,13 +212,15 @@ pub struct UiStakeHistoryEntry {
mod test { mod test {
use super::*; use super::*;
use solana_sdk::{ use solana_sdk::{
account::create_account_for_test, fee_calculator::FeeCalculator, hash::Hash, fee_calculator::FeeCalculator,
sysvar::recent_blockhashes::IterItem, hash::Hash,
sysvar::{recent_blockhashes::IterItem, Sysvar},
}; };
use std::iter::FromIterator;
#[test] #[test]
fn test_parse_sysvars() { fn test_parse_sysvars() {
let clock_sysvar = create_account_for_test(&Clock::default()); let clock_sysvar = Clock::default().create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(), parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
SysvarAccountType::Clock(UiClock::default()), SysvarAccountType::Clock(UiClock::default()),
@@ -233,13 +233,13 @@ mod test {
first_normal_epoch: 1, first_normal_epoch: 1,
first_normal_slot: 12, first_normal_slot: 12,
}; };
let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule); let epoch_schedule_sysvar = epoch_schedule.create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(), parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
SysvarAccountType::EpochSchedule(epoch_schedule), SysvarAccountType::EpochSchedule(epoch_schedule),
); );
let fees_sysvar = create_account_for_test(&Fees::default()); let fees_sysvar = Fees::default().create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(), parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
SysvarAccountType::Fees(UiFees::default()), SysvarAccountType::Fees(UiFees::default()),
@@ -249,10 +249,9 @@ mod test {
let fee_calculator = FeeCalculator { let fee_calculator = FeeCalculator {
lamports_per_signature: 10, lamports_per_signature: 10,
}; };
let recent_blockhashes: RecentBlockhashes = vec![IterItem(0, &hash, &fee_calculator)] let recent_blockhashes =
.into_iter() RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter());
.collect(); let recent_blockhashes_sysvar = recent_blockhashes.create_account(1);
let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
assert_eq!( assert_eq!(
parse_sysvar( parse_sysvar(
&recent_blockhashes_sysvar.data, &recent_blockhashes_sysvar.data,
@@ -270,13 +269,13 @@ mod test {
exemption_threshold: 2.0, exemption_threshold: 2.0,
burn_percent: 5, burn_percent: 5,
}; };
let rent_sysvar = create_account_for_test(&rent); let rent_sysvar = rent.create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(), parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
SysvarAccountType::Rent(rent.into()), SysvarAccountType::Rent(rent.into()),
); );
let rewards_sysvar = create_account_for_test(&Rewards::default()); let rewards_sysvar = Rewards::default().create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(), parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
SysvarAccountType::Rewards(UiRewards::default()), SysvarAccountType::Rewards(UiRewards::default()),
@@ -284,7 +283,7 @@ mod test {
let mut slot_hashes = SlotHashes::default(); let mut slot_hashes = SlotHashes::default();
slot_hashes.add(1, hash); slot_hashes.add(1, hash);
let slot_hashes_sysvar = create_account_for_test(&slot_hashes); let slot_hashes_sysvar = slot_hashes.create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(), parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
SysvarAccountType::SlotHashes(vec![UiSlotHashEntry { SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
@@ -295,7 +294,7 @@ mod test {
let mut slot_history = SlotHistory::default(); let mut slot_history = SlotHistory::default();
slot_history.add(42); slot_history.add(42);
let slot_history_sysvar = create_account_for_test(&slot_history); let slot_history_sysvar = slot_history.create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(), parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
SysvarAccountType::SlotHistory(UiSlotHistory { SysvarAccountType::SlotHistory(UiSlotHistory {
@@ -311,7 +310,7 @@ mod test {
deactivating: 3, deactivating: 3,
}; };
stake_history.add(1, stake_history_entry.clone()); stake_history.add(1, stake_history_entry.clone());
let stake_history_sysvar = create_account_for_test(&stake_history); let stake_history_sysvar = stake_history.create_account(1);
assert_eq!( assert_eq!(
parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(), parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry { SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
@@ -320,7 +319,7 @@ mod test {
}]), }]),
); );
let bad_pubkey = solana_sdk::pubkey::new_rand(); let bad_pubkey = Pubkey::new_rand();
assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err()); assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
let bad_data = vec![0; 4]; let bad_data = vec![0; 4];

View File

@@ -1,44 +1,34 @@
use crate::{ use crate::{
parse_account_data::{ParsableAccount, ParseAccountError}, parse_account_data::{ParsableAccount, ParseAccountError},
StringAmount, StringDecimals, StringAmount,
}; };
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use spl_token_v2_0::{ use spl_token_v1_0::{
solana_program::{ option::COption,
program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey, solana_sdk::pubkey::Pubkey as SplTokenPubkey,
}, state::{unpack, Account, Mint, Multisig},
state::{Account, AccountState, Mint, Multisig},
}; };
use std::str::FromStr; use std::{mem::size_of, str::FromStr};
// A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to // A helper function to convert spl_token_v1_0::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey // solana_sdk::pubkey::Pubkey
pub fn spl_token_id_v2_0() -> Pubkey { pub fn spl_token_id_v1_0() -> Pubkey {
Pubkey::new_from_array(spl_token_v2_0::id().to_bytes()) Pubkey::from_str(&spl_token_v1_0::id().to_string()).unwrap()
} }
// A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to // A helper function to convert spl_token_v1_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
// solana_sdk::pubkey::Pubkey // solana_sdk::pubkey::Pubkey
pub fn spl_token_v2_0_native_mint() -> Pubkey { pub fn spl_token_v1_0_native_mint() -> Pubkey {
Pubkey::new_from_array(spl_token_v2_0::native_mint::id().to_bytes()) Pubkey::from_str(&spl_token_v1_0::native_mint::id().to_string()).unwrap()
}
// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey
pub fn spl_token_v2_0_pubkey(pubkey: &Pubkey) -> SplTokenPubkey {
SplTokenPubkey::new_from_array(pubkey.to_bytes())
}
// A helper function to convert a spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey
pub fn pubkey_from_spl_token_v2_0(pubkey: &SplTokenPubkey) -> Pubkey {
Pubkey::new_from_array(pubkey.to_bytes())
} }
pub fn parse_token( pub fn parse_token(
data: &[u8], data: &[u8],
mint_decimals: Option<u8>, mint_decimals: Option<u8>,
) -> Result<TokenAccountType, ParseAccountError> { ) -> Result<TokenAccountType, ParseAccountError> {
if data.len() == Account::get_packed_len() { let mut data = data.to_vec();
let account = Account::unpack(data) if data.len() == size_of::<Account>() {
let account: Account = *unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
let decimals = mint_decimals.ok_or_else(|| { let decimals = mint_decimals.ok_or_else(|| {
ParseAccountError::AdditionalDataMissing( ParseAccountError::AdditionalDataMissing(
@@ -53,12 +43,8 @@ pub fn parse_token(
COption::Some(pubkey) => Some(pubkey.to_string()), COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None, COption::None => None,
}, },
state: account.state.into(), is_initialized: account.is_initialized,
is_native: account.is_native(), 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() { delegated_amount: if account.delegate.is_none() {
None None
} else { } else {
@@ -67,29 +53,20 @@ pub fn parse_token(
decimals, 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() { } else if data.len() == size_of::<Mint>() {
let mint = Mint::unpack(data) let mint: Mint = *unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Mint(UiMint { Ok(TokenAccountType::Mint(UiMint {
mint_authority: match mint.mint_authority { owner: match mint.owner {
COption::Some(pubkey) => Some(pubkey.to_string()), COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None, COption::None => None,
}, },
supply: mint.supply.to_string(),
decimals: mint.decimals, decimals: mint.decimals,
is_initialized: mint.is_initialized, 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() { } else if data.len() == size_of::<Multisig>() {
let multisig = Multisig::unpack(data) let multisig: Multisig = *unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?; .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Multisig(UiMultisig { Ok(TokenAccountType::Multisig(UiMultisig {
num_required_signers: multisig.m, num_required_signers: multisig.m,
@@ -130,105 +107,36 @@ pub struct UiTokenAccount {
pub token_amount: UiTokenAmount, pub token_amount: UiTokenAmount,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub delegate: Option<String>, pub delegate: Option<String>,
pub state: UiAccountState, pub is_initialized: bool,
pub is_native: bool, pub is_native: bool,
#[serde(skip_serializing_if = "Option::is_none")] #[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>, 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,
}
}
}
pub fn real_number_string(amount: u64, decimals: u8) -> StringDecimals {
let decimals = decimals as usize;
if decimals > 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 {
amount.to_string()
}
}
pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> StringDecimals {
let mut s = real_number_string(amount, decimals);
if decimals > 0 {
let zeros_trimmed = s.trim_end_matches('0');
s = zeros_trimmed.trim_end_matches('.').to_string();
}
s
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UiTokenAmount { pub struct UiTokenAmount {
pub ui_amount: Option<f64>, pub ui_amount: f64,
pub decimals: u8, pub decimals: u8,
pub amount: StringAmount, pub amount: StringAmount,
pub ui_amount_string: StringDecimals,
}
impl UiTokenAmount {
pub fn real_number_string(&self) -> String {
real_number_string(
u64::from_str(&self.amount).unwrap_or_default(),
self.decimals as u8,
)
}
pub fn real_number_string_trimmed(&self) -> String {
if !self.ui_amount_string.is_empty() {
self.ui_amount_string.clone()
} else {
real_number_string_trimmed(
u64::from_str(&self.amount).unwrap_or_default(),
self.decimals as u8,
)
}
}
} }
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount { pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
let amount_decimals = 10_usize // 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
.checked_pow(decimals as u32) let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
.map(|dividend| amount as f64 / dividend as f64);
UiTokenAmount { UiTokenAmount {
ui_amount: amount_decimals, ui_amount: amount_decimals,
decimals, decimals,
amount: amount.to_string(), amount: amount.to_string(),
ui_amount_string: real_number_string_trimmed(amount, decimals),
} }
} }
#[derive(Debug, Serialize, Deserialize, PartialEq)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UiMint { pub struct UiMint {
pub mint_authority: Option<String>, pub owner: Option<String>,
pub supply: StringAmount,
pub decimals: u8, pub decimals: u8,
pub is_initialized: bool, pub is_initialized: bool,
pub freeze_authority: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, PartialEq)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
@@ -241,7 +149,7 @@ pub struct UiMultisig {
} }
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> { pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
if data.len() == Account::get_packed_len() { if data.len() == size_of::<Account>() {
Some(Pubkey::new(&data[0..32])) Some(Pubkey::new(&data[0..32]))
} else { } else {
None None
@@ -251,21 +159,18 @@ pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use spl_token_v1_0::state::unpack_unchecked;
#[test] #[test]
fn test_parse_token() { fn test_parse_token() {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]); let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let owner_pubkey = SplTokenPubkey::new(&[3; 32]); let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
let mut account_data = vec![0; Account::get_packed_len()]; let mut account_data = [0; size_of::<Account>()];
let mut account = Account::unpack_unchecked(&account_data).unwrap(); let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
account.mint = mint_pubkey; account.mint = mint_pubkey;
account.owner = owner_pubkey; account.owner = owner_pubkey;
account.amount = 42; account.amount = 42;
account.state = AccountState::Initialized; account.is_initialized = true;
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!(parse_token(&account_data, None).is_err());
assert_eq!( assert_eq!(
parse_token(&account_data, Some(2)).unwrap(), parse_token(&account_data, Some(2)).unwrap(),
@@ -273,55 +178,44 @@ mod test {
mint: mint_pubkey.to_string(), mint: mint_pubkey.to_string(),
owner: owner_pubkey.to_string(), owner: owner_pubkey.to_string(),
token_amount: UiTokenAmount { token_amount: UiTokenAmount {
ui_amount: Some(0.42), ui_amount: 0.42,
decimals: 2, decimals: 2,
amount: "42".to_string(), amount: "42".to_string()
ui_amount_string: "0.42".to_string()
}, },
delegate: None, delegate: None,
state: UiAccountState::Initialized, is_initialized: true,
is_native: false, is_native: false,
rent_exempt_reserve: None,
delegated_amount: None, delegated_amount: None,
close_authority: Some(owner_pubkey.to_string()),
}), }),
); );
let mut mint_data = vec![0; Mint::get_packed_len()]; let mut mint_data = [0; size_of::<Mint>()];
let mut mint = Mint::unpack_unchecked(&mint_data).unwrap(); let mut mint: &mut Mint = unpack_unchecked(&mut mint_data).unwrap();
mint.mint_authority = COption::Some(owner_pubkey); mint.owner = COption::Some(owner_pubkey);
mint.supply = 42;
mint.decimals = 3; mint.decimals = 3;
mint.is_initialized = true; mint.is_initialized = true;
mint.freeze_authority = COption::Some(owner_pubkey);
Mint::pack(mint, &mut mint_data).unwrap();
assert_eq!( assert_eq!(
parse_token(&mint_data, None).unwrap(), parse_token(&mint_data, None).unwrap(),
TokenAccountType::Mint(UiMint { TokenAccountType::Mint(UiMint {
mint_authority: Some(owner_pubkey.to_string()), owner: Some(owner_pubkey.to_string()),
supply: 42.to_string(),
decimals: 3, decimals: 3,
is_initialized: true, is_initialized: true,
freeze_authority: Some(owner_pubkey.to_string()),
}), }),
); );
let signer1 = SplTokenPubkey::new(&[1; 32]); let signer1 = SplTokenPubkey::new(&[1; 32]);
let signer2 = SplTokenPubkey::new(&[2; 32]); let signer2 = SplTokenPubkey::new(&[2; 32]);
let signer3 = SplTokenPubkey::new(&[3; 32]); let signer3 = SplTokenPubkey::new(&[3; 32]);
let mut multisig_data = vec![0; Multisig::get_packed_len()]; let mut multisig_data = [0; size_of::<Multisig>()];
let mut multisig: &mut Multisig = unpack_unchecked(&mut multisig_data).unwrap();
let mut signers = [SplTokenPubkey::default(); 11]; let mut signers = [SplTokenPubkey::default(); 11];
signers[0] = signer1; signers[0] = signer1;
signers[1] = signer2; signers[1] = signer2;
signers[2] = signer3; signers[2] = signer3;
let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
multisig.m = 2; multisig.m = 2;
multisig.n = 3; multisig.n = 3;
multisig.is_initialized = true; multisig.is_initialized = true;
multisig.signers = signers; multisig.signers = signers;
Multisig::pack(multisig, &mut multisig_data).unwrap();
assert_eq!( assert_eq!(
parse_token(&multisig_data, None).unwrap(), parse_token(&multisig_data, None).unwrap(),
TokenAccountType::Multisig(UiMultisig { TokenAccountType::Multisig(UiMultisig {
@@ -343,10 +237,9 @@ mod test {
#[test] #[test]
fn test_get_token_account_mint() { fn test_get_token_account_mint() {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]); let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let mut account_data = vec![0; Account::get_packed_len()]; let mut account_data = [0; size_of::<Account>()];
let mut account = Account::unpack_unchecked(&account_data).unwrap(); let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
account.mint = mint_pubkey; account.mint = mint_pubkey;
Account::pack(account, &mut account_data).unwrap();
let expected_mint_pubkey = Pubkey::new(&[2; 32]); let expected_mint_pubkey = Pubkey::new(&[2; 32]);
assert_eq!( assert_eq!(
@@ -354,90 +247,4 @@ mod test {
Some(expected_mint_pubkey) Some(expected_mint_pubkey)
); );
} }
#[test]
fn test_ui_token_amount_real_string() {
assert_eq!(&real_number_string(1, 0), "1");
assert_eq!(&real_number_string_trimmed(1, 0), "1");
let token_amount = token_amount_to_ui_amount(1, 0);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(1, 0)
);
assert_eq!(token_amount.ui_amount, Some(1.0));
assert_eq!(&real_number_string(10, 0), "10");
assert_eq!(&real_number_string_trimmed(10, 0), "10");
let token_amount = token_amount_to_ui_amount(10, 0);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(10, 0)
);
assert_eq!(token_amount.ui_amount, Some(10.0));
assert_eq!(&real_number_string(1, 9), "0.000000001");
assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
let token_amount = token_amount_to_ui_amount(1, 9);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(1, 9)
);
assert_eq!(token_amount.ui_amount, Some(0.000000001));
assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(1_000_000_000, 9)
);
assert_eq!(token_amount.ui_amount, Some(1.0));
assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(1_234_567_890, 3)
);
assert_eq!(token_amount.ui_amount, Some(1234567.89));
assert_eq!(
&real_number_string(1_234_567_890, 25),
"0.0000000000000001234567890"
);
assert_eq!(
&real_number_string_trimmed(1_234_567_890, 25),
"0.000000000000000123456789"
);
let token_amount = token_amount_to_ui_amount(1_234_567_890, 20);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(1_234_567_890, 20)
);
assert_eq!(token_amount.ui_amount, None);
}
#[test]
fn test_ui_token_amount_real_string_zero() {
assert_eq!(&real_number_string(0, 0), "0");
assert_eq!(&real_number_string_trimmed(0, 0), "0");
let token_amount = token_amount_to_ui_amount(0, 0);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(0, 0)
);
assert_eq!(token_amount.ui_amount, Some(0.0));
assert_eq!(&real_number_string(0, 9), "0.000000000");
assert_eq!(&real_number_string_trimmed(0, 9), "0");
let token_amount = token_amount_to_ui_amount(0, 9);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(0, 9)
);
assert_eq!(token_amount.ui_amount, Some(0.0));
assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
assert_eq!(&real_number_string_trimmed(0, 25), "0");
let token_amount = token_amount_to_ui_amount(0, 20);
assert_eq!(
token_amount.ui_amount_string,
real_number_string_trimmed(0, 20)
);
assert_eq!(token_amount.ui_amount, None);
}
} }

View File

@@ -128,13 +128,11 @@ mod test {
fn test_parse_vote() { fn test_parse_vote() {
let vote_state = VoteState::default(); let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()]; let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::new_current(vote_state); let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap(); VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let expected_vote_state = UiVoteState { let mut expected_vote_state = UiVoteState::default();
node_pubkey: Pubkey::default().to_string(), expected_vote_state.node_pubkey = Pubkey::default().to_string();
authorized_withdrawer: Pubkey::default().to_string(), expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
..UiVoteState::default()
};
assert_eq!( assert_eq!(
parse_vote(&vote_account_data).unwrap(), parse_vote(&vote_account_data).unwrap(),
VoteAccountType::Vote(expected_vote_state) VoteAccountType::Vote(expected_vote_state)

View File

@@ -2,20 +2,18 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-accounts-bench" name = "solana-accounts-bench"
version = "1.7.1" version = "1.3.2"
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.11" log = "0.4.6"
rayon = "1.5.0" rayon = "1.3.1"
solana-logger = { path = "../logger", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-runtime = { path = "../runtime", version = "=1.7.1" } solana-runtime = { path = "../runtime", version = "1.3.2" }
solana-measure = { path = "../measure", version = "=1.7.1" } solana-measure = { path = "../measure", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-version = { path = "../version", version = "=1.7.1" }
rand = "0.7.0" rand = "0.7.0"
clap = "2.33.1" clap = "2.33.1"
crossbeam-channel = "0.4" crossbeam-channel = "0.4"

View File

@@ -1,23 +1,20 @@
#![allow(clippy::integer_arithmetic)] use clap::{value_t, App, Arg};
#[macro_use]
extern crate log;
use clap::{crate_description, crate_name, 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_bench, Accounts}, accounts::{create_test_accounts, update_accounts, Accounts},
accounts_index::AccountSecondaryIndexes, accounts_index::Ancestors,
ancestors::Ancestors,
}; };
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey}; use solana_sdk::pubkey::Pubkey;
use std::{env, fs, path::PathBuf}; use std::fs;
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")
@@ -53,18 +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");
println!("cleaning file system: {:?}", path);
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_with_config( let accounts = Accounts::new(vec![path]);
vec![path],
&ClusterType::Testnet,
AccountSecondaryIndexes::default(),
false,
);
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)
@@ -88,59 +78,28 @@ fn main() {
num_slots, num_slots,
create_time create_time
); );
let mut ancestors = Vec::with_capacity(num_slots); let mut ancestors: Ancestors = vec![(0, 0)].into_iter().collect();
ancestors.push(0);
for i in 1..num_slots { for i in 1..num_slots {
ancestors.push(i as u64); ancestors.insert(i as u64, i - 1);
accounts.add_root(i as u64); accounts.add_root(i as u64);
} }
let ancestors = Ancestors::from(ancestors);
let mut elapsed = vec![0; iterations];
let mut elapsed_store = vec![0; iterations];
for x in 0..iterations { for x in 0..iterations {
if clean { if clean {
let mut time = Measure::start("clean"); let mut time = Measure::start("clean");
accounts.accounts_db.clean_accounts(None, false); accounts.accounts_db.clean_accounts();
time.stop(); time.stop();
println!("{}", time); println!("{}", time);
for slot in 0..num_slots { for slot in 0..num_slots {
update_accounts_bench(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64); update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
accounts.add_root((x * num_slots + slot) as u64); accounts.add_root((x * num_slots + slot) as u64);
} }
} 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 results = accounts.accounts_db.update_accounts_hash(0, &ancestors); let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
time.stop(); time.stop();
let mut time_store = Measure::start("hash using store"); println!("hash: {} {}", hash, time);
let results_store = accounts.accounts_db.update_accounts_hash_with_index_option(
false,
false,
solana_sdk::clock::Slot::default(),
&ancestors,
None,
);
time_store.stop();
if results != results_store {
error!("results different: \n{:?}\n{:?}", results, results_store);
}
println!(
"hash,{},{},{},{}%",
results.0,
time,
time_store,
(time_store.as_us() as f64 / time.as_us() as f64 * 100.0f64) as u32
);
create_test_accounts(&accounts, &mut pubkeys, 1, 0); create_test_accounts(&accounts, &mut pubkeys, 1, 0);
elapsed[x] = time.as_us();
elapsed_store[x] = time_store.as_us();
} }
} }
for x in elapsed {
info!("update_accounts_hash(us),{}", x);
}
for x in elapsed_store {
info!("calculate_accounts_hash_without_index(us),{}", x);
}
} }

View File

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

View File

@@ -1,35 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-accounts-cluster-bench"
version = "1.7.1"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
clap = "2.33.1"
log = "0.4.11"
rand = "0.7.0"
rayon = "1.4.1"
solana-account-decoder = { path = "../account-decoder", version = "=1.7.1" }
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" }
solana-client = { path = "../client", version = "=1.7.1" }
solana-core = { path = "../core", version = "=1.7.1" }
solana-faucet = { path = "../faucet", version = "=1.7.1" }
solana-gossip = { path = "../gossip", version = "=1.7.1" }
solana-logger = { path = "../logger", version = "=1.7.1" }
solana-measure = { path = "../measure", version = "=1.7.1" }
solana-net-utils = { path = "../net-utils", version = "=1.7.1" }
solana-runtime = { path = "../runtime", version = "=1.7.1" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
solana-transaction-status = { path = "../transaction-status", version = "=1.7.1" }
solana-version = { path = "../version", version = "=1.7.1" }
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "=1.7.1" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,742 +0,0 @@
#![allow(clippy::integer_arithmetic)]
use clap::{crate_description, crate_name, value_t, values_t_or_exit, App, Arg};
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana_account_decoder::parse_token::spl_token_v2_0_pubkey;
use solana_clap_utils::input_parsers::pubkey_of;
use solana_client::rpc_client::RpcClient;
use solana_faucet::faucet::{request_airdrop_transaction, FAUCET_PORT};
use solana_gossip::gossip_service::discover;
use solana_measure::measure::Measure;
use solana_runtime::inline_spl_token_v2_0;
use solana_sdk::{
commitment_config::CommitmentConfig,
message::Message,
pubkey::Pubkey,
rpc_port::DEFAULT_RPC_PORT,
signature::{read_keypair_file, Keypair, Signature, Signer},
system_instruction, system_program,
timing::timestamp,
transaction::Transaction,
};
use solana_transaction_status::parse_token::spl_token_v2_0_instruction;
use std::{
net::SocketAddr,
process::exit,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc, RwLock,
},
thread::{sleep, Builder, JoinHandle},
time::{Duration, Instant},
};
// Create and close messages both require 2 signatures; if transaction construction changes, update
// this magic number
const NUM_SIGNATURES: u64 = 2;
pub fn airdrop_lamports(
client: &RpcClient,
faucet_addr: &SocketAddr,
id: &Keypair,
desired_balance: u64,
) -> bool {
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
info!("starting balance {}", starting_balance);
if starting_balance < desired_balance {
let airdrop_amount = desired_balance - starting_balance;
info!(
"Airdropping {:?} lamports from {} for {}",
airdrop_amount,
faucet_addr,
id.pubkey(),
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => {
let mut tries = 0;
loop {
tries += 1;
let result = client.send_and_confirm_transaction(&transaction);
if result.is_ok() {
break;
}
if tries >= 5 {
panic!(
"Error requesting airdrop: to addr: {:?} amount: {} {:?}",
faucet_addr, airdrop_amount, result
)
}
}
}
Err(err) => {
panic!(
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
err, faucet_addr, airdrop_amount
);
}
};
let current_balance = client.get_balance(&id.pubkey()).unwrap_or_else(|e| {
panic!("airdrop error {}", e);
});
info!("current balance {}...", current_balance);
if current_balance - starting_balance != airdrop_amount {
info!(
"Airdrop failed? {} {} {} {}",
id.pubkey(),
current_balance,
starting_balance,
airdrop_amount,
);
}
}
true
}
// signature, timestamp, id
type PendingQueue = Vec<(Signature, u64, u64)>;
struct TransactionExecutor {
sig_clear_t: JoinHandle<()>,
sigs: Arc<RwLock<PendingQueue>>,
cleared: Arc<RwLock<Vec<u64>>>,
exit: Arc<AtomicBool>,
counter: AtomicU64,
client: RpcClient,
}
impl TransactionExecutor {
fn new(entrypoint_addr: SocketAddr) -> Self {
let sigs = Arc::new(RwLock::new(Vec::new()));
let cleared = Arc::new(RwLock::new(Vec::new()));
let exit = Arc::new(AtomicBool::new(false));
let sig_clear_t = Self::start_sig_clear_thread(&exit, &sigs, &cleared, entrypoint_addr);
let client =
RpcClient::new_socket_with_commitment(entrypoint_addr, CommitmentConfig::confirmed());
Self {
sigs,
cleared,
sig_clear_t,
exit,
counter: AtomicU64::new(0),
client,
}
}
fn num_outstanding(&self) -> usize {
self.sigs.read().unwrap().len()
}
fn push_transactions(&self, txs: Vec<Transaction>) -> Vec<u64> {
let mut ids = vec![];
let new_sigs = txs.into_iter().filter_map(|tx| {
let id = self.counter.fetch_add(1, Ordering::Relaxed);
ids.push(id);
match self.client.send_transaction(&tx) {
Ok(sig) => {
return Some((sig, timestamp(), id));
}
Err(e) => {
info!("error: {:#?}", e);
}
}
None
});
let mut sigs_w = self.sigs.write().unwrap();
sigs_w.extend(new_sigs);
ids
}
fn drain_cleared(&self) -> Vec<u64> {
std::mem::take(&mut *self.cleared.write().unwrap())
}
fn close(self) {
self.exit.store(true, Ordering::Relaxed);
self.sig_clear_t.join().unwrap();
}
fn start_sig_clear_thread(
exit: &Arc<AtomicBool>,
sigs: &Arc<RwLock<PendingQueue>>,
cleared: &Arc<RwLock<Vec<u64>>>,
entrypoint_addr: SocketAddr,
) -> JoinHandle<()> {
let sigs = sigs.clone();
let exit = exit.clone();
let cleared = cleared.clone();
Builder::new()
.name("sig_clear".to_string())
.spawn(move || {
let client = RpcClient::new_socket_with_commitment(
entrypoint_addr,
CommitmentConfig::confirmed(),
);
let mut success = 0;
let mut error_count = 0;
let mut timed_out = 0;
let mut last_log = Instant::now();
while !exit.load(Ordering::Relaxed) {
let sigs_len = sigs.read().unwrap().len();
if sigs_len > 0 {
let mut sigs_w = sigs.write().unwrap();
let mut start = Measure::start("sig_status");
let statuses: Vec<_> = sigs_w
.chunks(200)
.flat_map(|sig_chunk| {
let only_sigs: Vec<_> = sig_chunk.iter().map(|s| s.0).collect();
client
.get_signature_statuses(&only_sigs)
.expect("status fail")
.value
})
.collect();
let mut num_cleared = 0;
let start_len = sigs_w.len();
let now = timestamp();
let mut new_ids = vec![];
let mut i = 0;
let mut j = 0;
while i != sigs_w.len() {
let mut retain = true;
let sent_ts = sigs_w[i].1;
if let Some(e) = &statuses[j] {
debug!("error: {:?}", e);
if e.status.is_ok() {
success += 1;
} else {
error_count += 1;
}
num_cleared += 1;
retain = false;
} else if now - sent_ts > 30_000 {
retain = false;
timed_out += 1;
}
if !retain {
new_ids.push(sigs_w.remove(i).2);
} else {
i += 1;
}
j += 1;
}
let final_sigs_len = sigs_w.len();
drop(sigs_w);
cleared.write().unwrap().extend(new_ids);
start.stop();
debug!(
"sigs len: {:?} success: {} took: {}ms cleared: {}/{}",
final_sigs_len,
success,
start.as_ms(),
num_cleared,
start_len,
);
if last_log.elapsed().as_millis() > 5000 {
info!(
"success: {} error: {} timed_out: {}",
success, error_count, timed_out,
);
last_log = Instant::now();
}
}
sleep(Duration::from_millis(200));
}
})
.unwrap()
}
}
struct SeedTracker {
max_created: Arc<AtomicU64>,
max_closed: Arc<AtomicU64>,
}
fn make_create_message(
keypair: &Keypair,
base_keypair: &Keypair,
max_created_seed: Arc<AtomicU64>,
num_instructions: usize,
balance: u64,
maybe_space: Option<u64>,
mint: Option<Pubkey>,
) -> Message {
let space = maybe_space.unwrap_or_else(|| thread_rng().gen_range(0, 1000));
let instructions: Vec<_> = (0..num_instructions)
.into_iter()
.map(|_| {
let program_id = if mint.is_some() {
inline_spl_token_v2_0::id()
} else {
system_program::id()
};
let seed = max_created_seed.fetch_add(1, Ordering::Relaxed).to_string();
let to_pubkey =
Pubkey::create_with_seed(&base_keypair.pubkey(), &seed, &program_id).unwrap();
let mut instructions = vec![system_instruction::create_account_with_seed(
&keypair.pubkey(),
&to_pubkey,
&base_keypair.pubkey(),
&seed,
balance,
space,
&program_id,
)];
if let Some(mint_address) = mint {
instructions.push(spl_token_v2_0_instruction(
spl_token_v2_0::instruction::initialize_account(
&spl_token_v2_0::id(),
&spl_token_v2_0_pubkey(&to_pubkey),
&spl_token_v2_0_pubkey(&mint_address),
&spl_token_v2_0_pubkey(&base_keypair.pubkey()),
)
.unwrap(),
));
}
instructions
})
.collect();
let instructions: Vec<_> = instructions.into_iter().flatten().collect();
Message::new(&instructions, Some(&keypair.pubkey()))
}
fn make_close_message(
keypair: &Keypair,
base_keypair: &Keypair,
max_closed_seed: Arc<AtomicU64>,
num_instructions: usize,
balance: u64,
spl_token: bool,
) -> Message {
let instructions: Vec<_> = (0..num_instructions)
.into_iter()
.map(|_| {
let program_id = if spl_token {
inline_spl_token_v2_0::id()
} else {
system_program::id()
};
let seed = max_closed_seed.fetch_add(1, Ordering::Relaxed).to_string();
let address =
Pubkey::create_with_seed(&base_keypair.pubkey(), &seed, &program_id).unwrap();
if spl_token {
spl_token_v2_0_instruction(
spl_token_v2_0::instruction::close_account(
&spl_token_v2_0::id(),
&spl_token_v2_0_pubkey(&address),
&spl_token_v2_0_pubkey(&keypair.pubkey()),
&spl_token_v2_0_pubkey(&base_keypair.pubkey()),
&[],
)
.unwrap(),
)
} else {
system_instruction::transfer_with_seed(
&address,
&base_keypair.pubkey(),
seed,
&program_id,
&keypair.pubkey(),
balance,
)
}
})
.collect();
Message::new(&instructions, Some(&keypair.pubkey()))
}
#[allow(clippy::too_many_arguments)]
fn run_accounts_bench(
entrypoint_addr: SocketAddr,
faucet_addr: SocketAddr,
payer_keypairs: &[&Keypair],
iterations: usize,
maybe_space: Option<u64>,
batch_size: usize,
close_nth: u64,
maybe_lamports: Option<u64>,
num_instructions: usize,
mint: Option<Pubkey>,
) {
assert!(num_instructions > 0);
let client =
RpcClient::new_socket_with_commitment(entrypoint_addr, CommitmentConfig::confirmed());
info!("Targeting {}", entrypoint_addr);
let mut last_blockhash = Instant::now();
let mut last_log = Instant::now();
let mut count = 0;
let mut recent_blockhash = client.get_recent_blockhash().expect("blockhash");
let mut tx_sent_count = 0;
let mut total_accounts_created = 0;
let mut total_accounts_closed = 0;
let mut balances: Vec<_> = payer_keypairs
.iter()
.map(|keypair| client.get_balance(&keypair.pubkey()).unwrap_or(0))
.collect();
let mut last_balance = Instant::now();
let default_max_lamports = 1000;
let min_balance = maybe_lamports.unwrap_or_else(|| {
let space = maybe_space.unwrap_or(default_max_lamports);
client
.get_minimum_balance_for_rent_exemption(space as usize)
.expect("min balance")
});
let base_keypair = Keypair::new();
let seed_tracker = SeedTracker {
max_created: Arc::new(AtomicU64::default()),
max_closed: Arc::new(AtomicU64::default()),
};
info!("Starting balance(s): {:?}", balances);
let executor = TransactionExecutor::new(entrypoint_addr);
loop {
if last_blockhash.elapsed().as_millis() > 10_000 {
recent_blockhash = client.get_recent_blockhash().expect("blockhash");
last_blockhash = Instant::now();
}
let fee = recent_blockhash
.1
.lamports_per_signature
.saturating_mul(NUM_SIGNATURES);
let lamports = min_balance + fee;
for (i, balance) in balances.iter_mut().enumerate() {
if *balance < lamports || last_balance.elapsed().as_millis() > 2000 {
if let Ok(b) = client.get_balance(&payer_keypairs[i].pubkey()) {
*balance = b;
}
last_balance = Instant::now();
if *balance < lamports * 2 {
info!(
"Balance {} is less than needed: {}, doing aidrop...",
balance, lamports
);
if !airdrop_lamports(
&client,
&faucet_addr,
&payer_keypairs[i],
lamports * 100_000,
) {
warn!("failed airdrop, exiting");
return;
}
}
}
}
let sigs_len = executor.num_outstanding();
if sigs_len < batch_size {
let num_to_create = batch_size - sigs_len;
if num_to_create >= payer_keypairs.len() {
info!("creating {} new", num_to_create);
let chunk_size = num_to_create / payer_keypairs.len();
if chunk_size > 0 {
for (i, keypair) in payer_keypairs.iter().enumerate() {
let txs: Vec<_> = (0..chunk_size)
.into_par_iter()
.map(|_| {
let message = make_create_message(
keypair,
&base_keypair,
seed_tracker.max_created.clone(),
num_instructions,
min_balance,
maybe_space,
mint,
);
let signers: Vec<&Keypair> = vec![keypair, &base_keypair];
Transaction::new(&signers, message, recent_blockhash.0)
})
.collect();
balances[i] = balances[i].saturating_sub(lamports * txs.len() as u64);
info!("txs: {}", txs.len());
let new_ids = executor.push_transactions(txs);
info!("ids: {}", new_ids.len());
tx_sent_count += new_ids.len();
total_accounts_created += num_instructions * new_ids.len();
}
}
}
if close_nth > 0 {
let expected_closed = total_accounts_created as u64 / close_nth;
if expected_closed > total_accounts_closed {
let txs: Vec<_> = (0..expected_closed - total_accounts_closed)
.into_par_iter()
.map(|_| {
let message = make_close_message(
&payer_keypairs[0],
&base_keypair,
seed_tracker.max_closed.clone(),
1,
min_balance,
mint.is_some(),
);
let signers: Vec<&Keypair> = vec![&payer_keypairs[0], &base_keypair];
Transaction::new(&signers, message, recent_blockhash.0)
})
.collect();
balances[0] = balances[0].saturating_sub(fee * txs.len() as u64);
info!("close txs: {}", txs.len());
let new_ids = executor.push_transactions(txs);
info!("close ids: {}", new_ids.len());
tx_sent_count += new_ids.len();
total_accounts_closed += new_ids.len() as u64;
}
}
} else {
let _ = executor.drain_cleared();
}
count += 1;
if last_log.elapsed().as_millis() > 3000 {
info!(
"total_accounts_created: {} total_accounts_closed: {} tx_sent_count: {} loop_count: {} balance(s): {:?}",
total_accounts_created, total_accounts_closed, tx_sent_count, count, balances
);
last_log = Instant::now();
}
if iterations != 0 && count >= iterations {
break;
}
if executor.num_outstanding() >= batch_size {
sleep(Duration::from_millis(500));
}
}
executor.close();
}
fn main() {
solana_logger::setup_with_default("solana=info");
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
.arg(
Arg::with_name("entrypoint")
.long("entrypoint")
.takes_value(true)
.value_name("HOST:PORT")
.help("RPC entrypoint address. Usually <ip>:8899"),
)
.arg(
Arg::with_name("faucet_addr")
.long("faucet")
.takes_value(true)
.value_name("HOST:PORT")
.help("Faucet entrypoint address. Usually <ip>:9900"),
)
.arg(
Arg::with_name("space")
.long("space")
.takes_value(true)
.value_name("BYTES")
.help("Size of accounts to create"),
)
.arg(
Arg::with_name("lamports")
.long("lamports")
.takes_value(true)
.value_name("LAMPORTS")
.help("How many lamports to fund each account"),
)
.arg(
Arg::with_name("identity")
.long("identity")
.takes_value(true)
.multiple(true)
.value_name("FILE")
.help("keypair file"),
)
.arg(
Arg::with_name("batch_size")
.long("batch-size")
.takes_value(true)
.value_name("BYTES")
.help("Number of transactions to send per batch"),
)
.arg(
Arg::with_name("close_nth")
.long("close-frequency")
.takes_value(true)
.value_name("BYTES")
.help(
"Send close transactions after this many accounts created. \
Note: a `close-frequency` value near or below `batch-size` \
may result in transaction-simulation errors, as the close \
transactions will be submitted before the corresponding \
create transactions have been confirmed",
),
)
.arg(
Arg::with_name("num_instructions")
.long("num-instructions")
.takes_value(true)
.value_name("NUM")
.help("Number of accounts to create on each transaction"),
)
.arg(
Arg::with_name("iterations")
.long("iterations")
.takes_value(true)
.value_name("NUM")
.help("Number of iterations to make"),
)
.arg(
Arg::with_name("check_gossip")
.long("check-gossip")
.help("Just use entrypoint address directly"),
)
.arg(
Arg::with_name("mint")
.long("mint")
.takes_value(true)
.help("Mint address to initialize account"),
)
.get_matches();
let skip_gossip = !matches.is_present("check_gossip");
let port = if skip_gossip { DEFAULT_RPC_PORT } else { 8001 };
let mut entrypoint_addr = SocketAddr::from(([127, 0, 0, 1], port));
if let Some(addr) = matches.value_of("entrypoint") {
entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
}
let mut faucet_addr = SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT));
if let Some(addr) = matches.value_of("faucet_addr") {
faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
}
let space = value_t!(matches, "space", u64).ok();
let lamports = value_t!(matches, "lamports", u64).ok();
let batch_size = value_t!(matches, "batch_size", usize).unwrap_or(4);
let close_nth = value_t!(matches, "close_nth", u64).unwrap_or(0);
let iterations = value_t!(matches, "iterations", usize).unwrap_or(10);
let num_instructions = value_t!(matches, "num_instructions", usize).unwrap_or(1);
if num_instructions == 0 || num_instructions > 500 {
eprintln!("bad num_instructions: {}", num_instructions);
exit(1);
}
let mint = pubkey_of(&matches, "mint");
let payer_keypairs: Vec<_> = values_t_or_exit!(matches, "identity", String)
.iter()
.map(|keypair_string| {
read_keypair_file(keypair_string)
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_string))
})
.collect();
let mut payer_keypair_refs: Vec<&Keypair> = vec![];
for keypair in payer_keypairs.iter() {
payer_keypair_refs.push(keypair);
}
let rpc_addr = if !skip_gossip {
info!("Finding cluster entry: {:?}", entrypoint_addr);
let (gossip_nodes, _validators) = discover(
None, // keypair
Some(&entrypoint_addr),
None, // num_nodes
Duration::from_secs(60), // timeout
None, // find_node_by_pubkey
Some(&entrypoint_addr), // find_node_by_gossip_addr
None, // my_gossip_addr
0, // my_shred_version
)
.unwrap_or_else(|err| {
eprintln!("Failed to discover {} node: {:?}", entrypoint_addr, err);
exit(1);
});
info!("done found {} nodes", gossip_nodes.len());
gossip_nodes[0].rpc
} else {
info!("Using {:?} as the RPC address", entrypoint_addr);
entrypoint_addr
};
run_accounts_bench(
rpc_addr,
faucet_addr,
&payer_keypair_refs,
iterations,
space,
batch_size,
close_nth,
lamports,
num_instructions,
mint,
);
}
#[cfg(test)]
pub mod test {
use super::*;
use solana_core::validator::ValidatorConfig;
use solana_local_cluster::{
local_cluster::{ClusterConfig, LocalCluster},
validator_configs::make_identical_validator_configs,
};
use solana_sdk::poh_config::PohConfig;
#[test]
fn test_accounts_cluster_bench() {
solana_logger::setup();
let validator_config = ValidatorConfig::default();
let num_nodes = 1;
let mut config = ClusterConfig {
cluster_lamports: 10_000_000,
poh_config: PohConfig::new_sleep(Duration::from_millis(50)),
node_stakes: vec![100; num_nodes],
validator_configs: make_identical_validator_configs(&validator_config, num_nodes),
..ClusterConfig::default()
};
let faucet_addr = SocketAddr::from(([127, 0, 0, 1], 9900));
let cluster = LocalCluster::new(&mut config);
let iterations = 10;
let maybe_space = None;
let batch_size = 100;
let close_nth = 100;
let maybe_lamports = None;
let num_instructions = 2;
let mut start = Measure::start("total accounts run");
run_accounts_bench(
cluster.entry_point_info.rpc,
faucet_addr,
&[&cluster.funding_keypair],
iterations,
maybe_space,
batch_size,
close_nth,
maybe_lamports,
num_instructions,
None,
);
start.stop();
info!("{}", start);
}
}

View File

@@ -2,30 +2,27 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-banking-bench" name = "solana-banking-bench"
version = "1.7.1" version = "1.3.2"
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.11" log = "0.4.6"
rand = "0.7.0" rand = "0.7.0"
rayon = "1.5.0" rayon = "1.3.1"
solana-core = { path = "../core", version = "=1.7.1" } solana-core = { path = "../core", version = "1.3.2" }
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" } solana-clap-utils = { path = "../clap-utils", version = "1.3.2" }
solana-gossip = { path = "../gossip", version = "=1.7.1" } solana-streamer = { path = "../streamer", version = "1.3.2" }
solana-ledger = { path = "../ledger", version = "=1.7.1" } solana-perf = { path = "../perf", version = "1.3.2" }
solana-logger = { path = "../logger", version = "=1.7.1" } solana-ledger = { path = "../ledger", version = "1.3.2" }
solana-measure = { path = "../measure", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-perf = { path = "../perf", version = "=1.7.1" } solana-runtime = { path = "../runtime", version = "1.3.2" }
solana-poh = { path = "../poh", version = "=1.7.1" } solana-measure = { path = "../measure", version = "1.3.2" }
solana-runtime = { path = "../runtime", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-streamer = { path = "../streamer", version = "=1.7.1" } solana-version = { path = "../version", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
solana-version = { path = "../version", version = "=1.7.1" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,11 +1,15 @@
#![allow(clippy::integer_arithmetic)]
use clap::{crate_description, crate_name, value_t, App, Arg}; use clap::{crate_description, crate_name, value_t, App, Arg};
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use log::*; use log::*;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use rayon::prelude::*; use rayon::prelude::*;
use solana_core::banking_stage::BankingStage; use solana_core::{
use solana_gossip::{cluster_info::ClusterInfo, cluster_info::Node}; banking_stage::{create_test_recorder, BankingStage},
cluster_info::ClusterInfo,
cluster_info::Node,
poh_recorder::PohRecorder,
poh_recorder::WorkingBankEntry,
};
use solana_ledger::{ use solana_ledger::{
blockstore::Blockstore, blockstore::Blockstore,
genesis_utils::{create_genesis_config, GenesisConfigInfo}, genesis_utils::{create_genesis_config, GenesisConfigInfo},
@@ -13,12 +17,10 @@ use solana_ledger::{
}; };
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_poh::poh_recorder::{create_test_recorder, PohRecorder, WorkingBankEntry}; use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_runtime::{
accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks,
};
use solana_sdk::{ use solana_sdk::{
hash::Hash, hash::Hash,
pubkey::Pubkey,
signature::Keypair, signature::Keypair,
signature::Signature, signature::Signature,
system_transaction, system_transaction,
@@ -67,18 +69,18 @@ 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)
.into_par_iter() .into_par_iter()
.map(|_| { .map(|_| {
let mut new = dummy.clone(); let mut new = dummy.clone();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).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
}) })
@@ -185,7 +187,7 @@ fn main() {
genesis_config.hash(), genesis_config.hash(),
); );
// Ignore any pesky duplicate signature errors in the case we are using single-payer // Ignore any pesky duplicate signature errors in the case we are using single-payer
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect(); let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
fund.signatures = vec![Signature::new(&sig[0..64])]; fund.signatures = vec![Signature::new(&sig[0..64])];
let x = bank.process_transaction(&fund); let x = bank.process_transaction(&fund);
x.unwrap(); x.unwrap();
@@ -239,7 +241,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,
@@ -322,7 +324,7 @@ fn main() {
poh_recorder.lock().unwrap().set_bank(&bank); poh_recorder.lock().unwrap().set_bank(&bank);
assert!(poh_recorder.lock().unwrap().bank().is_some()); assert!(poh_recorder.lock().unwrap().bank().is_some());
if bank.slot() > 32 { if bank.slot() > 32 {
bank_forks.set_root(root, &AbsRequestSender::default(), None); bank_forks.set_root(root, &None, None);
root += 1; root += 1;
} }
debug!( debug!(
@@ -351,7 +353,7 @@ fn main() {
if bank.slot() > 0 && bank.slot() % 16 == 0 { if bank.slot() > 0 && bank.slot() % 16 == 0 {
for tx in transactions.iter_mut() { for tx in transactions.iter_mut() {
tx.message.recent_blockhash = bank.last_blockhash(); tx.message.recent_blockhash = bank.last_blockhash();
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect(); let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
tx.signatures[0] = Signature::new(&sig[0..64]); tx.signatures[0] = Signature::new(&sig[0..64]);
} }
verified = to_packets_chunked(&transactions.clone(), packets_per_chunk); verified = to_packets_chunked(&transactions.clone(), packets_per_chunk);

View File

@@ -1,34 +0,0 @@
[package]
name = "solana-banks-client"
version = "1.7.1"
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/"
documentation = "https://docs.rs/solana-banks-client"
edition = "2018"
[dependencies]
bincode = "1.3.1"
borsh = "0.8.1"
borsh-derive = "0.8.1"
futures = "0.3"
mio = "0.7.6"
solana-banks-interface = { path = "../banks-interface", version = "=1.7.1" }
solana-program = { path = "../sdk/program", version = "=1.7.1" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
tarpc = { version = "0.24.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
tokio-serde = { version = "0.8", features = ["bincode"] }
[dev-dependencies]
solana-runtime = { path = "../runtime", version = "=1.7.1" }
solana-banks-server = { path = "../banks-server", version = "=1.7.1" }
[lib]
crate-type = ["lib"]
name = "solana_banks_client"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,406 +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 borsh::BorshDeserialize;
use futures::{future::join_all, Future, FutureExt};
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
use solana_banks_interface::{BanksRequest, BanksResponse};
use solana_program::{
clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey,
rent::Rent, sysvar,
};
use solana_sdk::{
account::{from_account, Account},
commitment_config::CommitmentLevel,
signature::Signature,
transaction::{self, Transaction},
transport,
};
use std::io::{self, Error, ErrorKind};
use tarpc::{
client::{self, channel::RequestDispatch, NewClient},
context::{self, Context},
rpc::{ClientMessage, Response},
serde_transport::tcp,
Transport,
};
use tokio::{net::ToSocketAddrs, time::Duration};
use tokio_serde::formats::Bincode;
// This exists only for backward compatibility
pub trait BanksClientExt {}
#[derive(Clone)]
pub struct BanksClient {
inner: TarpcClient,
}
impl BanksClient {
#[allow(clippy::new_ret_no_self)]
pub fn new<C>(
config: client::Config,
transport: C,
) -> NewClient<TarpcClient, RequestDispatch<BanksRequest, BanksResponse, C>>
where
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>>,
{
TarpcClient::new(config, transport)
}
pub fn send_transaction_with_context(
&mut self,
ctx: Context,
transaction: Transaction,
) -> impl Future<Output = io::Result<()>> + '_ {
self.inner.send_transaction_with_context(ctx, transaction)
}
pub fn get_fees_with_commitment_and_context(
&mut self,
ctx: Context,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
self.inner
.get_fees_with_commitment_and_context(ctx, commitment)
}
pub fn get_transaction_status_with_context(
&mut self,
ctx: Context,
signature: Signature,
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
self.inner
.get_transaction_status_with_context(ctx, signature)
}
pub fn get_slot_with_context(
&mut self,
ctx: Context,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<Slot>> + '_ {
self.inner.get_slot_with_context(ctx, commitment)
}
pub fn process_transaction_with_commitment_and_context(
&mut self,
ctx: Context,
transaction: Transaction,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<Option<transaction::Result<()>>>> + '_ {
self.inner
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
}
pub fn get_account_with_commitment_and_context(
&mut self,
ctx: Context,
address: Pubkey,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
self.inner
.get_account_with_commitment_and_context(ctx, address, commitment)
}
/// 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.
pub fn send_transaction(
&mut self,
transaction: Transaction,
) -> impl Future<Output = io::Result<()>> + '_ {
self.send_transaction_with_context(context::current(), transaction)
}
/// 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.
pub fn get_fees(
&mut self,
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default())
}
/// Return the cluster rent
pub fn get_rent(&mut self) -> impl Future<Output = io::Result<Rent>> + '_ {
self.get_account(sysvar::rent::id()).map(|result| {
let rent_sysvar = result?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Rent sysvar not present"))?;
from_account::<Rent, _>(&rent_sysvar).ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Rent sysvar")
})
})
}
/// 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.
pub fn get_recent_blockhash(&mut self) -> impl Future<Output = io::Result<Hash>> + '_ {
self.get_fees().map(|result| Ok(result?.1))
}
/// Send a transaction and return after the transaction has been rejected or
/// reached the given level of commitment.
pub fn process_transaction_with_commitment(
&mut self,
transaction: Transaction,
commitment: CommitmentLevel,
) -> impl Future<Output = transport::Result<()>> + '_ {
let mut ctx = context::current();
ctx.deadline += Duration::from_secs(50);
self.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
.map(|result| match result? {
None => {
Err(Error::new(ErrorKind::TimedOut, "invalid blockhash or fee-payer").into())
}
Some(transaction_result) => Ok(transaction_result?),
})
}
/// Send a transaction and return until the transaction has been finalized or rejected.
pub fn process_transaction(
&mut self,
transaction: Transaction,
) -> impl Future<Output = transport::Result<()>> + '_ {
self.process_transaction_with_commitment(transaction, CommitmentLevel::default())
}
pub async fn process_transactions_with_commitment(
&mut self,
transactions: Vec<Transaction>,
commitment: CommitmentLevel,
) -> transport::Result<()> {
let mut clients: Vec<_> = transactions.iter().map(|_| self.clone()).collect();
let futures = clients
.iter_mut()
.zip(transactions)
.map(|(client, transaction)| {
client.process_transaction_with_commitment(transaction, commitment)
});
let statuses = join_all(futures).await;
statuses.into_iter().collect() // Convert Vec<Result<_, _>> to Result<Vec<_>>
}
/// Send transactions and return until the transaction has been finalized or rejected.
pub fn process_transactions(
&mut self,
transactions: Vec<Transaction>,
) -> impl Future<Output = transport::Result<()>> + '_ {
self.process_transactions_with_commitment(transactions, CommitmentLevel::default())
}
/// 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.
pub fn get_root_slot(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
self.get_slot_with_context(context::current(), CommitmentLevel::default())
}
/// 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.
pub fn get_account_with_commitment(
&mut self,
address: Pubkey,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
self.get_account_with_commitment_and_context(context::current(), address, commitment)
}
/// 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.
pub fn get_account(
&mut self,
address: Pubkey,
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
self.get_account_with_commitment(address, CommitmentLevel::default())
}
/// Return the unpacked account data at the given address
/// If the account is not found, an error is returned
pub fn get_packed_account_data<T: Pack>(
&mut self,
address: Pubkey,
) -> impl Future<Output = io::Result<T>> + '_ {
self.get_account(address).map(|result| {
let account =
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Account not found"))?;
T::unpack_from_slice(&account.data)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to deserialize account"))
})
}
/// Return the unpacked account data at the given address
/// If the account is not found, an error is returned
pub fn get_account_data_with_borsh<T: BorshDeserialize>(
&mut self,
address: Pubkey,
) -> impl Future<Output = io::Result<T>> + '_ {
self.get_account(address).map(|result| {
let account =
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "account not found"))?;
T::try_from_slice(&account.data)
})
}
/// Return the balance in lamports of an account at the given address at the slot
/// corresponding to the given commitment level.
pub fn get_balance_with_commitment(
&mut self,
address: Pubkey,
commitment: CommitmentLevel,
) -> impl Future<Output = io::Result<u64>> + '_ {
self.get_account_with_commitment_and_context(context::current(), address, commitment)
.map(|result| Ok(result?.map(|x| x.lamports).unwrap_or(0)))
}
/// Return the balance in lamports of an account at the given address at the time
/// of the most recent root slot.
pub fn get_balance(&mut self, address: Pubkey) -> impl Future<Output = io::Result<u64>> + '_ {
self.get_balance_with_commitment(address, CommitmentLevel::default())
}
/// 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.
pub fn get_transaction_status(
&mut self,
signature: Signature,
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
self.get_transaction_status_with_context(context::current(), signature)
}
/// Same as get_transaction_status, but for multiple transactions.
pub 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<C>(transport: C) -> io::Result<BanksClient>
where
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>> + Send + 'static,
{
Ok(BanksClient {
inner: TarpcClient::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?;
Ok(BanksClient {
inner: TarpcClient::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, commitment::BlockCommitmentCache,
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::sleep};
#[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 = Bank::new(&genesis.genesis_config);
let slot = bank.slot();
let block_commitment_cache = Arc::new(RwLock::new(
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
));
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
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, block_commitment_cache).await;
let mut banks_client = start_client(client_transport).await?;
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 = Bank::new(&genesis.genesis_config);
let slot = bank.slot();
let block_commitment_cache = Arc::new(RwLock::new(
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
));
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
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, block_commitment_cache).await;
let mut banks_client = start_client(client_transport).await?;
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;
}
sleep(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(())
})
}
}

View File

@@ -1,26 +0,0 @@
[package]
name = "solana-banks-interface"
version = "1.7.1"
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/"
documentation = "https://docs.rs/solana-banks-interface"
edition = "2018"
[dependencies]
mio = "0.7.6"
serde = { version = "1.0.122", features = ["derive"] }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
tarpc = { version = "0.24.1", features = ["full"] }
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
[lib]
crate-type = ["lib"]
name = "solana_banks_interface"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,57 +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 enum TransactionConfirmationStatus {
Processed,
Confirmed,
Finalized,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TransactionStatus {
pub slot: Slot,
pub confirmations: Option<usize>, // None = rooted
pub err: Option<TransactionError>,
pub confirmation_status: Option<TransactionConfirmationStatus>,
}
#[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);
}
}

View File

@@ -1,31 +0,0 @@
[package]
name = "solana-banks-server"
version = "1.7.1"
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/"
documentation = "https://docs.rs/solana-banks-server"
edition = "2018"
[dependencies]
bincode = "1.3.1"
futures = "0.3"
log = "0.4.11"
mio = "0.7.6"
solana-banks-interface = { path = "../banks-interface", version = "=1.7.1" }
solana-runtime = { path = "../runtime", version = "=1.7.1" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
solana-metrics = { path = "../metrics", version = "=1.7.1" }
tarpc = { version = "0.24.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
tokio-serde = { version = "0.8", features = ["bincode"] }
tokio-stream = "0.1"
[lib]
crate-type = ["lib"]
name = "solana_banks_server"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,298 +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, TransactionConfirmationStatus, TransactionStatus,
};
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
use solana_sdk::{
account::Account,
clock::Slot,
commitment_config::CommitmentLevel,
fee_calculator::FeeCalculator,
hash::Hash,
pubkey::Pubkey,
signature::Signature,
transaction::{self, Transaction},
};
use std::{
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::sleep;
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_forks: Arc<RwLock<BankForks>>, 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 = bank_forks.read().unwrap().working_bank();
let _ = bank.process_transactions(&transactions);
}
}
/// Useful for unit-testing
fn new_loopback(
bank_forks: Arc<RwLock<BankForks>>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
) -> Self {
let (transaction_sender, transaction_receiver) = channel();
let bank = bank_forks.read().unwrap().working_bank();
let slot = bank.slot();
{
// ensure that the commitment cache and bank are synced
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
w_block_commitment_cache.set_all_slots(slot, slot);
}
let server_bank_forks = bank_forks.clone();
Builder::new()
.name("solana-bank-forks-client".to_string())
.spawn(move || Self::run(server_bank_forks, 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,
blockhash: &Hash,
last_valid_slot: Slot,
commitment: CommitmentLevel,
) -> Option<transaction::Result<()>> {
let mut status = self
.bank(commitment)
.get_signature_status_with_blockhash(signature, blockhash);
while status.is_none() {
sleep(Duration::from_millis(200)).await;
let bank = self.bank(commitment);
if bank.slot() > last_valid_slot {
break;
}
status = bank.get_signature_status_with_blockhash(signature, blockhash);
}
status
}
}
fn verify_transaction(transaction: &Transaction) -> transaction::Result<()> {
if let Err(err) = transaction.verify() {
Err(err)
} else if let Err(err) = transaction.verify_precompiles() {
Err(err)
} else {
Ok(())
}
}
#[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::Processed);
let (slot, status) = bank.get_signature_status_slot(&signature)?;
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
let optimistically_confirmed_bank = self.bank(CommitmentLevel::Confirmed);
let optimistically_confirmed =
optimistically_confirmed_bank.get_signature_status_slot(&signature);
let confirmations = if r_block_commitment_cache.root() >= slot
&& r_block_commitment_cache.highest_confirmed_root() >= slot
{
None
} else {
r_block_commitment_cache
.get_confirmation_count(slot)
.or(Some(0))
};
Some(TransactionStatus {
slot,
confirmations,
err: status.err(),
confirmation_status: if confirmations.is_none() {
Some(TransactionConfirmationStatus::Finalized)
} else if optimistically_confirmed.is_some() {
Some(TransactionConfirmationStatus::Confirmed)
} else {
Some(TransactionConfirmationStatus::Processed)
},
})
}
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<()>> {
if let Err(err) = verify_transaction(&transaction) {
return Some(Err(err));
}
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, blockhash, 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).map(Account::from)
}
}
pub async fn start_local_server(
bank_forks: Arc<RwLock<BankForks>>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> {
let banks_server = BanksServer::new_loopback(bank_forks, block_commitment_cache);
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(())
}

View File

@@ -1,7 +0,0 @@
#![allow(clippy::integer_arithmetic)]
pub mod banks_server;
pub mod rpc_banks_service;
pub mod send_transaction_service;
#[macro_use]
extern crate solana_metrics;

View File

@@ -1,117 +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},
};
use tokio_stream::wrappers::IntervalStream;
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 = IntervalStream::new(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();
}
}

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-bench-exchange" name = "solana-bench-exchange"
version = "1.7.1" version = "1.3.2"
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,29 +11,28 @@ publish = false
[dependencies] [dependencies]
clap = "2.33.1" clap = "2.33.1"
itertools = "0.9.0" itertools = "0.9.0"
log = "0.4.11" 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.5.0" rayon = "1.3.1"
serde_json = "1.0.56" serde_json = "1.0.56"
serde_yaml = "0.8.13" serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" } solana-clap-utils = { path = "../clap-utils", version = "1.3.2" }
solana-core = { path = "../core", version = "=1.7.1" } solana-core = { path = "../core", version = "1.3.2" }
solana-genesis = { path = "../genesis", version = "=1.7.1" } solana-genesis = { path = "../genesis", version = "1.3.2" }
solana-client = { path = "../client", version = "=1.7.1" } solana-client = { path = "../client", version = "1.3.2" }
solana-exchange-program = { path = "../programs/exchange", version = "=1.7.1" } solana-faucet = { path = "../faucet", version = "1.3.2" }
solana-faucet = { path = "../faucet", version = "=1.7.1" } solana-exchange-program = { path = "../programs/exchange", version = "1.3.2" }
solana-gossip = { path = "../gossip", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-logger = { path = "../logger", version = "=1.7.1" } solana-metrics = { path = "../metrics", version = "1.3.2" }
solana-metrics = { path = "../metrics", version = "=1.7.1" } solana-net-utils = { path = "../net-utils", version = "1.3.2" }
solana-net-utils = { path = "../net-utils", version = "=1.7.1" } solana-runtime = { path = "../runtime", version = "1.3.2" }
solana-runtime = { path = "../runtime", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" } solana-version = { path = "../version", version = "1.3.2" }
solana-version = { path = "../version", version = "=1.7.1" }
[dev-dependencies] [dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "=1.7.1" } solana-local-cluster = { path = "../local-cluster", version = "1.3.2" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,5 +1,4 @@
#![allow(clippy::useless_attribute)] #![allow(clippy::useless_attribute)]
#![allow(clippy::integer_arithmetic)]
use crate::order_book::*; use crate::order_book::*;
use itertools::izip; use itertools::izip;
@@ -391,7 +390,7 @@ fn swapper<T>(
while client while client
.get_balance_with_commitment( .get_balance_with_commitment(
&trade_infos[trade_index].trade_account, &trade_infos[trade_index].trade_account,
CommitmentConfig::processed(), CommitmentConfig::recent(),
) )
.unwrap_or(0) .unwrap_or(0)
== 0 == 0
@@ -446,7 +445,7 @@ fn swapper<T>(
account_group = (account_group + 1) % account_groups as usize; account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator, _last_valid_slot) = client let (blockhash, _fee_calculator, _last_valid_slot) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap let to_swap_txs: Vec<_> = to_swap
.par_iter() .par_iter()
@@ -572,7 +571,7 @@ fn trader<T>(
account_group = (account_group + 1) % account_groups as usize; account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator, _last_valid_slot) = client let (blockhash, _fee_calculator, _last_valid_slot) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| { trades.chunks(chunk_size).for_each(|chunk| {
@@ -659,7 +658,7 @@ where
{ {
for s in &tx.signatures { for s in &tx.signatures {
if let Ok(Some(r)) = if let Ok(Some(r)) =
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::processed()) sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
{ {
match r { match r {
Ok(_) => { Ok(_) => {
@@ -682,7 +681,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
if verify_transaction(client, tx) { if verify_transaction(client, tx) {
for a in &tx.message().account_keys[1..] { for a in &tx.message().account_keys[1..] {
if client if client
.get_balance_with_commitment(a, CommitmentConfig::processed()) .get_balance_with_commitment(a, CommitmentConfig::recent())
.unwrap_or(0) .unwrap_or(0)
>= amount >= amount
{ {
@@ -765,7 +764,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
); );
let (blockhash, _fee_calculator, _last_valid_slot) = client let (blockhash, _fee_calculator, _last_valid_slot) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("blockhash"); .expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| { to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash); tx.sign(&[*k], blockhash);
@@ -804,7 +803,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
funded.append(&mut new_funded); funded.append(&mut new_funded);
funded.retain(|(k, b)| { funded.retain(|(k, b)| {
client client
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::processed()) .get_balance_with_commitment(&k.pubkey(), CommitmentConfig::recent())
.unwrap_or(0) .unwrap_or(0)
> lamports > lamports
&& *b > lamports && *b > lamports
@@ -858,7 +857,7 @@ pub fn create_token_accounts<T: Client>(
let mut retries = 0; let mut retries = 0;
while !to_create_txs.is_empty() { while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator, _last_valid_slot) = client let (blockhash, _fee_calculator, _last_valid_slot) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
to_create_txs to_create_txs
.par_iter_mut() .par_iter_mut()
@@ -904,7 +903,7 @@ pub fn create_token_accounts<T: Client>(
let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![]; let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![];
for f in &notfunded { for f in &notfunded {
if client if client
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::processed()) .get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::recent())
.unwrap_or(0) .unwrap_or(0)
== 0 == 0
{ {
@@ -969,7 +968,7 @@ pub fn airdrop_lamports<T: Client>(
id: &Keypair, id: &Keypair,
amount: u64, amount: u64,
) { ) {
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed()); let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
let balance = balance.unwrap_or(0); let balance = balance.unwrap_or(0);
if balance >= amount { if balance >= amount {
return; return;
@@ -987,7 +986,7 @@ pub fn airdrop_lamports<T: Client>(
let mut tries = 0; let mut tries = 0;
loop { loop {
let (blockhash, _fee_calculator, _last_valid_slot) = client let (blockhash, _fee_calculator, _last_valid_slot) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) .get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) { match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
Ok(transaction) => { Ok(transaction) => {
@@ -996,14 +995,14 @@ pub fn airdrop_lamports<T: Client>(
for _ in 0..30 { for _ in 0..30 {
if let Ok(Some(_)) = client.get_signature_status_with_commitment( if let Ok(Some(_)) = client.get_signature_status_with_commitment(
&signature, &signature,
CommitmentConfig::processed(), CommitmentConfig::recent(),
) { ) {
break; break;
} }
sleep(Duration::from_millis(100)); sleep(Duration::from_millis(100));
} }
if client if client
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed()) .get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
.unwrap_or(0) .unwrap_or(0)
>= amount >= amount
{ {

View File

@@ -163,8 +163,7 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
) )
} }
#[allow(clippy::field_reassign_with_default)] pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
pub fn extract_args(matches: &ArgMatches) -> Config {
let mut args = Config::default(); let mut args = Config::default();
args.entrypoint_addr = solana_net_utils::parse_host_port( args.entrypoint_addr = solana_net_utils::parse_host_port(

View File

@@ -1,11 +1,10 @@
#![allow(clippy::integer_arithmetic)]
pub mod bench; pub mod bench;
mod cli; mod cli;
pub mod order_book; pub mod order_book;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config}; use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*; use log::*;
use solana_gossip::gossip_service::{discover_cluster, get_multi_client}; use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::Signer; use solana_sdk::signature::Signer;
fn main() { fn main() {

View File

@@ -1,21 +1,19 @@
use log::*; use log::*;
use solana_bench_exchange::bench::{airdrop_lamports, do_bench_exchange, Config}; use solana_bench_exchange::bench::{airdrop_lamports, do_bench_exchange, Config};
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_core::validator::ValidatorConfig; use solana_core::validator::ValidatorConfig;
use solana_exchange_program::{ use solana_exchange_program::exchange_processor::process_instruction;
exchange_processor::process_instruction, id, solana_exchange_program, use solana_exchange_program::id;
}; use solana_exchange_program::solana_exchange_program;
use solana_faucet::faucet::run_local_faucet_with_port; use solana_faucet::faucet::run_local_faucet;
use solana_gossip::gossip_service::{discover_cluster, get_multi_client}; use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_local_cluster::{ use solana_runtime::bank::Bank;
local_cluster::{ClusterConfig, LocalCluster}, use solana_runtime::bank_client::BankClient;
validator_configs::make_identical_validator_configs, use solana_sdk::genesis_config::create_genesis_config;
}; use solana_sdk::signature::{Keypair, Signer};
use solana_runtime::{bank::Bank, bank_client::BankClient}; use std::process::exit;
use solana_sdk::{ use std::sync::mpsc::channel;
genesis_config::create_genesis_config, use std::time::Duration;
signature::{Keypair, Signer},
};
use std::{process::exit, sync::mpsc::channel, time::Duration};
#[test] #[test]
#[ignore] #[ignore]
@@ -24,17 +22,15 @@ fn test_exchange_local_cluster() {
const NUM_NODES: usize = 1; const NUM_NODES: usize = 1;
let config = Config { let mut config = Config::default();
identity: Keypair::new(), config.identity = Keypair::new();
duration: Duration::from_secs(1), config.duration = Duration::from_secs(1);
fund_amount: 100_000, config.fund_amount = 100_000;
threads: 1, config.threads = 1;
transfer_delay: 20, // 15 config.transfer_delay = 20; // 15
batch_size: 100, // 1000 config.batch_size = 100; // 1000;
chunk_size: 10, // 200 config.chunk_size = 10; // 200;
account_groups: 1, // 10 config.account_groups = 1; // 10;
..Config::default()
};
let Config { let Config {
fund_amount, fund_amount,
batch_size, batch_size,
@@ -43,10 +39,10 @@ fn test_exchange_local_cluster() {
} = config; } = config;
let accounts_in_groups = batch_size * account_groups; let accounts_in_groups = batch_size * account_groups;
let cluster = LocalCluster::new(&mut ClusterConfig { let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES], node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000, cluster_lamports: 100_000_000_000_000,
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES), validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
native_instruction_processors: [solana_exchange_program!()].to_vec(), native_instruction_processors: [solana_exchange_program!()].to_vec(),
..ClusterConfig::default() ..ClusterConfig::default()
}); });
@@ -59,11 +55,8 @@ fn test_exchange_local_cluster() {
); );
let (addr_sender, addr_receiver) = channel(); let (addr_sender, addr_receiver) = channel();
run_local_faucet_with_port(faucet_keypair, addr_sender, Some(1_000_000_000_000), 0); run_local_faucet(faucet_keypair, addr_sender, Some(1_000_000_000_000));
let faucet_addr = addr_receiver let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
.recv_timeout(Duration::from_secs(2))
.expect("run_local_faucet")
.expect("faucet_addr");
info!("Connecting to the cluster"); info!("Connecting to the cluster");
let nodes = let nodes =
@@ -93,21 +86,18 @@ fn test_exchange_bank_client() {
solana_logger::setup(); solana_logger::setup();
let (genesis_config, identity) = create_genesis_config(100_000_000_000_000); let (genesis_config, identity) = create_genesis_config(100_000_000_000_000);
let mut bank = Bank::new(&genesis_config); let mut bank = Bank::new(&genesis_config);
bank.add_builtin("exchange_program", id(), process_instruction); bank.add_builtin_program("exchange_program", id(), process_instruction);
let clients = vec![BankClient::new(bank)]; let clients = vec![BankClient::new(bank)];
do_bench_exchange( let mut config = Config::default();
clients, config.identity = identity;
Config { config.duration = Duration::from_secs(1);
identity, config.fund_amount = 100_000;
duration: Duration::from_secs(1), config.threads = 1;
fund_amount: 100_000, config.transfer_delay = 20; // 0;
threads: 1, config.batch_size = 100; // 1500;
transfer_delay: 20, // 0; config.chunk_size = 10; // 1500;
batch_size: 100, // 1500; config.account_groups = 1; // 50;
chunk_size: 10, // 1500;
account_groups: 1, // 50; do_bench_exchange(clients, config);
..Config::default()
},
);
} }

View File

@@ -2,19 +2,18 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-bench-streamer" name = "solana-bench-streamer"
version = "1.7.1" version = "1.3.2"
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.7.1" } solana-clap-utils = { path = "../clap-utils", version = "1.3.2" }
solana-streamer = { path = "../streamer", version = "=1.7.1" } solana-streamer = { path = "../streamer", version = "1.3.2" }
solana-logger = { path = "../logger", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-net-utils = { path = "../net-utils", version = "=1.7.1" } solana-net-utils = { path = "../net-utils", version = "1.3.2" }
solana-version = { path = "../version", version = "=1.7.1" } solana-version = { path = "../version", version = "1.3.2" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,4 +1,3 @@
#![allow(clippy::integer_arithmetic)]
use clap::{crate_description, crate_name, App, Arg}; use clap::{crate_description, crate_name, App, Arg};
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE}; use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
use solana_streamer::streamer::{receiver, PacketReceiver}; use solana_streamer::streamer::{receiver, PacketReceiver};
@@ -28,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;
} }
@@ -91,7 +90,6 @@ fn main() -> Result<()> {
s_reader, s_reader,
recycler.clone(), recycler.clone(),
"bench-streamer-test", "bench-streamer-test",
1,
)); ));
} }

View File

@@ -2,36 +2,35 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-bench-tps" name = "solana-bench-tps"
version = "1.7.1" version = "1.3.2"
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.3.1"
clap = "2.33.1" clap = "2.33.1"
log = "0.4.11" log = "0.4.8"
rayon = "1.5.0" rayon = "1.3.1"
serde_json = "1.0.56" serde_json = "1.0.56"
serde_yaml = "0.8.13" serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" } solana-clap-utils = { path = "../clap-utils", version = "1.3.2" }
solana-core = { path = "../core", version = "=1.7.1" } solana-core = { path = "../core", version = "1.3.2" }
solana-genesis = { path = "../genesis", version = "=1.7.1" } solana-genesis = { path = "../genesis", version = "1.3.2" }
solana-client = { path = "../client", version = "=1.7.1" } solana-client = { path = "../client", version = "1.3.2" }
solana-faucet = { path = "../faucet", version = "=1.7.1" } solana-faucet = { path = "../faucet", version = "1.3.2" }
solana-gossip = { path = "../gossip", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-logger = { path = "../logger", version = "=1.7.1" } solana-metrics = { path = "../metrics", version = "1.3.2" }
solana-metrics = { path = "../metrics", version = "=1.7.1" } solana-measure = { path = "../measure", version = "1.3.2" }
solana-measure = { path = "../measure", version = "=1.7.1" } solana-net-utils = { path = "../net-utils", version = "1.3.2" }
solana-net-utils = { path = "../net-utils", version = "=1.7.1" } solana-runtime = { path = "../runtime", version = "1.3.2" }
solana-runtime = { path = "../runtime", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" } solana-version = { path = "../version", version = "1.3.2" }
solana-version = { path = "../version", version = "=1.7.1" }
[dev-dependencies] [dev-dependencies]
serial_test = "0.4.0" serial_test = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "=1.7.1" } serial_test_derive = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "1.3.2" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -8,7 +8,7 @@ use solana_measure::measure::Measure;
use solana_metrics::{self, datapoint_info}; use solana_metrics::{self, datapoint_info};
use solana_sdk::{ use solana_sdk::{
client::Client, client::Client,
clock::{DEFAULT_S_PER_SLOT, MAX_PROCESSING_AGE}, clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
fee_calculator::FeeCalculator, fee_calculator::FeeCalculator,
hash::Hash, hash::Hash,
@@ -32,7 +32,8 @@ use std::{
}; };
// The point at which transactions become "too old", in seconds. // The point at which transactions become "too old", in seconds.
const MAX_TX_QUEUE_AGE: u64 = (MAX_PROCESSING_AGE as f64 * DEFAULT_S_PER_SLOT) as u64; const MAX_TX_QUEUE_AGE: u64 =
MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
pub const MAX_SPENDS_PER_TX: u64 = 4; pub const MAX_SPENDS_PER_TX: u64 = 4;
@@ -47,7 +48,7 @@ pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
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::processed()) { match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
Ok((blockhash, fee_calculator, _last_valid_slot)) => { Ok((blockhash, fee_calculator, _last_valid_slot)) => {
return (blockhash, fee_calculator) return (blockhash, fee_calculator)
} }
@@ -496,7 +497,7 @@ fn do_tx_transfers<T: Client>(
fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool { fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool {
for a in &tx.message().account_keys[1..] { for a in &tx.message().account_keys[1..] {
match client.get_balance_with_commitment(a, CommitmentConfig::processed()) { match client.get_balance_with_commitment(a, CommitmentConfig::recent()) {
Ok(balance) => return balance >= amount, Ok(balance) => return balance >= amount,
Err(err) => error!("failed to get balance {:?}", err), Err(err) => error!("failed to get balance {:?}", err),
} }
@@ -761,7 +762,7 @@ pub fn airdrop_lamports<T: Client>(
}; };
let current_balance = client let current_balance = client
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed()) .get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
info!("airdrop error {}", e); info!("airdrop error {}", e);
starting_balance starting_balance
@@ -937,12 +938,10 @@ mod tests {
let bank = Bank::new(&genesis_config); let bank = Bank::new(&genesis_config);
let client = Arc::new(BankClient::new(bank)); let client = Arc::new(BankClient::new(bank));
let config = Config { let mut config = Config::default();
id, config.id = id;
tx_count: 10, config.tx_count = 10;
duration: Duration::from_secs(5), config.duration = Duration::from_secs(5);
..Config::default()
};
let keypair_count = config.tx_count * config.keypair_multiplier; let keypair_count = config.tx_count * config.keypair_multiplier;
let keypairs = let keypairs =
@@ -966,7 +965,7 @@ mod tests {
for kp in &keypairs { for kp in &keypairs {
assert_eq!( assert_eq!(
client client
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::processed()) .get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::recent())
.unwrap(), .unwrap(),
lamports lamports
); );

View File

@@ -196,7 +196,7 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
/// * `matches` - command line arguments parsed by clap /// * `matches` - command line arguments parsed by clap
/// # Panics /// # Panics
/// Panics if there is trouble parsing any of the arguments /// Panics if there is trouble parsing any of the arguments
pub fn extract_args(matches: &ArgMatches) -> Config { pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default(); let mut args = Config::default();
if let Some(addr) = matches.value_of("entrypoint") { if let Some(addr) = matches.value_of("entrypoint") {

View File

@@ -1,3 +1,2 @@
#![allow(clippy::integer_arithmetic)]
pub mod bench; pub mod bench;
pub mod cli; pub mod cli;

View File

@@ -1,9 +1,8 @@
#![allow(clippy::integer_arithmetic)]
use log::*; use log::*;
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs}; use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
use solana_bench_tps::cli; use solana_bench_tps::cli;
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
use solana_genesis::Base64Account; use solana_genesis::Base64Account;
use solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_client};
use solana_sdk::fee_calculator::FeeRateGovernor; use solana_sdk::fee_calculator::FeeRateGovernor;
use solana_sdk::signature::{Keypair, Signer}; use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_program; use solana_sdk::system_program;

View File

@@ -1,32 +1,24 @@
#![allow(clippy::integer_arithmetic)] use serial_test_derive::serial;
use serial_test::serial; use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs};
use solana_bench_tps::{ use solana_bench_tps::cli::Config;
bench::{do_bench_tps, generate_and_fund_keypairs},
cli::Config,
};
use solana_client::thin_client::create_client; use solana_client::thin_client::create_client;
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_with_port; use solana_faucet::faucet::run_local_faucet;
use solana_gossip::cluster_info::VALIDATOR_PORT_RANGE; use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_local_cluster::{
local_cluster::{ClusterConfig, LocalCluster},
validator_configs::make_identical_validator_configs,
};
use solana_sdk::signature::{Keypair, Signer}; use solana_sdk::signature::{Keypair, Signer};
use std::{ use std::sync::{mpsc::channel, Arc};
sync::{mpsc::channel, Arc}, use std::time::Duration;
time::Duration,
};
fn test_bench_tps_local_cluster(config: Config) { fn test_bench_tps_local_cluster(config: Config) {
let native_instruction_processors = vec![]; let native_instruction_processors = vec![];
solana_logger::setup(); solana_logger::setup();
const NUM_NODES: usize = 1; const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&mut ClusterConfig { let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES], node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 200_000_000, cluster_lamports: 200_000_000,
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES), validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
native_instruction_processors, native_instruction_processors,
..ClusterConfig::default() ..ClusterConfig::default()
}); });
@@ -44,11 +36,8 @@ fn test_bench_tps_local_cluster(config: Config) {
)); ));
let (addr_sender, addr_receiver) = channel(); let (addr_sender, addr_receiver) = channel();
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0); run_local_faucet(faucet_keypair, addr_sender, None);
let faucet_addr = addr_receiver let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
.recv_timeout(Duration::from_secs(2))
.expect("run_local_faucet")
.expect("faucet_addr");
let lamports_per_account = 100; let lamports_per_account = 100;
@@ -71,9 +60,9 @@ fn test_bench_tps_local_cluster(config: Config) {
#[test] #[test]
#[serial] #[serial]
fn test_bench_tps_local_cluster_solana() { fn test_bench_tps_local_cluster_solana() {
test_bench_tps_local_cluster(Config { let mut config = Config::default();
tx_count: 100, config.tx_count = 100;
duration: Duration::from_secs(10), config.duration = Duration::from_secs(10);
..Config::default()
}); test_bench_tps_local_cluster(config);
} }

28
cargo
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
# shellcheck source=ci/rust-version.sh
here=$(dirname "$0")
toolchain=
case "$1" in
stable)
source "${here}"/ci/rust-version.sh stable
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
toolchain="$rust_stable"
shift
;;
nightly)
source "${here}"/ci/rust-version.sh nightly
# shellcheck disable=SC2054 # rust_nightly is sourced from rust-version.sh
toolchain="$rust_nightly"
shift
;;
*)
source "${here}"/ci/rust-version.sh stable
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
toolchain="$rust_stable"
;;
esac
set -x
exec cargo "+${toolchain}" "${@}"

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
here=$(dirname "$0")
maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf"
for a in "$@"; do
if [[ $a = --bpf-sdk ]]; then
maybe_bpf_sdk=
fi
done
set -x
exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-build-bpf/Cargo.toml -- $maybe_bpf_sdk "$@"

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
here=$(dirname "$0")
maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf"
for a in "$@"; do
if [[ $a = --bpf-sdk ]]; then
maybe_bpf_sdk=
fi
done
export CARGO_BUILD_BPF="$here"/cargo-build-bpf
set -x
exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-test-bpf/Cargo.toml -- $maybe_bpf_sdk "$@"

View File

@@ -47,8 +47,6 @@ sudo ./setup-new-buildkite-agent/setup-buildkite.sh
``` ```
- Copy the pubkey contents from `~buildkite-agent/.ssh/id_ecdsa.pub` and - Copy the pubkey contents from `~buildkite-agent/.ssh/id_ecdsa.pub` and
add the pubkey as an authorized SSH key on github. add the pubkey as an authorized SSH key on github.
- In net/scripts/solana-user-authorized_keys.sh
- Bug mvines to add it to the "solana-grimes" github user
- Edit `/etc/buildkite-agent/buildkite-agent.cfg` and/or `/etc/systemd/system/buildkite-agent@*` to the desired configuration of the agent(s) - Edit `/etc/buildkite-agent/buildkite-agent.cfg` and/or `/etc/systemd/system/buildkite-agent@*` to the desired configuration of the agent(s)
- Copy `ejson` keys from another CI node at `/opt/ejson/keys/` - Copy `ejson` keys from another CI node at `/opt/ejson/keys/`
to the same location on the new node. to the same location on the new node.

View File

@@ -175,30 +175,6 @@ EOF
"Stable-perf skipped as no relevant files were modified" "Stable-perf skipped as no relevant files were modified"
fi fi
# Downstream backwards compatibility
if affects \
.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/ \
^scripts/build-downstream-projects.sh \
; then
cat >> "$output_file" <<"EOF"
- command: "scripts/build-downstream-projects.sh"
name: "downstream-projects"
timeout_in_minutes: 30
EOF
else
annotate --style info \
"downstream-projects skipped as no relevant files were modified"
fi
# Benches... # Benches...
if affects \ if affects \
.rs$ \ .rs$ \
@@ -263,7 +239,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
annotate --style info --context pr-backlink \ annotate --style info --context pr-backlink \
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH" "Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
if [[ $GITHUB_USER = "dependabot[bot]" ]]; then if [[ $GITHUB_USER = "dependabot-preview[bot]" ]]; then
command_step dependabot "ci/dependabot-pr.sh" 5 command_step dependabot "ci/dependabot-pr.sh" 5
wait_step wait_step
fi fi

View File

@@ -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-bpf-sdk.sh"
timeout_in_minutes: 5
name: "publish bpf sdk"
- wait - wait
- command: "sdk/docker-solana/build.sh" - command: "sdk/docker-solana/build.sh"
timeout_in_minutes: 60 timeout_in_minutes: 60

View File

@@ -89,26 +89,12 @@ 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" CHANNEL=stable
elif [[ -n $CI_BRANCH ]]; then elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
BRANCH="$CI_BRANCH" CHANNEL=edge
fi elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
CHANNEL=beta
if [[ -z "$CHANNEL" ]]; then
if [[ $BRANCH = "$STABLE_CHANNEL" ]]; then
CHANNEL=stable
elif [[ $BRANCH = "$EDGE_CHANNEL" ]]; then
CHANNEL=edge
elif [[ $BRANCH = "$BETA_CHANNEL" ]]; then
CHANNEL=beta
fi
fi
if [[ $CHANNEL = beta ]]; then
CHANNEL_LATEST_TAG="$BETA_CHANNEL_LATEST_TAG"
elif [[ $CHANNEL = stable ]]; then
CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG"
fi fi
echo EDGE_CHANNEL="$EDGE_CHANNEL" echo EDGE_CHANNEL="$EDGE_CHANNEL"
@@ -117,6 +103,5 @@ echo BETA_CHANNEL_LATEST_TAG="$BETA_CHANNEL_LATEST_TAG"
echo STABLE_CHANNEL="$STABLE_CHANNEL" echo STABLE_CHANNEL="$STABLE_CHANNEL"
echo STABLE_CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG" echo STABLE_CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG"
echo CHANNEL="$CHANNEL" echo CHANNEL="$CHANNEL"
echo CHANNEL_LATEST_TAG="$CHANNEL_LATEST_TAG"
exit 0 exit 0

View File

@@ -6,13 +6,13 @@ 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\[bot\]" --oneline -n1 | git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
grep -o '[Bb]ump.*$' | grep -o '[Bb]ump.*$' |
sed -r 's/[Bb]ump ([^ ]+) from ([^ ]+) to ([^ ]+)/-p \1:\2 --precise \3/' sed -r 's/[Bb]ump ([^ ]+) 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\[bot\]" --oneline -n1 | git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
grep -o '[Bb]ump.*$' | grep -o '[Bb]ump.*$' |
sed -r 's/[Bb]ump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/' sed -r 's/[Bb]ump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/'
)" )"

View File

@@ -1,43 +0,0 @@
#!/usr/bin/env bash
set -e
here="$(dirname "$0")"
src_root="$(readlink -f "${here}/..")"
cd "${src_root}"
cargo_audit_ignores=(
# failure is officially deprecated/unmaintained
#
# 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
# Potential segfault in the time crate
#
# Blocked on multiple crates updating `time` to >= 0.2.23
--ignore RUSTSEC-2020-0071
# difference is unmaintained
#
# Blocked on predicates v1.0.6 removing its dependency on `difference`
--ignore RUSTSEC-2020-0095
# generic-array: arr! macro erases lifetimes
#
# Blocked on libsecp256k1 releasing with upgraded dependencies
# https://github.com/paritytech/libsecp256k1/issues/66
--ignore RUSTSEC-2020-0146
)
scripts/cargo-for-all-lock-files.sh stable audit "${cargo_audit_ignores[@]}"

View File

@@ -1,4 +1,4 @@
FROM solanalabs/rust:1.52.1 FROM solanalabs/rust:1.45.1
ARG date ARG date
RUN set -x \ RUN set -x \

View File

@@ -20,7 +20,7 @@ To update the pinned version:
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 `YYYY-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_ALLOCATE_TTY=1 SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-checks.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]...` 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

View File

@@ -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.52.1 FROM rust:1.45.1
# 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

View File

@@ -70,7 +70,7 @@ done
source ci/upload-ci-artifact.sh source ci/upload-ci-artifact.sh
source scripts/configure-metrics.sh source scripts/configure-metrics.sh
source multinode-demo/common.sh --prebuild source multinode-demo/common.sh
nodes=( nodes=(
"multinode-demo/bootstrap-validator.sh \ "multinode-demo/bootstrap-validator.sh \
@@ -78,6 +78,7 @@ nodes=(
--init-complete-file init-complete-node0.log \ --init-complete-file init-complete-node0.log \
--dynamic-port-range 8000-8050" --dynamic-port-range 8000-8050"
"multinode-demo/validator.sh \ "multinode-demo/validator.sh \
--enable-rpc-exit \
--no-restart \ --no-restart \
--dynamic-port-range 8050-8100 --dynamic-port-range 8050-8100
--init-complete-file init-complete-node1.log \ --init-complete-file init-complete-node1.log \
@@ -200,10 +201,17 @@ killNodes() {
[[ ${#pids[@]} -gt 0 ]] || return [[ ${#pids[@]} -gt 0 ]] || return
# Try to use the RPC exit API to cleanly exit the first two nodes # Try to use the RPC exit API to cleanly exit the first two nodes
# (dynamic nodes, -x, are just killed) # (dynamic nodes, -x, are just killed since their RPC port is not known)
echo "--- RPC exit" echo "--- RPC exit"
$solana_validator --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator exit --force || true for port in 8899 18899; do
$solana_validator --ledger "$SOLANA_CONFIG_DIR"/validator exit --force || true (
set -x
curl --retry 5 --retry-delay 2 --retry-connrefused \
-X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' \
http://localhost:$port
)
done
# Give the nodes a splash of time to cleanly exit before killing them # Give the nodes a splash of time to cleanly exit before killing them
sleep 2 sleep 2

View File

@@ -19,7 +19,6 @@ declare prints=(
# Parts of the tree that are expected to be print free # Parts of the tree that are expected to be print free
declare print_free_tree=( declare print_free_tree=(
':core/src/**.rs' ':core/src/**.rs'
':^core/src/validator.rs'
':faucet/src/**.rs' ':faucet/src/**.rs'
':ledger/src/**.rs' ':ledger/src/**.rs'
':metrics/src/**.rs' ':metrics/src/**.rs'
@@ -27,9 +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/cargo-build-bpf/**.rs'
':^sdk/program/src/program_option.rs'
':^sdk/program/src/program_stubs.rs'
':programs/**.rs' ':programs/**.rs'
':^**bin**.rs' ':^**bin**.rs'
':^**bench**.rs' ':^**bench**.rs'

View File

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

36
ci/publish-bpf-sdk.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
eval "$(ci/channel-info.sh)"
if [[ -n "$CI_TAG" ]]; then
CHANNEL_OR_TAG=$CI_TAG
else
CHANNEL_OR_TAG=$CHANNEL
fi
(
set -x
sdk/bpf/scripts/package.sh
[[ -f bpf-sdk.tar.bz2 ]]
)
echo --- AWS S3 Store
if [[ -z $CHANNEL_OR_TAG ]]; then
echo Skipped
else
(
set -x
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/bpf-sdk.tar.bz2 \
s3://solana-sdk/"$CHANNEL_OR_TAG"/bpf-sdk.tar.bz2
)
fi
exit 0

View File

@@ -4,8 +4,6 @@ cd "$(dirname "$0")/.."
source ci/semver_bash/semver.sh source ci/semver_bash/semver.sh
source ci/rust-version.sh stable source ci/rust-version.sh stable
cargo="$(readlink -f ./cargo)"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
is_crate_version_uploaded() { is_crate_version_uploaded() {
name=$1 name=$1
@@ -40,7 +38,7 @@ for Cargo_toml in $Cargo_tomls; do
crate_name=$(grep -m 1 '^name = ' "$Cargo_toml" | cut -f 3 -d ' ' | tr -d \") crate_name=$(grep -m 1 '^name = ' "$Cargo_toml" | cut -f 3 -d ' ' | tr -d \")
if grep -q "^publish = false" "$Cargo_toml"; then if grep -q "^publish = false" "$Cargo_toml"; then
echo "$crate_name is marked as unpublishable" echo "$crate_name is is marked as unpublishable"
continue continue
fi fi
@@ -68,11 +66,11 @@ for Cargo_toml in $Cargo_tomls; do
( (
set -x set -x
rm -rf crate-test rm -rf crate-test
"$cargo" stable init crate-test cargo +"$rust_stable" init crate-test
cd crate-test/ cd crate-test/
echo "${crate_name} = \"${expectedCrateVersion}\"" >> Cargo.toml echo "${crate_name} = \"${expectedCrateVersion}\"" >> Cargo.toml
echo "[workspace]" >> Cargo.toml echo "[workspace]" >> Cargo.toml
"$cargo" stable check cargo +"$rust_stable" check
) && really_uploaded=1 ) && really_uploaded=1
if ((really_uploaded)); then if ((really_uploaded)); then
break; break;

View File

@@ -83,7 +83,7 @@ echo --- Creating release tarball
export CHANNEL export CHANNEL
source ci/rust-version.sh stable source ci/rust-version.sh stable
scripts/cargo-install-all.sh stable "${RELEASE_BASENAME}" scripts/cargo-install-all.sh +"$rust_stable" "${RELEASE_BASENAME}"
tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}" tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}"
bzip2 "${TARBALL_BASENAME}"-$TARGET.tar bzip2 "${TARBALL_BASENAME}"-$TARGET.tar
@@ -91,15 +91,17 @@ echo --- Creating release tarball
cp "${RELEASE_BASENAME}"/version.yml "${TARBALL_BASENAME}"-$TARGET.yml cp "${RELEASE_BASENAME}"/version.yml "${TARBALL_BASENAME}"-$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
@@ -113,10 +115,19 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.
if [[ -n $BUILDKITE ]]; then if [[ -n $BUILDKITE ]]; then
echo --- AWS S3 Store: "$file" echo --- AWS S3 Store: "$file"
upload-s3-artifact "/solana/$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file" (
set -x
$DRYRUN docker run \
--rm \
--env AWS_ACCESS_KEY_ID \
--env AWS_SECRET_ACCESS_KEY \
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
echo Published to: 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
ci/upload-github-release-asset.sh "$file" ci/upload-github-release-asset.sh "$file"
@@ -138,22 +149,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 $DO_NOT_PUBLISH_TAR ]]; then
echo "Skipping publishing install wrapper"
elif [[ -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"
$DRYRUN upload-s3-artifact "/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

View File

@@ -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
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./run.sh & timeout 120 ./run.sh &
pid=$! pid=$!
attempts=20 attempts=20
@@ -16,20 +16,16 @@ while [[ ! -f config/run/init-completed ]]; do
if ((--attempts == 0)); then if ((--attempts == 0)); then
echo "Error: validator failed to boot" echo "Error: validator failed to boot"
exit 1 exit 1
else
echo "Checking init"
fi fi
done done
snapshot_slot=1 snapshot_slot=1
# wait a bit longer than snapshot_slot # wait a bit longer than snapshot_slot
while [[ $($solana_cli --url http://localhost:8899 slot --commitment processed) -le $((snapshot_slot + 1)) ]]; do while [[ $($solana_cli --url http://localhost:8899 slot --commitment recent) -le $((snapshot_slot + 1)) ]]; do
sleep 1 sleep 1
echo "Checking slot"
done done
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
$solana_validator --ledger config/ledger exit --force || true
wait $pid wait $pid

View File

@@ -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.52.1 stable_version=1.45.1
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=2021-05-18 nightly_version=2020-07-27
fi fi

View File

@@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
TimeoutStartSec=10 TimeoutStartSec=10
TimeoutStopSec=0 TimeoutStopSec=0
KillMode=process KillMode=process
LimitNOFILE=700000 LimitNOFILE=65536
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -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 700000" > /etc/security/limits.d/90-solana-nofiles.conf sed -i 's/^\(# End of file\)/* soft nofile 65535\n\n\1/' /etc/security/limits.conf

View File

@@ -7,7 +7,6 @@ SOLANA_ROOT="$HERE"/../..
source "$HERE"/utils.sh source "$HERE"/utils.sh
ensure_env || exit 1 ensure_env || exit 1
check_ssh_authorized_keys || exit 1
set -ex set -ex

View File

@@ -6,11 +6,6 @@ HERE="$(dirname "$0")"
source "$HERE"/utils.sh source "$HERE"/utils.sh
ensure_env || exit 1 ensure_env || exit 1
# This is a last ditch effort to prevent the caller from locking themselves
# out of the machine. Exiting here will likely leave the system in some
# half-configured state. To prevent this, duplicate the next line at the top
# of the entrypoint script.
check_ssh_authorized_keys || exit 1
set -xe set -xe
# Setup sshd # Setup sshd

View File

@@ -14,33 +14,3 @@ ensure_env() {
$RC $RC
} }
# Some scripts disable SSH password logins. If no one hash setup authorized_keys
# this will result in the machine being remotely inaccessible. Check that the
# user running this script has setup their keys
check_ssh_authorized_keys() {
declare rc=false
declare user_home=
if [[ -n "$SUDO_USER" ]]; then
declare user uid gid home
declare passwd_entry
passwd_entry="$(grep "$SUDO_USER:[^:]*:$SUDO_UID:$SUDO_GID" /etc/passwd)"
IFS=: read -r user _ uid gid _ home _ <<<"$passwd_entry"
if [[ "$user" == "$SUDO_USER" && "$uid" == "$SUDO_UID" && "$gid" == "$SUDO_GID" ]]; then
user_home="$home"
fi
else
user_home="$HOME"
fi
declare authorized_keys="${user_home}/.ssh/authorized_keys"
if [[ -n "$user_home" ]]; then
[[ -s "$authorized_keys" ]] && rc=true
fi
if ! $rc; then
echo "ERROR! This script will disable SSH password logins and you don't"
echo "appear to have set up any authorized keys. Please add you SSH"
echo "public key to ${authorized_keys} before continuing!"
fi
$rc
}
check_ssh_authorized_keys

View File

@@ -6,8 +6,7 @@ source ci/_
source ci/upload-ci-artifact.sh source ci/upload-ci-artifact.sh
eval "$(ci/channel-info.sh)" eval "$(ci/channel-info.sh)"
source ci/rust-version.sh all
cargo="$(readlink -f "./cargo")"
set -o pipefail set -o pipefail
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
@@ -23,52 +22,40 @@ fi
BENCH_FILE=bench_output.log BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.log BENCH_ARTIFACT=current_bench_results.log
# solana-keygen required when building C programs # Clear the C dependency files, if dependeny moves these files are not regenerated
_ "$cargo" build --manifest-path=keygen/Cargo.toml
export PATH="$PWD/target/debug":$PATH
# Clear the C dependency files, if dependency moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Ensure all dependencies are built # Ensure all dependencies are built
_ "$cargo" nightly build --release _ cargo +$rust_nightly build --release
# Remove "BENCH_FILE", if it exists so that the following commands can append # Remove "BENCH_FILE", if it exists so that the following commands can append
rm -f "$BENCH_FILE" rm -f "$BENCH_FILE"
# Run sdk benches # Run sdk benches
_ "$cargo" nightly bench --manifest-path sdk/Cargo.toml ${V:+--verbose} \ _ cargo +$rust_nightly bench --manifest-path sdk/Cargo.toml ${V:+--verbose} \
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE" -- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
# Run runtime benches # Run runtime benches
_ "$cargo" nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \ _ cargo +$rust_nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
# Run gossip benches
_ "$cargo" nightly bench --manifest-path gossip/Cargo.toml ${V:+--verbose} \
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
# Run poh benches
_ "$cargo" nightly bench --manifest-path poh/Cargo.toml ${V:+--verbose} \
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE" -- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
# Run core benches # Run core benches
_ "$cargo" nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \ _ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE" -- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
# Run bpf benches # Run bpf benches
_ "$cargo" nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \ _ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE" -- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
# Run banking/accounts bench. Doesn't require nightly, but use since it is already built. # Run banking/accounts bench. Doesn't require nightly, but use since it is already built.
_ "$cargo" nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE" _ cargo +$rust_nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
_ "$cargo" nightly run --release --manifest-path accounts-bench/Cargo.toml ${V:+--verbose} -- --num_accounts 10000 --num_slots 4 | tee -a "$BENCH_FILE" _ cargo +$rust_nightly run --release --manifest-path accounts-bench/Cargo.toml ${V:+--verbose} -- --num_accounts 10000 --num_slots 4 | tee -a "$BENCH_FILE"
# `solana-upload-perf` disabled as it can take over 30 minutes to complete for some # `solana-upload-perf` disabled as it can take over 30 minutes to complete for some
# reason # reason
exit 0 exit 0
_ "$cargo" nightly run --release --package solana-upload-perf \ _ cargo +$rust_nightly run --release --package solana-upload-perf \
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT" -- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT"
upload-ci-artifact "$BENCH_FILE" upload-ci-artifact "$BENCH_FILE"

View File

@@ -8,19 +8,6 @@ source ci/_
source ci/rust-version.sh stable 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)"
cargo="$(readlink -f "./cargo")"
scripts/increment-cargo-version.sh check
# Disallow uncommitted Cargo.lock changes
(
_ scripts/cargo-for-all-lock-files.sh tree
set +e
if ! _ git diff --exit-code; then
echo -e "\nError: Uncommitted Cargo.lock changes" 1>&2
exit 1
fi
)
echo --- build environment echo --- build environment
( (
@@ -29,14 +16,14 @@ echo --- build environment
rustup run "$rust_stable" rustc --version --verbose rustup run "$rust_stable" rustc --version --verbose
rustup run "$rust_nightly" rustc --version --verbose rustup run "$rust_nightly" rustc --version --verbose
"$cargo" stable --version --verbose cargo +"$rust_stable" --version --verbose
"$cargo" nightly --version --verbose cargo +"$rust_nightly" --version --verbose
"$cargo" stable clippy --version --verbose cargo +"$rust_stable" clippy --version --verbose
"$cargo" nightly clippy --version --verbose cargo +"$rust_nightly" clippy --version --verbose
# audit is done only with stable # audit is done only with stable
"$cargo" stable audit --version cargo +"$rust_stable" audit --version
) )
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
@@ -45,7 +32,7 @@ export RUSTFLAGS="-D warnings -A incomplete_features"
# 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 # Exclude --benches as it's not available in rust stable yet
if _ scripts/cargo-for-all-lock-files.sh stable check --locked --tests --bins --examples; 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=$?
@@ -54,31 +41,32 @@ if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
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 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 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
# Ensure nightly and --benches
_ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets
_ ci/order-crates-for-publishing.py _ ci/order-crates-for-publishing.py
_ cargo +"$rust_stable" fmt --all -- --check
# -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612 # -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there # run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic _ cargo +"$rust_nightly" clippy -Zunstable-options --workspace --all-targets -- --deny=warnings
_ "$cargo" stable fmt --all -- --check _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
_ ci/do-audit.sh
{ {
cd programs/bpf cd programs/bpf
_ cargo +"$rust_stable" audit
for project in rust/*/ ; do for project in rust/*/ ; do
echo "+++ do_bpf_checks $project" echo "+++ do_bpf_checks $project"
( (
cd "$project" cd "$project"
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc _ cargo +"$rust_stable" fmt -- --check
_ "$cargo" stable fmt -- --check _ cargo +"$rust_nightly" test
_ cargo +"$rust_nightly" clippy -- --deny=warnings --allow=clippy::missing_safety_doc
) )
done done
} }

View File

@@ -8,16 +8,10 @@ source ci/_
( (
echo --- git diff --check echo --- git diff --check
set -x set -x
if [[ -n $CI_BASE_BRANCH ]]
then branch="$CI_BASE_BRANCH"
else branch="master"
fi
# Look for failed mergify.io backports by searching leftover conflict markers # Look for failed mergify.io backports by searching leftover conflict markers
# Also check for any trailing whitespaces! # Also check for any trailing whitespaces!
git fetch origin "$branch" git fetch origin "$CI_BASE_BRANCH"
git diff "$(git merge-base HEAD "origin/$branch")" --check --oneline git diff "$(git merge-base HEAD "origin/$CI_BASE_BRANCH")..HEAD" --check --oneline
) )
echo echo
@@ -25,29 +19,4 @@ echo
_ ci/nits.sh _ ci/nits.sh
_ ci/check-ssh-keys.sh _ ci/check-ssh-keys.sh
# Ensure the current channel version is not equal ("greater") than
# the version of the latest tag
if [[ -z $CI_TAG ]]; then
echo "--- channel version check"
(
eval "$(ci/channel-info.sh)"
if [[ -n $CHANNEL_LATEST_TAG ]]; then
source scripts/read-cargo-variable.sh
version=$(readCargoVariable version "version/Cargo.toml")
echo "version: v$version"
echo "latest channel tag: $CHANNEL_LATEST_TAG"
if [[ $CHANNEL_LATEST_TAG = v$version ]]; then
echo "Error: please run ./scripts/increment-cargo-version.sh"
exit 1
fi
else
echo "Skipped. CHANNEL_LATEST_TAG (CHANNEL=$CHANNEL) unset"
fi
)
fi
echo --- ok echo --- ok

View File

@@ -2,8 +2,6 @@
set -e set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
cargo="$(readlink -f "./cargo")"
source ci/_ source ci/_
annotate() { annotate() {
@@ -21,10 +19,16 @@ export RUST_BACKTRACE=1
export RUSTFLAGS="-D warnings" export RUSTFLAGS="-D warnings"
source scripts/ulimit-n.sh source scripts/ulimit-n.sh
# Clear cached json keypair files
rm -rf "$HOME/.config/solana"
# Clear the C dependency files, if dependency moves these files are not regenerated # Clear the C dependency files, if dependency moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Clear the BPF sysroot files, they are not automatically rebuilt
rm -rf target/xargo # Issue #3105
# Limit compiler jobs to reduce memory usage # Limit compiler jobs to reduce memory usage
# on machines with 2gb/thread of memory # on machines with 2gb/thread of memory
NPROC=$(nproc) NPROC=$(nproc)
@@ -33,23 +37,12 @@ NPROC=$((NPROC>14 ? 14 : NPROC))
echo "Executing $testName" echo "Executing $testName"
case $testName in case $testName in
test-stable) test-stable)
_ "$cargo" 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
;; ;;
test-stable-perf) test-stable-perf)
# solana-keygen required when building C programs # BPF program tests
_ "$cargo" build --manifest-path=keygen/Cargo.toml
export PATH="$PWD/target/debug":$PATH
# BPF solana-sdk legacy compile test
./cargo-build-bpf --manifest-path sdk/Cargo.toml
# BPF Program unit tests
"$cargo" test --manifest-path programs/bpf/Cargo.toml
cargo-build-bpf --manifest-path programs/bpf/Cargo.toml --bpf-sdk sdk/bpf
# BPF program system tests
_ make -C programs/bpf/c tests _ make -C programs/bpf/c tests
_ "$cargo" stable test \ _ cargo +"$rust_stable" test \
--manifest-path programs/bpf/Cargo.toml \ --manifest-path programs/bpf/Cargo.toml \
--no-default-features --features=bpf_c,bpf_rust -- --nocapture --no-default-features --features=bpf_c,bpf_rust -- --nocapture
@@ -69,13 +62,13 @@ test-stable-perf)
export SOLANA_CUDA=1 export SOLANA_CUDA=1
fi fi
_ "$cargo" stable build --bins ${V:+--verbose} _ cargo +"$rust_stable" build --bins ${V:+--verbose}
_ "$cargo" 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" stable run --manifest-path poh-bench/Cargo.toml ${V:+--verbose} -- --hashes-per-tick 10 _ cargo +"$rust_stable" run --manifest-path poh-bench/Cargo.toml ${V:+--verbose} -- --hashes-per-tick 10
;; ;;
test-local-cluster) test-local-cluster)
_ "$cargo" stable build --release --bins ${V:+--verbose} _ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
_ "$cargo" stable test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1 _ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
exit 0 exit 0
;; ;;
*) *)

View File

@@ -16,16 +16,3 @@ upload-ci-artifact() {
fi fi
} }
upload-s3-artifact() {
echo "--- artifact: $1 to $2"
(
set -x
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 "$1" "$2"
)
}

View File

@@ -1,28 +1,23 @@
[package] [package]
name = "solana-clap-utils" name = "solana-clap-utils"
version = "1.7.1" version = "1.3.2"
description = "Solana utilities for the clap" description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
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/"
documentation = "https://docs.rs/solana-clap-utils"
edition = "2018" 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.7.1" } solana-remote-wallet = { path = "../remote-wallet", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
thiserror = "1.0.21" thiserror = "1.0.20"
tiny-bip39 = "0.8.0" tiny-bip39 = "0.7.0"
uriparse = "0.6.3"
url = "2.1.0" url = "2.1.0"
chrono = "0.4" chrono = "0.4"
[dev-dependencies]
tempfile = "3.1.0"
[lib] [lib]
name = "solana_clap_utils" name = "solana_clap_utils"

View File

@@ -0,0 +1,22 @@
use crate::ArgConstant;
use clap::Arg;
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
name: "commitment",
long: "commitment",
help: "Return information at the selected commitment level",
};
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
commitment_arg_with_default("recent")
}
pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'a, 'b> {
Arg::with_name(COMMITMENT_ARG.name)
.long(COMMITMENT_ARG.long)
.takes_value(true)
.possible_values(&["recent", "single", "root", "max"])
.default_value(default_value)
.value_name("COMMITMENT_LEVEL")
.help(COMMITMENT_ARG.help)
}

View File

@@ -1,21 +0,0 @@
use {
crate::{input_validators, ArgConstant},
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)
}

View File

@@ -1,24 +1,18 @@
use { use crate::keypair::{
crate::keypair::{ keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
},
chrono::DateTime,
clap::ArgMatches,
solana_remote_wallet::remote_wallet::RemoteWalletManager,
solana_sdk::{
clock::UnixTimestamp,
commitment_config::CommitmentConfig,
genesis_config::ClusterType,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, Signature, Signer},
},
std::{str::FromStr, sync::Arc},
}; };
use chrono::DateTime;
// Sentinel value used to indicate to write to screen instead of file use clap::ArgMatches;
pub const STDOUT_OUTFILE_TOKEN: &str = "-"; use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
clock::UnixTimestamp,
commitment_config::CommitmentConfig,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, Signature, Signer},
};
use std::{str::FromStr, sync::Arc};
// Return parsed values from matches at `name` // Return parsed values from matches at `name`
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>> pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
@@ -60,7 +54,7 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
if let Some(value) = matches.value_of(name) { if let Some(value) = matches.value_of(name) {
if value == ASK_KEYWORD { if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() keypair_from_seed_phrase(name, skip_validation, true).ok()
} else { } else {
read_keypair_file(value).ok() read_keypair_file(value).ok()
} }
@@ -75,7 +69,7 @@ pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>>
.filter_map(|value| { .filter_map(|value| {
if value == ASK_KEYWORD { if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok() keypair_from_seed_phrase(name, skip_validation, true).ok()
} else { } else {
read_keypair_file(value).ok() read_keypair_file(value).ok()
} }
@@ -172,26 +166,26 @@ pub fn resolve_signer(
name: &str, name: &str,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>, wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn std::error::Error>> { ) -> Result<Option<String>, Box<dyn std::error::Error>> {
resolve_signer_from_path( Ok(resolve_signer_from_path(
matches, matches,
matches.value_of(name).unwrap(), matches.value_of(name).unwrap(),
name, name,
wallet_manager, wallet_manager,
) )?)
} }
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> { 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 matches.value_of(name).map(|value| match value {
.value_of(name) "max" => CommitmentConfig::max(),
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default()) "recent" => CommitmentConfig::recent(),
"root" => CommitmentConfig::root(),
"single" => CommitmentConfig::single(),
_ => CommitmentConfig::default(),
})
} }
#[cfg(test)] #[cfg(test)]
@@ -229,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",
@@ -252,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()]);
@@ -332,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);

View File

@@ -1,15 +1,13 @@
use { use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
crate::keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD}, use chrono::DateTime;
chrono::DateTime, use solana_sdk::{
solana_sdk::{ clock::Slot,
clock::{Epoch, Slot}, hash::Hash,
hash::Hash, pubkey::Pubkey,
pubkey::{Pubkey, MAX_SEED_LEN}, signature::{read_keypair_file, Signature},
signature::{read_keypair_file, Signature},
},
std::fmt::Display,
std::str::FromStr,
}; };
use std::fmt::Display;
use std::str::FromStr;
fn is_parsable_generic<U, T>(string: T) -> Result<(), String> fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
where where
@@ -34,29 +32,6 @@ where
is_parsable_generic::<T, String>(string) is_parsable_generic::<T, String>(string)
} }
// Return an error if string cannot be parsed as numeric type T, and value not within specified
// range
pub fn is_within_range<T>(string: String, range_min: T, range_max: T) -> Result<(), String>
where
T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
T::Err: Display,
{
match string.parse::<T>() {
Ok(input) => {
let range = range_min..range_max + 1.into();
if !range.contains(&input) {
Err(format!(
"input '{:?}' out of range ({:?}..{:?}]",
input, range_min, range_max
))
} else {
Ok(())
}
}
Err(err) => Err(format!("error parsing '{}': {}", string, err)),
}
}
// Return an error if a pubkey cannot be parsed. // Return an error if a pubkey cannot be parsed.
pub fn is_pubkey<T>(string: T) -> Result<(), String> pub fn is_pubkey<T>(string: T) -> Result<(), String>
where where
@@ -96,26 +71,6 @@ where
.map_err(|err| format!("{}", err)) .map_err(|err| format!("{}", err))
} }
// Return an error if a `SignerSourceKind::Prompt` cannot be parsed
pub fn is_prompt_signer_source<T>(string: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
if string.as_ref() == ASK_KEYWORD {
return Ok(());
}
match parse_signer_source(string.as_ref())
.map_err(|err| format!("{}", err))?
.kind
{
SignerSourceKind::Prompt => Ok(()),
_ => Err(format!(
"Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {}",
string
)),
}
}
// Return an error if string cannot be parsed as pubkey string or keypair file location // Return an error if string cannot be parsed as pubkey string or keypair file location
pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String> pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
where where
@@ -130,11 +85,8 @@ pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
where where
T: AsRef<str> + Display, T: AsRef<str> + Display,
{ {
match parse_signer_source(string.as_ref()) match parse_keypair_path(string.as_ref()) {
.map_err(|err| format!("{}", err))? KeypairUrl::Filepath(path) => is_keypair(path),
.kind
{
SignerSourceKind::Filepath(path) => is_keypair(path),
_ => Ok(()), _ => Ok(()),
} }
} }
@@ -196,40 +148,6 @@ where
} }
} }
pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
Ok(url) => {
if url.has_host() {
Ok(())
} else {
Err("no host provided".to_string())
}
}
Err(err) => Err(format!("{}", err)),
}
}
pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
match url_or_moniker.as_ref() {
"m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
"t" | "testnet" => "https://api.testnet.solana.com",
"d" | "devnet" => "https://api.devnet.solana.com",
"l" | "localhost" => "http://localhost:8899",
url => url,
}
.to_string()
}
pub fn is_epoch<T>(epoch: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
is_parsable_generic::<Epoch, _>(epoch)
}
pub fn is_slot<T>(slot: T) -> Result<(), String> pub fn is_slot<T>(slot: T) -> Result<(), String>
where where
T: AsRef<str> + Display, T: AsRef<str> + Display,
@@ -339,21 +257,6 @@ where
.map(|_| ()) .map(|_| ())
} }
pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
let value = value.as_ref();
if value.len() > MAX_SEED_LEN {
Err(format!(
"Address seed must not be longer than {} bytes",
MAX_SEED_LEN
))
} else {
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -1,313 +1,49 @@
use { use crate::{
crate::{ input_parsers::pubkeys_sigs_of,
input_parsers::{pubkeys_sigs_of, STDOUT_OUTFILE_TOKEN}, offline::{SIGNER_ARG, SIGN_ONLY_ARG},
offline::{SIGNER_ARG, SIGN_ONLY_ARG}, ArgConstant,
ArgConstant, };
use bip39::{Language, Mnemonic, Seed};
use clap::ArgMatches;
use rpassword::prompt_password_stderr;
use solana_remote_wallet::{
remote_keypair::generate_remote_keypair,
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
};
use solana_sdk::{
pubkey::Pubkey,
signature::{
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
}, },
bip39::{Language, Mnemonic, Seed}, };
clap::ArgMatches, use std::{
rpassword::prompt_password_stderr, error,
solana_remote_wallet::{ io::{stdin, stdout, Write},
locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError}, process::exit,
remote_keypair::generate_remote_keypair, str::FromStr,
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager}, sync::Arc,
},
solana_sdk::{
derivation_path::{DerivationPath, DerivationPathError},
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::{
generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed,
keypair_from_seed_and_derivation_path, keypair_from_seed_phrase_and_passphrase,
read_keypair, read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
},
},
std::{
cell::RefCell,
convert::TryFrom,
error,
io::{stdin, stdout, Write},
ops::Deref,
process::exit,
str::FromStr,
sync::Arc,
},
thiserror::Error,
}; };
pub struct SignOnly { pub enum KeypairUrl {
pub blockhash: Hash, Ask,
pub message: Option<String>,
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 fn index_of_or_none(&self, pubkey: Option<Pubkey>) -> Option<usize> {
if let Some(pubkey) = pubkey {
self.signers
.iter()
.position(|signer| signer.pubkey() == pubkey)
} else {
None
}
}
pub fn signers_for_message(&self, message: &Message) -> Vec<&dyn Signer> {
self.signers
.iter()
.filter_map(|k| {
if message.signer_keys().contains(&&k.pubkey()) {
Some(k.as_ref())
} else {
None
}
})
.collect()
}
}
#[derive(Debug, Default)]
pub struct DefaultSigner {
pub arg_name: String,
pub path: String,
is_path_checked: RefCell<bool>,
}
impl DefaultSigner {
pub fn new<AN: AsRef<str>, P: AsRef<str>>(arg_name: AN, path: P) -> Self {
let arg_name = arg_name.as_ref().to_string();
let path = path.as_ref().to_string();
Self {
arg_name,
path,
..Self::default()
}
}
fn path(&self) -> Result<&str, Box<dyn std::error::Error>> {
if !self.is_path_checked.borrow().deref() {
parse_signer_source(&self.path)
.and_then(|s| {
if let SignerSourceKind::Filepath(path) = &s.kind {
std::fs::metadata(path).map(|_| ()).map_err(|e| e.into())
} else {
Ok(())
}
})
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"No default signer found, run \"solana-keygen new -o {}\" to create a new one",
self.path
),
)
})?;
*self.is_path_checked.borrow_mut() = true;
}
Ok(&self.path)
}
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().flatten() {
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 fn signer_from_path_with_config(
&self,
matches: &ArgMatches,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
config: &SignerFromPathConfig,
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
signer_from_path_with_config(
matches,
self.path()?,
&self.arg_name,
wallet_manager,
config,
)
}
}
pub(crate) struct SignerSource {
pub kind: SignerSourceKind,
pub derivation_path: Option<DerivationPath>,
pub legacy: bool,
}
impl SignerSource {
fn new(kind: SignerSourceKind) -> Self {
Self {
kind,
derivation_path: None,
legacy: false,
}
}
fn new_legacy(kind: SignerSourceKind) -> Self {
Self {
kind,
derivation_path: None,
legacy: true,
}
}
}
const SIGNER_SOURCE_PROMPT: &str = "prompt";
const SIGNER_SOURCE_FILEPATH: &str = "file";
const SIGNER_SOURCE_USB: &str = "usb";
const SIGNER_SOURCE_STDIN: &str = "stdin";
const SIGNER_SOURCE_PUBKEY: &str = "pubkey";
pub(crate) enum SignerSourceKind {
Prompt,
Filepath(String), Filepath(String),
Usb(RemoteWalletLocator), Usb(String),
Stdin, Stdin,
Pubkey(Pubkey), Pubkey(Pubkey),
} }
impl AsRef<str> for SignerSourceKind { pub fn parse_keypair_path(path: &str) -> KeypairUrl {
fn as_ref(&self) -> &str { if path == "-" {
match self { KeypairUrl::Stdin
Self::Prompt => SIGNER_SOURCE_PROMPT, } else if path == ASK_KEYWORD {
Self::Filepath(_) => SIGNER_SOURCE_FILEPATH, KeypairUrl::Ask
Self::Usb(_) => SIGNER_SOURCE_USB, } else if path.starts_with("usb://") {
Self::Stdin => SIGNER_SOURCE_STDIN, KeypairUrl::Usb(path.to_string())
Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY, } else if let Ok(pubkey) = Pubkey::from_str(path) {
} KeypairUrl::Pubkey(pubkey)
} } else {
} KeypairUrl::Filepath(path.to_string())
impl std::fmt::Debug for SignerSourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s: &str = self.as_ref();
write!(f, "{}", s)
}
}
#[derive(Debug, Error)]
pub(crate) enum SignerSourceError {
#[error("unrecognized signer source")]
UnrecognizedSource,
#[error(transparent)]
RemoteWalletLocatorError(#[from] RemoteWalletLocatorError),
#[error(transparent)]
DerivationPathError(#[from] DerivationPathError),
#[error(transparent)]
IoError(#[from] std::io::Error),
}
pub(crate) fn parse_signer_source<S: AsRef<str>>(
source: S,
) -> Result<SignerSource, SignerSourceError> {
let source = source.as_ref();
let source = {
#[cfg(target_family = "windows")]
{
source.replace("\\", "/")
}
#[cfg(not(target_family = "windows"))]
{
source.to_string()
}
};
match uriparse::URIReference::try_from(source.as_str()) {
Err(_) => Err(SignerSourceError::UnrecognizedSource),
Ok(uri) => {
if let Some(scheme) = uri.scheme() {
let scheme = scheme.as_str().to_ascii_lowercase();
match scheme.as_str() {
SIGNER_SOURCE_PROMPT => Ok(SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: DerivationPath::from_uri_any_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(SignerSourceKind::Filepath(
uri.path().to_string(),
))),
SIGNER_SOURCE_USB => Ok(SignerSource {
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
derivation_path: DerivationPath::from_uri_key_query(&uri)?,
legacy: false,
}),
SIGNER_SOURCE_STDIN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
_ => {
#[cfg(target_family = "windows")]
// On Windows, an absolute path's drive letter will be parsed as the URI
// scheme. Assume a filepath source in case of a single character shceme.
if scheme.len() == 1 {
return Ok(SignerSource::new(SignerSourceKind::Filepath(source)));
}
Err(SignerSourceError::UnrecognizedSource)
}
}
} else {
match source.as_str() {
STDOUT_OUTFILE_TOKEN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
_ => match Pubkey::from_str(source.as_str()) {
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
Err(_) => std::fs::metadata(source.as_str())
.map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
.map_err(|err| err.into()),
},
}
}
}
} }
} }
@@ -324,72 +60,40 @@ pub fn presigner_from_pubkey_sigs(
}) })
} }
#[derive(Debug)]
pub struct SignerFromPathConfig {
pub allow_null_signer: bool,
}
impl Default for SignerFromPathConfig {
fn default() -> Self {
Self {
allow_null_signer: false,
}
}
}
pub fn signer_from_path( pub fn signer_from_path(
matches: &ArgMatches, matches: &ArgMatches,
path: &str, path: &str,
keypair_name: &str, keypair_name: &str,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>, wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> { ) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
let config = SignerFromPathConfig::default(); match parse_keypair_path(path) {
signer_from_path_with_config(matches, path, keypair_name, wallet_manager, &config) KeypairUrl::Ask => {
}
pub fn signer_from_path_with_config(
matches: &ArgMatches,
path: &str,
keypair_name: &str,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
config: &SignerFromPathConfig,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
match kind {
SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
Ok(Box::new(keypair_from_seed_phrase( Ok(Box::new(keypair_from_seed_phrase(
keypair_name, keypair_name,
skip_validation, skip_validation,
false, false,
derivation_path,
legacy,
)?)) )?))
} }
SignerSourceKind::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)),
}, },
SignerSourceKind::Stdin => { KeypairUrl::Stdin => {
let mut stdin = std::io::stdin(); let mut stdin = std::io::stdin();
Ok(Box::new(read_keypair(&mut stdin)?)) Ok(Box::new(read_keypair(&mut stdin)?))
} }
SignerSourceKind::Usb(locator) => { KeypairUrl::Usb(path) => {
if wallet_manager.is_none() { if wallet_manager.is_none() {
*wallet_manager = maybe_wallet_manager()?; *wallet_manager = maybe_wallet_manager()?;
} }
if let Some(wallet_manager) = wallet_manager { if let Some(wallet_manager) = wallet_manager {
Ok(Box::new(generate_remote_keypair( Ok(Box::new(generate_remote_keypair(
locator, path,
derivation_path.unwrap_or_default(),
wallet_manager, wallet_manager,
matches.is_present("confirm_key"), matches.is_present("confirm_key"),
keypair_name, keypair_name,
@@ -398,13 +102,13 @@ pub fn signer_from_path_with_config(
Err(RemoteWalletError::NoDeviceFound.into()) Err(RemoteWalletError::NoDeviceFound.into())
} }
} }
SignerSourceKind::Pubkey(pubkey) => { KeypairUrl::Pubkey(pubkey) => {
let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name) let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name)
.as_ref() .as_ref()
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners)); .and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
if let Some(presigner) = presigner { if let Some(presigner) = presigner {
Ok(Box::new(presigner)) Ok(Box::new(presigner))
} else if config.allow_null_signer || matches.is_present(SIGN_ONLY_ARG.name) { } else if matches.is_present(SIGN_ONLY_ARG.name) {
Ok(Box::new(NullSigner::new(&pubkey))) Ok(Box::new(NullSigner::new(&pubkey)))
} else { } else {
Err(std::io::Error::new( Err(std::io::Error::new(
@@ -423,9 +127,8 @@ pub fn pubkey_from_path(
keypair_name: &str, keypair_name: &str,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>, wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<Pubkey, Box<dyn error::Error>> { ) -> Result<Pubkey, Box<dyn error::Error>> {
let SignerSource { kind, .. } = parse_signer_source(path)?; match parse_keypair_path(path) {
match kind { KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
SignerSourceKind::Pubkey(pubkey) => Ok(pubkey),
_ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()), _ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
} }
} }
@@ -436,51 +139,34 @@ pub fn resolve_signer_from_path(
keypair_name: &str, keypair_name: &str,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>, wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn error::Error>> { ) -> Result<Option<String>, Box<dyn error::Error>> {
let SignerSource { match parse_keypair_path(path) {
kind, KeypairUrl::Ask => {
derivation_path,
legacy,
} = parse_signer_source(path)?;
match kind {
SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name); let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
// This method validates the seed phrase, but returns `None` because there is no path // This method validates the seed phrase, but returns `None` because there is no path
// on disk or to a device // on disk or to a device
keypair_from_seed_phrase( keypair_from_seed_phrase(keypair_name, skip_validation, false).map(|_| None)
keypair_name,
skip_validation,
false,
derivation_path,
legacy,
)
.map(|_| None)
} }
SignerSourceKind::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!( format!("could not find keypair file: {} error: {}", path, e),
"could not read keypair file \"{}\". \
Run \"solana-keygen new\" to create a keypair file: {}",
path, e
),
) )
.into()), .into()),
Ok(_) => Ok(Some(path.to_string())), Ok(_) => Ok(Some(path.to_string())),
}, },
SignerSourceKind::Stdin => { KeypairUrl::Stdin => {
let mut stdin = std::io::stdin(); let mut stdin = std::io::stdin();
// This method validates the keypair from stdin, but returns `None` because there is no // This method validates the keypair from stdin, but returns `None` because there is no
// path on disk or to a device // path on disk or to a device
read_keypair(&mut stdin).map(|_| None) read_keypair(&mut stdin).map(|_| None)
} }
SignerSourceKind::Usb(locator) => { KeypairUrl::Usb(path) => {
if wallet_manager.is_none() { if wallet_manager.is_none() {
*wallet_manager = maybe_wallet_manager()?; *wallet_manager = maybe_wallet_manager()?;
} }
if let Some(wallet_manager) = wallet_manager { if let Some(wallet_manager) = wallet_manager {
let path = generate_remote_keypair( let path = generate_remote_keypair(
locator, path,
derivation_path.unwrap_or_default(),
wallet_manager, wallet_manager,
matches.is_present("confirm_key"), matches.is_present("confirm_key"),
keypair_name, keypair_name,
@@ -495,7 +181,7 @@ pub fn resolve_signer_from_path(
} }
} }
// Keyword used to indicate that the user should be prompted for a keypair seed phrase // Keyword used to indicate that the user should be asked for a keypair seed phrase
pub const ASK_KEYWORD: &str = "ASK"; pub const ASK_KEYWORD: &str = "ASK";
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant { pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
@@ -516,56 +202,6 @@ pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>>
Ok(passphrase) Ok(passphrase)
} }
/// Parses a path into a SignerSource and returns a Keypair for supporting SignerSourceKinds
pub fn keypair_from_path(
matches: &ArgMatches,
path: &str,
keypair_name: &str,
confirm_pubkey: bool,
) -> Result<Keypair, Box<dyn error::Error>> {
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
match kind {
SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
Ok(keypair_from_seed_phrase(
keypair_name,
skip_validation,
confirm_pubkey,
derivation_path,
legacy,
)?)
}
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"could not read keypair file \"{}\". \
Run \"solana-keygen new\" to create a keypair file: {}",
path, e
),
)
.into()),
Ok(file) => Ok(file),
},
SignerSourceKind::Stdin => {
let mut stdin = std::io::stdin();
Ok(read_keypair(&mut stdin)?)
}
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"signer of type `{:?}` does not support Keypair output",
kind
),
)
.into()),
}
}
/// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation /// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation
/// Optionally skips validation of seed phrase /// Optionally skips validation of seed phrase
/// Optionally confirms recovered public key /// Optionally confirms recovered public key
@@ -573,8 +209,6 @@ pub fn keypair_from_seed_phrase(
keypair_name: &str, keypair_name: &str,
skip_validation: bool, skip_validation: bool,
confirm_pubkey: bool, confirm_pubkey: bool,
derivation_path: Option<DerivationPath>,
legacy: bool,
) -> Result<Keypair, Box<dyn error::Error>> { ) -> Result<Keypair, Box<dyn error::Error>> {
let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?; let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?;
let seed_phrase = seed_phrase.trim(); let seed_phrase = seed_phrase.trim();
@@ -585,39 +219,13 @@ pub fn keypair_from_seed_phrase(
let keypair = if skip_validation { let keypair = if skip_validation {
let passphrase = prompt_passphrase(&passphrase_prompt)?; let passphrase = prompt_passphrase(&passphrase_prompt)?;
if legacy { keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
} else {
let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase);
keypair_from_seed_and_derivation_path(&seed, derivation_path)?
}
} 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);
if legacy { keypair_from_seed(seed.as_bytes())?
keypair_from_seed(seed.as_bytes())?
} else {
keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)?
}
}; };
if confirm_pubkey { if confirm_pubkey {
@@ -645,9 +253,6 @@ fn sanitize_seed_phrase(seed_phrase: &str) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_remote_wallet::locator::Manufacturer;
use solana_sdk::system_instruction;
use tempfile::NamedTempFile;
#[test] #[test]
fn test_sanitize_seed_phrase() { fn test_sanitize_seed_phrase() {
@@ -657,153 +262,4 @@ mod tests {
sanitize_seed_phrase(seed_phrase) sanitize_seed_phrase(seed_phrase)
); );
} }
#[test]
fn test_signer_info_signers_for_message() {
let source = Keypair::new();
let fee_payer = Keypair::new();
let nonsigner1 = Keypair::new();
let nonsigner2 = Keypair::new();
let recipient = Pubkey::new_unique();
let message = Message::new(
&[system_instruction::transfer(
&source.pubkey(),
&recipient,
42,
)],
Some(&fee_payer.pubkey()),
);
let signers = vec![
Box::new(fee_payer) as Box<dyn Signer>,
Box::new(source) as Box<dyn Signer>,
Box::new(nonsigner1) as Box<dyn Signer>,
Box::new(nonsigner2) as Box<dyn Signer>,
];
let signer_info = CliSignerInfo { signers };
let msg_signers = signer_info.signers_for_message(&message);
let signer_pubkeys = msg_signers.iter().map(|s| s.pubkey()).collect::<Vec<_>>();
let expect = vec![
signer_info.signers[0].pubkey(),
signer_info.signers[1].pubkey(),
];
assert_eq!(signer_pubkeys, expect);
}
#[test]
fn test_parse_signer_source() {
assert!(matches!(
parse_signer_source(STDOUT_OUTFILE_TOKEN).unwrap(),
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
));
let stdin = "stdin:".to_string();
assert!(matches!(
parse_signer_source(&stdin).unwrap(),
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
));
assert!(matches!(
parse_signer_source(ASK_KEYWORD).unwrap(),
SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: None,
legacy: true,
}
));
let pubkey = Pubkey::new_unique();
assert!(
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
kind: SignerSourceKind::Pubkey(p),
derivation_path: None,
legacy: false,
}
if p == pubkey)
);
// Set up absolute and relative path strs
let file0 = NamedTempFile::new().unwrap();
let path = file0.path();
assert!(path.is_absolute());
let absolute_path_str = path.to_str().unwrap();
let file1 = NamedTempFile::new_in(std::env::current_dir().unwrap()).unwrap();
let path = file1.path().file_name().unwrap().to_str().unwrap();
let path = std::path::Path::new(path);
assert!(path.is_relative());
let relative_path_str = path.to_str().unwrap();
assert!(
matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == absolute_path_str)
);
assert!(
matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == relative_path_str)
);
let usb = "usb://ledger".to_string();
let expected_locator = RemoteWalletLocator {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
kind: SignerSourceKind::Usb(u),
derivation_path: None,
legacy: false,
} if u == expected_locator));
let usb = "usb://ledger?key=0/0".to_string();
let expected_locator = RemoteWalletLocator {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
let expected_derivation_path = Some(DerivationPath::new_bip44(Some(0), Some(0)));
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
kind: SignerSourceKind::Usb(u),
derivation_path: d,
legacy: false,
} if u == expected_locator && d == expected_derivation_path));
// Catchall into SignerSource::Filepath fails
let junk = "sometextthatisnotapubkeyorfile".to_string();
assert!(Pubkey::from_str(&junk).is_err());
assert!(matches!(
parse_signer_source(&junk),
Err(SignerSourceError::IoError(_))
));
let prompt = "prompt:".to_string();
assert!(matches!(
parse_signer_source(&prompt).unwrap(),
SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: None,
legacy: false,
}
));
assert!(
matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == absolute_path_str)
);
assert!(
matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource {
kind: SignerSourceKind::Filepath(p),
derivation_path: None,
legacy: false,
} if p == relative_path_str)
);
}
} }

View File

@@ -23,10 +23,8 @@ impl std::fmt::Debug for DisplayError {
} }
} }
pub mod fee_payer; pub mod commitment;
pub mod input_parsers; pub mod input_parsers;
pub mod input_validators; pub mod input_validators;
pub mod keypair; pub mod keypair;
pub mod memo;
pub mod nonce;
pub mod offline; pub mod offline;

View File

@@ -1,15 +0,0 @@
use {crate::ArgConstant, clap::Arg};
pub const MEMO_ARG: ArgConstant<'static> = ArgConstant {
name: "memo",
long: "--with-memo",
help: "Specify a memo string to include in the transaction.",
};
pub fn memo_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(MEMO_ARG.name)
.long(MEMO_ARG.long)
.takes_value(true)
.value_name("MEMO")
.help(MEMO_ARG.help)
}

View File

@@ -1,52 +0,0 @@
use {
crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant},
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),
)
}
}

View File

@@ -1,7 +1,4 @@
use { use crate::ArgConstant;
crate::{input_validators::*, ArgConstant},
clap::{App, Arg},
};
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant { pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
name: "blockhash", name: "blockhash",
@@ -20,79 +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 const DUMP_TRANSACTION_MESSAGE: ArgConstant<'static> = ArgConstant {
name: "dump_transaction_message",
long: "dump-transaction-message",
help: "Display the base64 encoded binary transaction message in sign-only mode",
};
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 fn dump_transaction_message<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(DUMP_TRANSACTION_MESSAGE.name)
.long(DUMP_TRANSACTION_MESSAGE.long)
.takes_value(false)
.requires(SIGN_ONLY_ARG.name)
.help(DUMP_TRANSACTION_MESSAGE.help)
}
pub trait ArgsConfig {
fn blockhash_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
fn signer_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
fn dump_transaction_message_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
}
pub trait OfflineArgs {
fn offline_args(self) -> Self;
fn offline_args_config(self, config: &dyn ArgsConfig) -> Self;
}
impl OfflineArgs for App<'_, '_> {
fn offline_args_config(self, config: &dyn ArgsConfig) -> Self {
self.arg(config.blockhash_arg(blockhash_arg()))
.arg(config.sign_only_arg(sign_only_arg()))
.arg(config.signer_arg(signer_arg()))
.arg(config.dump_transaction_message_arg(dump_transaction_message()))
}
fn offline_args(self) -> Self {
struct NullArgsConfig {}
impl ArgsConfig for NullArgsConfig {}
self.offline_args_config(&NullArgsConfig {})
}
}

View File

@@ -3,16 +3,15 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-cli-config" name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale" description = "Blockchain, Rebuilt for Scale"
version = "1.7.1" version = "1.3.2"
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/"
documentation = "https://docs.rs/solana-cli-config"
[dependencies] [dependencies]
dirs-next = "2.0.0" dirs = "2.0.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
serde = "1.0.122" serde = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_yaml = "0.8.13" serde_yaml = "0.8.13"
url = "2.1.1" url = "2.1.1"

View File

@@ -5,7 +5,7 @@ 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,16 +17,15 @@ 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>,
#[serde(default)]
pub commitment: String,
} }
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()
}; };
@@ -42,14 +41,11 @@ impl Default for Config {
"System Program".to_string(), "System Program".to_string(),
); );
let commitment = "confirmed".to_string();
Self { Self {
json_rpc_url, json_rpc_url,
websocket_url, websocket_url,
keypair_path, keypair_path,
address_labels, address_labels,
commitment,
} }
} }
} }
@@ -75,8 +71,7 @@ impl Config {
.set_scheme(if is_secure { "wss" } else { "ws" }) .set_scheme(if is_secure { "wss" } else { "ws" })
.expect("unable to set scheme"); .expect("unable to set scheme");
if let Some(port) = json_rpc_url.port() { if let Some(port) = json_rpc_url.port() {
let port = port.checked_add(1).expect("port out of range"); ws_url.set_port(Some(port + 1)).expect("unable to set port");
ws_url.set_port(Some(port)).expect("unable to set port");
} }
ws_url.to_string() ws_url.to_string()
} }
@@ -107,13 +102,13 @@ mod test {
#[test] #[test]
fn compute_websocket_url() { fn compute_websocket_url() {
assert_eq!( assert_eq!(
Config::compute_websocket_url(&"http://api.devnet.solana.com"), Config::compute_websocket_url(&"http://devnet.solana.com"),
"ws://api.devnet.solana.com/".to_string() "ws://devnet.solana.com/".to_string()
); );
assert_eq!( assert_eq!(
Config::compute_websocket_url(&"https://api.devnet.solana.com"), Config::compute_websocket_url(&"https://devnet.solana.com"),
"wss://api.devnet.solana.com/".to_string() "wss://devnet.solana.com/".to_string()
); );
assert_eq!( assert_eq!(

View File

@@ -1,32 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli-output"
description = "Blockchain, Rebuilt for Scale"
version = "1.7.1"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-cli-output"
[dependencies]
base64 = "0.13.0"
chrono = { version = "0.4.11", features = ["serde"] }
console = "0.14.1"
humantime = "2.0.1"
Inflector = "0.11.4"
indicatif = "0.15.0"
serde = "1.0.122"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "=1.7.1" }
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" }
solana-client = { path = "../client", version = "=1.7.1" }
solana-sdk = { path = "../sdk", version = "=1.7.1" }
solana-stake-program = { path = "../programs/stake", version = "=1.7.1" }
solana-transaction-status = { path = "../transaction-status", version = "=1.7.1" }
solana-vote-program = { path = "../programs/vote", version = "=1.7.1" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,460 +0,0 @@
use {
crate::cli_output::CliSignatureVerificationStatus,
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
console::style,
indicatif::{ProgressBar, ProgressStyle},
solana_sdk::{
clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol,
program_utils::limited_deserialize, pubkey::Pubkey, transaction::Transaction,
},
solana_transaction_status::UiTransactionStatusMeta,
spl_memo::id as spl_memo_id,
spl_memo::v1::id as spl_memo_v1_id,
std::{collections::HashMap, fmt, io},
};
#[derive(Clone, Debug)]
pub struct BuildBalanceMessageConfig {
pub use_lamports_unit: bool,
pub show_unit: bool,
pub trim_trailing_zeros: bool,
}
impl Default for BuildBalanceMessageConfig {
fn default() -> Self {
Self {
use_lamports_unit: false,
show_unit: true,
trim_trailing_zeros: true,
}
}
}
fn is_memo_program(k: &Pubkey) -> bool {
let k_str = k.to_string();
(k_str == spl_memo_v1_id().to_string()) || (k_str == spl_memo_id().to_string())
}
pub fn build_balance_message_with_config(
lamports: u64,
config: &BuildBalanceMessageConfig,
) -> String {
let value = if config.use_lamports_unit {
lamports.to_string()
} else {
let sol = lamports_to_sol(lamports);
let sol_str = format!("{:.9}", sol);
if config.trim_trailing_zeros {
sol_str
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} else {
sol_str
}
};
let unit = if config.show_unit {
if config.use_lamports_unit {
let ess = if lamports == 1 { "" } else { "s" };
format!(" lamport{}", ess)
} else {
" SOL".to_string()
}
} else {
"".to_string()
};
format!("{}{}", value, unit)
}
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
build_balance_message_with_config(
lamports,
&BuildBalanceMessageConfig {
use_lamports_unit,
show_unit,
..BuildBalanceMessageConfig::default()
},
)
}
// Pretty print a "name value"
pub fn println_name_value(name: &str, value: &str) {
let styled_value = if value.is_empty() {
style("(not set)").italic()
} else {
style(value)
};
println!("{} {}", style(name).bold(), styled_value);
}
pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
let styled_value = if value.is_empty() {
style("(not set)").italic()
} else {
style(value)
};
writeln!(f, "{} {}", style(name).bold(), styled_value)
}
pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
let label = address_labels.get(pubkey);
match label {
Some(label) => format!(
"{:.31} ({:.4}..{})",
label,
pubkey,
pubkey.split_at(pubkey.len() - 4).1
),
None => pubkey.to_string(),
}
}
pub fn println_signers(
blockhash: &Hash,
signers: &[String],
absent: &[String],
bad_sig: &[String],
) {
println!();
println!("Blockhash: {}", blockhash);
if !signers.is_empty() {
println!("Signers (Pubkey=Signature):");
signers.iter().for_each(|signer| println!(" {}", signer))
}
if !absent.is_empty() {
println!("Absent Signers (Pubkey):");
absent.iter().for_each(|pubkey| println!(" {}", pubkey))
}
if !bad_sig.is_empty() {
println!("Bad Signatures (Pubkey):");
bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
}
println!();
}
fn format_account_mode(message: &Message, index: usize) -> String {
format!(
"{}r{}{}", // accounts are always readable...
if message.is_signer(index) {
"s" // stands for signer
} else {
"-"
},
if message.is_writable(index, /*demote_sysvar_write_locks=*/ true) {
"w" // comment for consistent rust fmt (no joking; lol)
} else {
"-"
},
// account may be executable on-chain while not being
// designated as a program-id in the message
if message.maybe_executable(index) {
"x"
} else {
// programs to be executed via CPI cannot be identified as
// executable from the message
"-"
},
)
}
pub fn write_transaction<W: io::Write>(
w: &mut W,
transaction: &Transaction,
transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) -> io::Result<()> {
let message = &transaction.message;
if let Some(block_time) = block_time {
writeln!(
w,
"{}Block Time: {:?}",
prefix,
Local.timestamp(block_time, 0)
)?;
}
writeln!(
w,
"{}Recent Blockhash: {:?}",
prefix, message.recent_blockhash
)?;
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
sigverify_status
.iter()
.map(|s| format!(" ({})", s))
.collect()
} else {
vec!["".to_string(); transaction.signatures.len()]
};
for (signature_index, (signature, sigverify_status)) in transaction
.signatures
.iter()
.zip(&sigverify_statuses)
.enumerate()
{
writeln!(
w,
"{}Signature {}: {:?}{}",
prefix, signature_index, signature, sigverify_status,
)?;
}
let mut fee_payer_index = None;
for (account_index, account) in message.account_keys.iter().enumerate() {
if fee_payer_index.is_none() && message.is_non_loader_key(account, account_index) {
fee_payer_index = Some(account_index)
}
writeln!(
w,
"{}Account {}: {} {}{}",
prefix,
account_index,
format_account_mode(message, account_index),
account,
if Some(account_index) == fee_payer_index {
" (fee payer)"
} else {
""
},
)?;
}
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
writeln!(
w,
"{} Program: {} ({})",
prefix, program_pubkey, instruction.program_id_index
)?;
for (account_index, account) in instruction.accounts.iter().enumerate() {
let account_pubkey = message.account_keys[*account as usize];
writeln!(
w,
"{} Account {}: {} ({})",
prefix, account_index, account_pubkey, account
)?;
}
let mut raw = true;
if program_pubkey == solana_vote_program::id() {
if let Ok(vote_instruction) = limited_deserialize::<
solana_vote_program::vote_instruction::VoteInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
raw = false;
}
} else if program_pubkey == solana_stake_program::id() {
if let Ok(stake_instruction) = limited_deserialize::<
solana_stake_program::stake_instruction::StakeInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
raw = false;
}
} else if program_pubkey == solana_sdk::system_program::id() {
if let Ok(system_instruction) = limited_deserialize::<
solana_sdk::system_instruction::SystemInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, system_instruction)?;
raw = false;
}
} else if is_memo_program(&program_pubkey) {
if let Ok(s) = std::str::from_utf8(&instruction.data) {
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
raw = false;
}
}
if raw {
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
}
}
if let Some(transaction_status) = transaction_status {
writeln!(
w,
"{}Status: {}",
prefix,
match &transaction_status.status {
Ok(_) => "Ok".into(),
Err(err) => err.to_string(),
}
)?;
writeln!(
w,
"{} Fee: ◎{}",
prefix,
lamports_to_sol(transaction_status.fee)
)?;
assert_eq!(
transaction_status.pre_balances.len(),
transaction_status.post_balances.len()
);
for (i, (pre, post)) in transaction_status
.pre_balances
.iter()
.zip(transaction_status.post_balances.iter())
.enumerate()
{
if pre == post {
writeln!(
w,
"{} Account {} balance: ◎{}",
prefix,
i,
lamports_to_sol(*pre)
)?;
} else {
writeln!(
w,
"{} Account {} balance: ◎{} -> ◎{}",
prefix,
i,
lamports_to_sol(*pre),
lamports_to_sol(*post)
)?;
}
}
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)?;
}
}
}
if let Some(rewards) = &transaction_status.rewards {
if !rewards.is_empty() {
writeln!(w, "{}Rewards:", prefix,)?;
writeln!(
w,
"{} {:<44} {:^15} {:<15} {:<20}",
prefix, "Address", "Type", "Amount", "New Balance"
)?;
for reward in rewards {
let sign = if reward.lamports < 0 { "-" } else { "" };
writeln!(
w,
"{} {:<44} {:^15} {:<15} {}",
prefix,
reward.pubkey,
if let Some(reward_type) = reward.reward_type {
format!("{}", reward_type)
} else {
"-".to_string()
},
format!(
"{}{:<14.9}",
sign,
lamports_to_sol(reward.lamports.abs() as u64)
),
format!("{:<18.9}", lamports_to_sol(reward.post_balance),)
)?;
}
}
}
} else {
writeln!(w, "{}Status: Unavailable", prefix)?;
}
Ok(())
}
pub fn println_transaction(
transaction: &Transaction,
transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) {
let mut w = Vec::new();
if write_transaction(
&mut w,
transaction,
transaction_status,
prefix,
sigverify_status,
block_time,
)
.is_ok()
{
if let Ok(s) = String::from_utf8(w) {
print!("{}", s);
}
}
}
pub fn writeln_transaction(
f: &mut dyn fmt::Write,
transaction: &Transaction,
transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str,
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
block_time: Option<UnixTimestamp>,
) -> fmt::Result {
let mut w = Vec::new();
if write_transaction(
&mut w,
transaction,
transaction_status,
prefix,
sigverify_status,
block_time,
)
.is_ok()
{
if let Ok(s) = String::from_utf8(w) {
write!(f, "{}", s)?;
}
}
Ok(())
}
/// Creates a new process bar for processing that will take an unknown amount of time
pub fn new_spinner_progress_bar() -> ProgressBar {
let progress_bar = ProgressBar::new(42);
progress_bar
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
progress_bar.enable_steady_tick(100);
progress_bar
}
pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
Some(ndt) => DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
None => format!("UnixTimestamp {}", unix_timestamp),
}
}
#[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)"
);
}
}

View File

@@ -1,16 +0,0 @@
#![allow(clippy::integer_arithmetic)]
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)
}
}

View File

@@ -3,11 +3,10 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018" edition = "2018"
name = "solana-cli" name = "solana-cli"
description = "Blockchain, Rebuilt for Scale" description = "Blockchain, Rebuilt for Scale"
version = "1.7.1" version = "1.3.2"
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/"
documentation = "https://docs.rs/solana-cli"
[dependencies] [dependencies]
bincode = "1.3.1" bincode = "1.3.1"
@@ -16,42 +15,41 @@ 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.5", features = ["termination"] }
console = "0.14.1" console = "0.11.3"
dirs-next = "2.0.0" dirs = "2.0.2"
log = "0.4.11" log = "0.4.8"
Inflector = "0.11.4" Inflector = "0.11.4"
indicatif = "0.15.0" indicatif = "0.15.0"
humantime = "2.0.1" humantime = "2.0.1"
num-traits = "0.2" num-traits = "0.2"
pretty-hex = "0.2.1" pretty-hex = "0.1.1"
reqwest = { version = "0.11.2", default-features = false, features = ["blocking", "rustls-tls", "json"] } reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serde = "1.0.122" serde = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.56" serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "=1.7.1" } solana-account-decoder = { path = "../account-decoder", version = "1.3.2" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.7.1" } solana-budget-program = { path = "../programs/budget", version = "1.3.2" }
solana-clap-utils = { path = "../clap-utils", version = "=1.7.1" } solana-clap-utils = { path = "../clap-utils", version = "1.3.2" }
solana-cli-config = { path = "../cli-config", version = "=1.7.1" } solana-cli-config = { path = "../cli-config", version = "1.3.2" }
solana-cli-output = { path = "../cli-output", version = "=1.7.1" } solana-client = { path = "../client", version = "1.3.2" }
solana-client = { path = "../client", version = "=1.7.1" } solana-config-program = { path = "../programs/config", version = "1.3.2" }
solana-config-program = { path = "../programs/config", version = "=1.7.1" } solana-faucet = { path = "../faucet", version = "1.3.2" }
solana-faucet = { path = "../faucet", version = "=1.7.1" } solana-logger = { path = "../logger", version = "1.3.2" }
solana-logger = { path = "../logger", version = "=1.7.1" } solana-net-utils = { path = "../net-utils", version = "1.3.2" }
solana-net-utils = { path = "../net-utils", version = "=1.7.1" } solana-remote-wallet = { path = "../remote-wallet", version = "1.3.2" }
solana_rbpf = "=0.2.11" solana-runtime = { path = "../runtime", version = "1.3.2" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.1" } solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "=1.7.1" } solana-stake-program = { path = "../programs/stake", version = "1.3.2" }
solana-stake-program = { path = "../programs/stake", version = "=1.7.1" } solana-transaction-status = { path = "../transaction-status", version = "1.3.2" }
solana-transaction-status = { path = "../transaction-status", version = "=1.7.1" } solana-version = { path = "../version", version = "1.3.2" }
solana-version = { path = "../version", version = "=1.7.1" } solana-vote-program = { path = "../programs/vote", version = "1.3.2" }
solana-vote-program = { path = "../programs/vote", version = "=1.7.1" } solana-vote-signer = { path = "../vote-signer", version = "1.3.2" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] } thiserror = "1.0.20"
thiserror = "1.0.21"
tiny-bip39 = "0.7.0"
url = "2.1.1" url = "2.1.1"
[dev-dependencies] [dev-dependencies]
solana-core = { path = "../core", version = "=1.7.1" } solana-core = { path = "../core", version = "1.3.2" }
solana-budget-program = { path = "../programs/budget", version = "1.3.2" }
tempfile = "3.1.0" tempfile = "3.1.0"
[[bin]] [[bin]]

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