Compare commits

..

39 Commits

Author SHA1 Message Date
mergify[bot]
9e42883d4b Fix a bug in input deserialization in the C SDK (#17217) (#17249)
When the input contains more accounts than the user has requested to be deserialized, and one of the excess ones is a dup, the input pointer is not adjusted correctly.

Compare the lines added by this commit to line 401: "input += 7; // padding". Since the input data layout does not depend on the number of accounts the user wants to deserialize, this adjustment by 7 bytes must happen in both branches.

(cherry picked from commit e02b4e1192)

Co-authored-by: Christian Machacek <39452430+machacekch@users.noreply.github.com>
2021-05-15 00:10:02 +00:00
mergify[bot]
e41460d500 feat: update api urls (backport #17186) (#17248)
* feat: update api urls

(cherry picked from commit 0f3045fb68)

* fix: cluster test

(cherry picked from commit ae5a10dffd)

* docs: update old devnet and testnet url references

(cherry picked from commit ec621e71dc)

* fix: update devnet and testnet urls

(cherry picked from commit 7be3171f4a)

Co-authored-by: Josh Hundley <josh.hundley@gmail.com>
2021-05-15 00:08:24 +00:00
steviez
9aacd0f3c3 Zero pad data shreds on fetch from blockstore (#17147)
* Zero pad data shreds on fetch from blockstore

This is a partial backport of #16602 to allow compatibility with that change.

* Remove size check and resize shreds to consistent length
2021-05-14 16:18:00 -05:00
mergify[bot]
3f908306a3 test-validator: Hint at airdrop when wallet is unavailable (#17235)
(cherry picked from commit 2c8dde7224)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-14 18:32:48 +00:00
mergify[bot]
8093586b78 docs: remove missig link (#17212) (#17230)
(cherry picked from commit 5e642a174c)

Co-authored-by: Laptev Stanislav <42931743+dubalda@users.noreply.github.com>
2021-05-14 15:51:22 +00:00
mergify[bot]
a08a6d55fa test-validator: Display genesis hash in dashboard (backport #17216) (#17225)
* rpc: plumb shred_version through RpcContactInfo

(cherry picked from commit 67e6a3106f)

* test-validator: Display more cluster info in dash

(cherry picked from commit 754c708473)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-14 09:56:27 +00:00
mergify[bot]
802c5fcb00 Update clusters.md (#17220) (#17221)
(cherry picked from commit 26afc7620b)

Co-authored-by: joeaba <77398477+joeaba@users.noreply.github.com>
2021-05-14 04:39:04 +00:00
mergify[bot]
8749a97b94 Remove bloat from secondary indexes (#17048) (#17219)
(cherry picked from commit 239ab8799c)

Co-authored-by: carllin <carl@solana.com>
2021-05-14 04:04:55 +00:00
mergify[bot]
4313240b1b Return error for excluded secondary-index keys (backport #17193) (#17215)
* Return error for excluded secondary-index keys (#17193)

* Add runtime helpers to check secondary indexes for key

* Add custom rpc error

* Check secondary-index key inclusion in rpc

* Clone complete AccountSecondaryIndexes into rpc to avoid bank query

(cherry picked from commit 27004f1b76)

# Conflicts:
#	core/src/rpc.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2021-05-13 23:04:01 +00:00
mergify[bot]
24bae00560 docs: Add docs for solana-test-validator (backport #17199) (#17211)
* docs: Add docs for `solana-test-validator`

(cherry picked from commit 768a2ebe9d)

* Update docs/src/developing/test-validator.md

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit 056c1a7b50)

* Update docs/src/developing/test-validator.md

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit 5b13d4057b)

* Update docs/src/developing/test-validator.md

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit 38d7e9a4c4)

* Update docs/src/developing/test-validator.md

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit e08687acfd)

* Update docs/src/developing/test-validator.md

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit 3214105a21)

* Update docs/src/developing/test-validator.md

(cherry picked from commit 7868df3211)

* Update docs/src/developing/test-validator.md

(cherry picked from commit 3e0c0abb53)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-13 18:41:16 +00:00
Trent Nelson
fae0a6307d docs: add hackathon banner 2021-05-13 05:03:15 +00:00
mergify[bot]
9753f1a6ca Add bip32 support to solana-keygen recover (#17180) (#17189)
* Fix spelling

* Add validator for  SignerSources

* Add helper to generate Keypair from supporting SignerSources

* Add bip32 support to solana-keygen recover

* Make SignerSourceKind const strs, use for Debug impl and URI schemes

(cherry picked from commit b437b0a49d)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2021-05-12 20:48:53 +00:00
mergify[bot]
8ad1554bc9 Update devnet and testnet endpoints (#17188) (#17191)
(cherry picked from commit 597373f5fa)

Co-authored-by: joeaba <77398477+joeaba@users.noreply.github.com>
2021-05-12 20:25:05 +00:00
mergify[bot]
2367f561dc include/exclude keys on account secondary index (backport #17110) (#17179)
* include/exclude keys on account secondary index (#17110)

* AccountSecondaryIndexes.include/exclude

* use normal scan if key is not indexed

* add a test to ask for a scan for an excluded secondary index

* fix up cli args

(cherry picked from commit 7d96f78821)

# Conflicts:
#	runtime/src/accounts_db.rs
#	runtime/src/accounts_index.rs

* resolve merge conflicts

Co-authored-by: Jeff Washington (jwash) <75863576+jeffwashington@users.noreply.github.com>
Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
2021-05-12 15:09:46 +00:00
mergify[bot]
21d41b976b Move Signer types out of the signature module (backport #17099) (#17177)
* sdk: Move `Signer` trait to own module

(cherry picked from commit af6f3d776e)

* sdk: Move `Keypair` to `signer` module

(cherry picked from commit 0eba6eb401)

* sdk: Move `Presigner` to `signer` module

(cherry picked from commit 12bf6c06c3)

* sdk: Move `NullSigner` to `signer` module

(cherry picked from commit b71e4bdc61)

* sdk: Move `signers` module into `signer` module

(cherry picked from commit 967840aed6)

* sdk: keypair - drop superfluous iter()

(cherry picked from commit dbac38702a)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-11 20:44:54 +00:00
mergify[bot]
3303ead54d Add Keccak256 syscall and sdk support (backport #16498) (#17157)
* Add Keccak256 syscall and sdk support (#16498)

(cherry picked from commit 8eb05d6ed4)

# Conflicts:
#	Cargo.lock
#	programs/bpf/Cargo.lock
#	programs/bpf/rust/sha/Cargo.toml
#	programs/bpf/tests/programs.rs
#	programs/bpf_loader/Cargo.toml
#	sdk/program/Cargo.toml
#	sdk/program/src/lib.rs
#	sdk/src/feature_set.rs

* resolve conflicts

Co-authored-by: Jack May <jack@solana.com>
2021-05-11 09:31:16 +00:00
mergify[bot]
f91d7da5a4 sdk: Add get_instance_packed_len for variable-size types (#17092) (#17153)
* sdk: Add get_instance_packed_len for variable-size types

* Add comment for get_packed_len

* Add more tests

(cherry picked from commit 4b60b2863e)

Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
2021-05-11 09:16:14 +00:00
mergify[bot]
b2dad84d05 Update web-wallet.md to add phantom with fixed link (#17161) (#17163)
* Update web-wallet.md to add phantom with fixed link

Update web-wallet.md to add phantom with fixed link

* Update web-wallets.md for phantom

removing trailing whitespaces

* Update docs/src/wallet-guide/web-wallets.md

Co-authored-by: Michael Vines <mvines@gmail.com>
(cherry picked from commit 4625231e30)

Co-authored-by: chaseeb <chaseeb@gmail.com>
2021-05-11 04:46:41 +00:00
mergify[bot]
a7b2939bc8 SignerSource: rename input scheme to prompt, default to bip44 solana base key (#17154) (#17159)
* Rename ask to prompt

* Default to Solana bip44 base if no derivation-path

* Add SignerSource legacy field, support legacy ASK

* Update docs

* Fix docs: validator current doesn't support uri SignerSources

(cherry picked from commit a5ec3a0547)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2021-05-11 02:43:49 +00:00
mergify[bot]
ea3b783b63 fix c program deploy help (#17152) (#17156)
(cherry picked from commit 82fb6712e7)

Co-authored-by: Jack May <jack@solana.com>
2021-05-10 23:36:16 +00:00
mergify[bot]
733ef4b0b8 type AccountSecondaryIndexes = HashSet (backport #17108) (#17149)
* type AccountSecondaryIndexes = HashSet (#17108)

(cherry picked from commit f39dda00e0)

# Conflicts:
#	runtime/benches/accounts.rs
#	runtime/src/accounts.rs
#	runtime/src/accounts_db.rs
#	runtime/src/accounts_index.rs

* resolve merge errors

Co-authored-by: Jeff Washington (jwash) <75863576+jeffwashington@users.noreply.github.com>
Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
2021-05-10 20:55:33 +00:00
mergify[bot]
0cf83887c6 Move block-time caching earlier (#17109) (#17150)
* Require that blockstore block-time only be recognized slot, instead of root

* Move cache_block_time to after Bank freeze

* Single use statement

* Pass transaction_status_sender by reference

* Remove unnecessary slot-existence check before caching block time altogether

* Move block-time existence check into Blockstore::cache_block_time, Blockstore no longer needed in blockstore_processor helper

(cherry picked from commit 6e9deaf1bd)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2021-05-10 20:31:56 +00:00
mergify[bot]
094271be7d indexes crds values by their insert order (backport #16809) (#17132)
* indexes crds values by their insert order

(cherry picked from commit dfa3e7a61c)

* reads gossip push messages off crds ordinal index

Having an ordinal index on crds values based on insert order allows to
efficiently filter values using a cursor. In particular
CrdsGossipPush::push_messages hash-map can be replaced with a cursor,
saving on the bookkeepings, purging, etc

(cherry picked from commit 22c02b917e)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2021-05-10 00:00:00 +00:00
mergify[bot]
efc3c0d65f Add a make target to run the readelf utility on a compiled program (#17131)
The readelf utility (already shipped with the solana tools) shows meta-information about ELF files, such as symbol tables. It is useful for investigating "unresolved symbol" errors that crop up at runtime.

This commit also fixes the objdump flags (two dashes are required and there is no "color" option) as well as a few typos.

(cherry picked from commit ff95e2aaa6)

Co-authored-by: Christian Machacek <39452430+machacekch@users.noreply.github.com>
2021-05-09 02:46:49 +00:00
mergify[bot]
0300eea0d6 Fix syscalls in the C SDK failing at runtime when compiled as C++ (#17124) (#17126)
Some syscalls are wrongly declared "static" in solana_sdk.h, which makes clang++ assume they are local to the compilation unit. It therefore ignores the extern "C" {} block and mangles their names. While that doesn't break C++ compilation, the syscall fails at runtime with something along the lines of "ELF error: Unresolved symbol (_ZL26sol_create_program_addressPK13SolSignerSeediPK9SolPubkeyS4_)".

(cherry picked from commit 6927d0c77e)

Co-authored-by: Christian Machacek <39452430+machacekch@users.noreply.github.com>
2021-05-08 17:27:56 +00:00
mergify[bot]
b03186e3c6 Add chinese translations to docs (#17125) (#17127)
* import zh translations

* Fix broken links

* fix whitespace

(cherry picked from commit a1df57a4ea)

Co-authored-by: Justin Starry <justin@solana.com>
2021-05-08 17:09:51 +00:00
Michael Vines
65e1b881f9 Bump version to v1.6.9 2021-05-08 06:28:08 +00:00
mergify[bot]
28b9e5b572 getBlockProduction now correctly reports block production (#17116)
(cherry picked from commit d6c076f1b6)

Co-authored-by: Michael Vines <mvines@gmail.com>
2021-05-08 04:19:39 +00:00
mergify[bot]
072e884c24 solana-validator exit now uses process::exit() to ensure prompt termination (#17107)
(cherry picked from commit ec2b06d81d)

Co-authored-by: Michael Vines <mvines@gmail.com>
2021-05-07 18:49:36 +00:00
mergify[bot]
dc95663de7 Add ledger-tool for restoring roots to the Roots CF (#17045) (#17091)
* Add ledger-tool for restoring roots to the Roots CF

* Print successful repair data, and repair in chunks

* Add parameter to limit num slots checked for root repair

(cherry picked from commit ddfbae260f)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2021-05-06 21:28:28 +00:00
mergify[bot]
a73303be22 Fixing a broken link in the docs (#16975) (#17085)
(cherry picked from commit 40c31f87e0)

Co-authored-by: Jordan Sexton <jordan@jordansexton.com>
2021-05-06 16:05:37 +00:00
mergify[bot]
330f42c375 implements cursor for gossip crds table queries (#16952) (#17084)
VersionedCrdsValue.insert_timestamp is used for fetching crds values
inserted since last query:
https://github.com/solana-labs/solana/blob/ec37a843a/core/src/cluster_info.rs#L1197-L1215
https://github.com/solana-labs/solana/blob/ec37a843a/core/src/cluster_info.rs#L1274-L1298

So it is crucial that insert_timestamp does not go backward in time when
new values are inserted into the table. However std::time::SystemTime is
not monotonic, or due to workload, lock contention, thread scheduling,
etc, ... new values may be inserted with a stalled timestamp way in the
past. Additionally, reading system time for the above purpose is
inefficient/unnecessary.

This commit adds an ordinal index to crds values indicating their insert
order. Additionally, it implements a new Cursor type for fetching values
inserted since last query.

(cherry picked from commit fa86a335b0)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2021-05-06 15:23:01 +00:00
mergify[bot]
5d088c7d06 Dump rent_collector/inflation with ledger-tool cap (#17069) (#17081)
(cherry picked from commit d19526e6c2)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2021-05-06 11:48:21 +00:00
mergify[bot]
5c9495f955 CLI: Print gossip nodes with cli-output crate (#17072)
(cherry picked from commit cb5e000615)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-06 09:07:36 +00:00
Michael Vines
fb163187b5 RpcClient now respects the retry-after server response header when getting rate limited
(cherry picked from commit 7d1637d89a)
2021-05-05 19:34:18 -07:00
mergify[bot]
970bba495f Add --tower argument to specify where tower files are persisted (#17060)
(cherry picked from commit 9ba2c53b85)

Co-authored-by: Michael Vines <mvines@gmail.com>
2021-05-05 20:37:36 +00:00
Stephen Akridge
9761af201b Don't recognize temp snapshots as possible snapshots to open
(cherry picked from commit 3e0fed48e7)
2021-05-05 09:32:54 -07:00
mergify[bot]
7600be946a SDK: Factor out pubkey on-curve test to a helper (#16935)
(cherry picked from commit cfc1cb1aee)

Co-authored-by: Trent Nelson <trent@solana.com>
2021-05-05 06:19:19 +00:00
Michael Vines
524b380a71 Bump version to 1.6.8 2021-05-04 12:46:57 -07:00
374 changed files with 17853 additions and 2693 deletions

286
Cargo.lock generated
View File

@@ -3934,7 +3934,7 @@ dependencies = [
[[package]]
name = "solana-account-decoder"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"Inflector",
"base64 0.12.3",
@@ -3956,14 +3956,14 @@ dependencies = [
[[package]]
name = "solana-accounts-bench"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"crossbeam-channel 0.4.4",
"log 0.4.11",
"rand 0.7.3",
"rayon",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-runtime",
"solana-sdk",
@@ -3972,7 +3972,7 @@ dependencies = [
[[package]]
name = "solana-accounts-cluster-bench"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"log 0.4.11",
@@ -3984,7 +3984,7 @@ dependencies = [
"solana-core",
"solana-faucet",
"solana-local-cluster",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-net-utils",
"solana-runtime",
@@ -3996,7 +3996,7 @@ dependencies = [
[[package]]
name = "solana-banking-bench"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"crossbeam-channel 0.4.4",
@@ -4006,7 +4006,7 @@ dependencies = [
"solana-clap-utils",
"solana-core",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-perf",
"solana-runtime",
@@ -4017,7 +4017,7 @@ dependencies = [
[[package]]
name = "solana-banks-client"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"borsh",
@@ -4026,7 +4026,7 @@ dependencies = [
"mio 0.7.6",
"solana-banks-interface",
"solana-banks-server",
"solana-program 1.6.7",
"solana-program 1.6.9",
"solana-runtime",
"solana-sdk",
"tarpc",
@@ -4036,7 +4036,7 @@ dependencies = [
[[package]]
name = "solana-banks-interface"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"mio 0.7.6",
"serde",
@@ -4047,7 +4047,7 @@ dependencies = [
[[package]]
name = "solana-banks-server"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"futures 0.3.8",
@@ -4065,7 +4065,7 @@ dependencies = [
[[package]]
name = "solana-bench-exchange"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"itertools",
@@ -4083,7 +4083,7 @@ dependencies = [
"solana-faucet",
"solana-genesis",
"solana-local-cluster",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-net-utils",
"solana-runtime",
@@ -4093,11 +4093,11 @@ dependencies = [
[[package]]
name = "solana-bench-streamer"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"solana-clap-utils",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-net-utils",
"solana-streamer",
"solana-version",
@@ -4105,7 +4105,7 @@ dependencies = [
[[package]]
name = "solana-bench-tps"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"clap",
@@ -4120,7 +4120,7 @@ dependencies = [
"solana-faucet",
"solana-genesis",
"solana-local-cluster",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-metrics",
"solana-net-utils",
@@ -4131,7 +4131,7 @@ dependencies = [
[[package]]
name = "solana-bpf-loader-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"byteorder",
@@ -4141,6 +4141,7 @@ dependencies = [
"rand 0.7.3",
"rand_core 0.6.2",
"rustversion",
"sha3",
"solana-measure",
"solana-runtime",
"solana-sdk",
@@ -4150,7 +4151,7 @@ dependencies = [
[[package]]
name = "solana-budget-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"chrono",
@@ -4166,7 +4167,7 @@ dependencies = [
[[package]]
name = "solana-cargo-build-bpf"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bzip2",
"cargo_metadata",
@@ -4178,7 +4179,7 @@ dependencies = [
[[package]]
name = "solana-cargo-test-bpf"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"cargo_metadata",
"clap",
@@ -4186,7 +4187,7 @@ dependencies = [
[[package]]
name = "solana-clap-utils"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"chrono",
"clap",
@@ -4202,7 +4203,7 @@ dependencies = [
[[package]]
name = "solana-cli"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"Inflector",
"bincode",
@@ -4231,7 +4232,7 @@ dependencies = [
"solana-config-program",
"solana-core",
"solana-faucet",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-net-utils",
"solana-remote-wallet",
"solana-sdk",
@@ -4249,7 +4250,7 @@ dependencies = [
[[package]]
name = "solana-cli-config"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"dirs-next",
"lazy_static",
@@ -4261,7 +4262,7 @@ dependencies = [
[[package]]
name = "solana-cli-output"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"Inflector",
"base64 0.13.0",
@@ -4284,7 +4285,7 @@ dependencies = [
[[package]]
name = "solana-client"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"base64 0.13.0",
@@ -4305,7 +4306,7 @@ dependencies = [
"solana-account-decoder",
"solana-clap-utils",
"solana-faucet",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-net-utils",
"solana-sdk",
"solana-transaction-status",
@@ -4319,7 +4320,7 @@ dependencies = [
[[package]]
name = "solana-config-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"chrono",
@@ -4327,13 +4328,13 @@ dependencies = [
"rand_core 0.6.2",
"serde",
"serde_derive",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-sdk",
]
[[package]]
name = "solana-core"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"ahash 0.6.1",
"base64 0.12.3",
@@ -4383,10 +4384,10 @@ dependencies = [
"solana-clap-utils",
"solana-client",
"solana-faucet",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-merkle-tree",
"solana-metrics",
@@ -4416,7 +4417,7 @@ dependencies = [
[[package]]
name = "solana-crate-features"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"backtrace",
"bytes 0.4.12",
@@ -4438,7 +4439,7 @@ dependencies = [
[[package]]
name = "solana-dos"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"clap",
@@ -4449,7 +4450,7 @@ dependencies = [
"solana-client",
"solana-core",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-net-utils",
"solana-runtime",
"solana-sdk",
@@ -4458,7 +4459,7 @@ dependencies = [
[[package]]
name = "solana-download-utils"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bzip2",
"console",
@@ -4472,7 +4473,7 @@ dependencies = [
[[package]]
name = "solana-exchange-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"log 0.4.11",
@@ -4480,7 +4481,7 @@ dependencies = [
"num-traits",
"serde",
"serde_derive",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-runtime",
"solana-sdk",
@@ -4489,7 +4490,7 @@ dependencies = [
[[package]]
name = "solana-failure-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"solana-runtime",
"solana-sdk",
@@ -4497,7 +4498,7 @@ dependencies = [
[[package]]
name = "solana-faucet"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"byteorder",
@@ -4507,7 +4508,7 @@ dependencies = [
"serde_derive",
"solana-clap-utils",
"solana-cli-config",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-sdk",
"solana-version",
@@ -4538,7 +4539,7 @@ dependencies = [
[[package]]
name = "solana-frozen-abi"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bs58",
"bv",
@@ -4549,8 +4550,8 @@ dependencies = [
"serde",
"serde_derive",
"sha2 0.9.2",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"thiserror",
]
@@ -4569,7 +4570,7 @@ dependencies = [
[[package]]
name = "solana-frozen-abi-macro"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"lazy_static",
"proc-macro2 1.0.24",
@@ -4580,7 +4581,7 @@ dependencies = [
[[package]]
name = "solana-genesis"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"base64 0.12.3",
"chrono",
@@ -4593,7 +4594,7 @@ dependencies = [
"solana-cli-config",
"solana-exchange-program",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-runtime",
"solana-sdk",
"solana-stake-program",
@@ -4605,12 +4606,12 @@ dependencies = [
[[package]]
name = "solana-gossip"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"solana-clap-utils",
"solana-core",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-net-utils",
"solana-sdk",
"solana-version",
@@ -4618,7 +4619,7 @@ dependencies = [
[[package]]
name = "solana-install"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"atty",
"bincode",
@@ -4639,7 +4640,7 @@ dependencies = [
"solana-clap-utils",
"solana-client",
"solana-config-program",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-sdk",
"solana-version",
"tar",
@@ -4651,7 +4652,7 @@ dependencies = [
[[package]]
name = "solana-keygen"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bs58",
"clap",
@@ -4667,7 +4668,7 @@ dependencies = [
[[package]]
name = "solana-ledger"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"bincode",
@@ -4700,9 +4701,9 @@ dependencies = [
"solana-account-decoder",
"solana-bpf-loader-program",
"solana-budget-program",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-measure",
"solana-merkle-tree",
"solana-metrics",
@@ -4724,7 +4725,7 @@ dependencies = [
[[package]]
name = "solana-ledger-tool"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_cmd",
"bs58",
@@ -4744,7 +4745,7 @@ dependencies = [
"solana-clap-utils",
"solana-cli-output",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-runtime",
"solana-sdk",
@@ -4759,7 +4760,7 @@ dependencies = [
[[package]]
name = "solana-local-cluster"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"crossbeam-channel 0.4.4",
@@ -4777,7 +4778,7 @@ dependencies = [
"solana-exchange-program",
"solana-faucet",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-rayon-threadlimit",
"solana-runtime",
"solana-sdk",
@@ -4789,14 +4790,14 @@ dependencies = [
[[package]]
name = "solana-log-analyzer"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"byte-unit",
"clap",
"serde",
"serde_json",
"solana-clap-utils",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-version",
]
@@ -4813,7 +4814,7 @@ dependencies = [
[[package]]
name = "solana-logger"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"env_logger 0.8.3",
"lazy_static",
@@ -4822,7 +4823,7 @@ dependencies = [
[[package]]
name = "solana-measure"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"jemalloc-ctl",
"jemallocator",
@@ -4833,11 +4834,11 @@ dependencies = [
[[package]]
name = "solana-merkle-root-bench"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"log 0.4.11",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-runtime",
"solana-sdk",
@@ -4846,17 +4847,17 @@ dependencies = [
[[package]]
name = "solana-merkle-tree"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"fast-math",
"hex",
"matches",
"solana-program 1.6.7",
"solana-program 1.6.9",
]
[[package]]
name = "solana-metrics"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"env_logger 0.8.3",
"gethostname",
@@ -4870,19 +4871,19 @@ dependencies = [
[[package]]
name = "solana-net-shaper"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"rand 0.7.3",
"serde",
"serde_json",
"solana-clap-utils",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
]
[[package]]
name = "solana-net-utils"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"clap",
@@ -4893,7 +4894,7 @@ dependencies = [
"serde_derive",
"socket2",
"solana-clap-utils",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-version",
"tokio 1.1.1",
"url 2.2.0",
@@ -4901,16 +4902,16 @@ dependencies = [
[[package]]
name = "solana-noop-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"log 0.4.11",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-sdk",
]
[[package]]
name = "solana-notifier"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"log 0.4.11",
"reqwest",
@@ -4919,7 +4920,7 @@ dependencies = [
[[package]]
name = "solana-ownable"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"num-derive",
@@ -4931,7 +4932,7 @@ dependencies = [
[[package]]
name = "solana-perf"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"curve25519-dalek 2.1.0",
@@ -4944,7 +4945,7 @@ dependencies = [
"rayon",
"serde",
"solana-budget-program",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-metrics",
"solana-rayon-threadlimit",
@@ -4953,7 +4954,7 @@ dependencies = [
[[package]]
name = "solana-poh-bench"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"log 0.4.11",
@@ -4961,7 +4962,7 @@ dependencies = [
"rayon",
"solana-clap-utils",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-perf",
"solana-sdk",
@@ -5002,7 +5003,7 @@ dependencies = [
[[package]]
name = "solana-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"bincode",
@@ -5026,16 +5027,17 @@ dependencies = [
"serde_derive",
"serde_json",
"sha2 0.9.2",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-sdk-macro 1.6.7",
"sha3",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-sdk-macro 1.6.9",
"thiserror",
]
[[package]]
name = "solana-program-test"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"async-trait",
@@ -5050,7 +5052,7 @@ dependencies = [
"solana-banks-client",
"solana-banks-server",
"solana-bpf-loader-program",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-runtime",
"solana-sdk",
"solana-stake-program",
@@ -5061,7 +5063,7 @@ dependencies = [
[[package]]
name = "solana-ramp-tps"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bzip2",
"clap",
@@ -5072,7 +5074,7 @@ dependencies = [
"serde_yaml",
"solana-client",
"solana-core",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-net-utils",
"solana-notifier",
@@ -5083,7 +5085,7 @@ dependencies = [
[[package]]
name = "solana-rayon-threadlimit"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"lazy_static",
"num_cpus",
@@ -5091,7 +5093,7 @@ dependencies = [
[[package]]
name = "solana-remote-wallet"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"base32",
"console",
@@ -5110,7 +5112,7 @@ dependencies = [
[[package]]
name = "solana-runtime"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"arrayref",
"assert_matches",
@@ -5141,9 +5143,9 @@ dependencies = [
"serde",
"serde_derive",
"solana-config-program",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-measure",
"solana-metrics",
"solana-noop-program",
@@ -5161,7 +5163,7 @@ dependencies = [
[[package]]
name = "solana-scripts"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"csv",
"serde",
@@ -5169,7 +5171,7 @@ dependencies = [
[[package]]
name = "solana-sdk"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"assert_matches",
"bincode",
@@ -5206,11 +5208,11 @@ dependencies = [
"sha2 0.9.2",
"sha3",
"solana-crate-features",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-program 1.6.7",
"solana-sdk-macro 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-program 1.6.9",
"solana-sdk-macro 1.6.9",
"thiserror",
"tiny-bip39 0.7.3",
"uriparse",
@@ -5231,7 +5233,7 @@ dependencies = [
[[package]]
name = "solana-sdk-macro"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bs58",
"proc-macro2 1.0.24",
@@ -5242,20 +5244,20 @@ dependencies = [
[[package]]
name = "solana-secp256k1-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"digest 0.9.0",
"libsecp256k1",
"rand 0.7.3",
"sha3",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-sdk",
]
[[package]]
name = "solana-stake-accounts"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"solana-clap-utils",
@@ -5269,7 +5271,7 @@ dependencies = [
[[package]]
name = "solana-stake-monitor"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"console",
@@ -5282,7 +5284,7 @@ dependencies = [
"solana-client",
"solana-core",
"solana-local-cluster",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-sdk",
"solana-stake-program",
@@ -5293,7 +5295,7 @@ dependencies = [
[[package]]
name = "solana-stake-o-matic"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"log 0.4.11",
@@ -5306,7 +5308,7 @@ dependencies = [
"solana-cli-config",
"solana-cli-output",
"solana-client",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-notifier",
"solana-sdk",
@@ -5317,7 +5319,7 @@ dependencies = [
[[package]]
name = "solana-stake-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"log 0.4.11",
@@ -5327,9 +5329,9 @@ dependencies = [
"serde",
"serde_derive",
"solana-config-program",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-metrics",
"solana-sdk",
"solana-vote-program",
@@ -5338,7 +5340,7 @@ dependencies = [
[[package]]
name = "solana-storage-bigtable"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"arc-swap 0.4.8",
"backoff",
@@ -5365,7 +5367,7 @@ dependencies = [
[[package]]
name = "solana-storage-proto"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"bs58",
@@ -5379,11 +5381,11 @@ dependencies = [
[[package]]
name = "solana-store-tool"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"log 0.4.11",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-runtime",
"solana-sdk",
@@ -5392,12 +5394,12 @@ dependencies = [
[[package]]
name = "solana-streamer"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"libc",
"log 0.4.11",
"nix 0.19.0",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-measure",
"solana-metrics",
"solana-perf",
@@ -5407,14 +5409,14 @@ dependencies = [
[[package]]
name = "solana-sys-tuner"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"libc",
"log 0.4.11",
"nix 0.19.0",
"solana-clap-utils",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-version",
"sysctl",
"unix_socket2",
@@ -5423,7 +5425,7 @@ dependencies = [
[[package]]
name = "solana-tokens"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"chrono",
@@ -5441,7 +5443,7 @@ dependencies = [
"solana-cli-config",
"solana-client",
"solana-core",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-program-test",
"solana-remote-wallet",
"solana-runtime",
@@ -5457,7 +5459,7 @@ dependencies = [
[[package]]
name = "solana-transaction-status"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"Inflector",
"base64 0.12.3",
@@ -5480,7 +5482,7 @@ dependencies = [
[[package]]
name = "solana-upload-perf"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"serde_json",
"solana-metrics",
@@ -5488,7 +5490,7 @@ dependencies = [
[[package]]
name = "solana-validator"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"base64 0.12.3",
"bincode",
@@ -5516,7 +5518,7 @@ dependencies = [
"solana-download-utils",
"solana-faucet",
"solana-ledger",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-net-utils",
"solana-perf",
@@ -5529,21 +5531,21 @@ dependencies = [
[[package]]
name = "solana-version"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"log 0.4.11",
"rustc_version",
"serde",
"serde_derive",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-sdk",
]
[[package]]
name = "solana-vest-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"chrono",
@@ -5559,7 +5561,7 @@ dependencies = [
[[package]]
name = "solana-vote-program"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"bincode",
"log 0.4.11",
@@ -5568,9 +5570,9 @@ dependencies = [
"rustc_version",
"serde",
"serde_derive",
"solana-frozen-abi 1.6.7",
"solana-frozen-abi-macro 1.6.7",
"solana-logger 1.6.7",
"solana-frozen-abi 1.6.9",
"solana-frozen-abi-macro 1.6.9",
"solana-logger 1.6.9",
"solana-metrics",
"solana-sdk",
"thiserror",
@@ -5578,7 +5580,7 @@ dependencies = [
[[package]]
name = "solana-watchtower"
version = "1.6.7"
version = "1.6.9"
dependencies = [
"clap",
"humantime 2.0.1",
@@ -5587,7 +5589,7 @@ dependencies = [
"solana-cli-config",
"solana-cli-output",
"solana-client",
"solana-logger 1.6.7",
"solana-logger 1.6.9",
"solana-metrics",
"solana-notifier",
"solana-sdk",

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-account-decoder"
version = "1.6.7"
version = "1.6.9"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -19,10 +19,10 @@ lazy_static = "1.4.0"
serde = "1.0.122"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-config-program = { path = "../programs/config", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.7" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.7" }
solana-config-program = { path = "../programs/config", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.9" }
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
thiserror = "1.0"
zstd = "0.5.1"

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-accounts-bench"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -11,11 +11,11 @@ publish = false
[dependencies]
log = "0.4.11"
rayon = "1.5.0"
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-measure = { path = "../measure", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-measure = { path = "../measure", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
rand = "0.7.0"
clap = "2.33.1"
crossbeam-channel = "0.4"

View File

@@ -6,10 +6,10 @@ use rayon::prelude::*;
use solana_measure::measure::Measure;
use solana_runtime::{
accounts::{create_test_accounts, update_accounts_bench, Accounts},
accounts_index::Ancestors,
accounts_index::{AccountSecondaryIndexes, Ancestors},
};
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
use std::{collections::HashSet, env, fs, path::PathBuf};
use std::{env, fs, path::PathBuf};
fn main() {
solana_logger::setup();
@@ -58,8 +58,12 @@ fn main() {
if fs::remove_dir_all(path.clone()).is_err() {
println!("Warning: Couldn't remove {:?}", path);
}
let accounts =
Accounts::new_with_config(vec![path], &ClusterType::Testnet, HashSet::new(), false);
let accounts = Accounts::new_with_config(
vec![path],
&ClusterType::Testnet,
AccountSecondaryIndexes::default(),
false,
);
println!("Creating {} accounts", num_accounts);
let mut create_time = Measure::start("create accounts");
let pubkeys: Vec<_> = (0..num_slots)

View File

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

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-banking-bench"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -14,16 +14,16 @@ crossbeam-channel = "0.4"
log = "0.4.11"
rand = "0.7.0"
rayon = "1.5.0"
solana-core = { path = "../core", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-streamer = { path = "../streamer", version = "=1.6.7" }
solana-perf = { path = "../perf", version = "=1.6.7" }
solana-ledger = { path = "../ledger", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-measure = { path = "../measure", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-core = { path = "../core", version = "=1.6.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-streamer = { path = "../streamer", version = "=1.6.9" }
solana-perf = { path = "../perf", version = "=1.6.9" }
solana-ledger = { path = "../ledger", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-measure = { path = "../measure", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-client"
version = "1.6.7"
version = "1.6.9"
description = "Solana banks client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -15,16 +15,16 @@ borsh = "0.8.1"
borsh-derive = "0.8.1"
futures = "0.3"
mio = "0.7.6"
solana-banks-interface = { path = "../banks-interface", version = "=1.6.7" }
solana-program = { path = "../sdk/program", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-banks-interface = { path = "../banks-interface", version = "=1.6.9" }
solana-program = { path = "../sdk/program", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
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.6.7" }
solana-banks-server = { path = "../banks-server", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-banks-server = { path = "../banks-server", version = "=1.6.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-interface"
version = "1.6.7"
version = "1.6.9"
description = "Solana banks RPC interface"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -12,7 +12,7 @@ edition = "2018"
[dependencies]
mio = "0.7.6"
serde = { version = "1.0.122", features = ["derive"] }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
tarpc = { version = "0.24.1", features = ["full"] }
[dev-dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-server"
version = "1.6.7"
version = "1.6.9"
description = "Solana banks server"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -14,10 +14,10 @@ bincode = "1.3.1"
futures = "0.3"
log = "0.4.11"
mio = "0.7.6"
solana-banks-interface = { path = "../banks-interface", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-metrics = { path = "../metrics", version = "=1.6.7" }
solana-banks-interface = { path = "../banks-interface", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-metrics = { path = "../metrics", version = "=1.6.9" }
tarpc = { version = "0.24.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
tokio-serde = { version = "0.8", features = ["bincode"] }

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-exchange"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -18,21 +18,21 @@ rand = "0.7.0"
rayon = "1.5.0"
serde_json = "1.0.56"
serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-core = { path = "../core", version = "=1.6.7" }
solana-genesis = { path = "../genesis", version = "=1.6.7" }
solana-client = { path = "../client", version = "=1.6.7" }
solana-faucet = { path = "../faucet", version = "=1.6.7" }
solana-exchange-program = { path = "../programs/exchange", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-metrics = { path = "../metrics", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-core = { path = "../core", version = "=1.6.9" }
solana-genesis = { path = "../genesis", version = "=1.6.9" }
solana-client = { path = "../client", version = "=1.6.9" }
solana-faucet = { path = "../faucet", version = "=1.6.9" }
solana-exchange-program = { path = "../programs/exchange", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-metrics = { path = "../metrics", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "=1.6.7" }
solana-local-cluster = { path = "../local-cluster", version = "=1.6.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-streamer"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -10,11 +10,11 @@ publish = false
[dependencies]
clap = "2.33.1"
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-streamer = { path = "../streamer", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-streamer = { path = "../streamer", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-tps"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -15,22 +15,22 @@ log = "0.4.11"
rayon = "1.5.0"
serde_json = "1.0.56"
serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-core = { path = "../core", version = "=1.6.7" }
solana-genesis = { path = "../genesis", version = "=1.6.7" }
solana-client = { path = "../client", version = "=1.6.7" }
solana-faucet = { path = "../faucet", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-metrics = { path = "../metrics", version = "=1.6.7" }
solana-measure = { path = "../measure", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-core = { path = "../core", version = "=1.6.9" }
solana-genesis = { path = "../genesis", version = "=1.6.9" }
solana-client = { path = "../client", version = "=1.6.9" }
solana-faucet = { path = "../faucet", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-metrics = { path = "../metrics", version = "=1.6.9" }
solana-measure = { path = "../measure", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
[dev-dependencies]
serial_test = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "=1.6.7" }
solana-local-cluster = { path = "../local-cluster", version = "=1.6.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-clap-utils"
version = "1.6.7"
version = "1.6.9"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -12,8 +12,8 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
rpassword = "4.0"
solana-remote-wallet = { path = "../remote-wallet", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
thiserror = "1.0.21"
tiny-bip39 = "0.8.0"
uriparse = "0.6.3"

View File

@@ -57,7 +57,7 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
if let Some(value) = matches.value_of(name) {
if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(name, skip_validation, true, None).ok()
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
} else {
read_keypair_file(value).ok()
}
@@ -72,7 +72,7 @@ pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>>
.filter_map(|value| {
if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(name, skip_validation, true, None).ok()
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
} else {
read_keypair_file(value).ok()
}

View File

@@ -96,6 +96,26 @@ where
.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
pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
where
@@ -195,8 +215,8 @@ where
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://testnet.solana.com",
"d" | "devnet" => "https://devnet.solana.com",
"t" | "testnet" => "https://api.testnet.solana.com",
"d" | "devnet" => "https://api.devnet.solana.com",
"l" | "localhost" => "http://localhost:8899",
url => url,
}

View File

@@ -18,7 +18,8 @@ use {
message::Message,
pubkey::Pubkey,
signature::{
generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed_and_derivation_path,
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,
},
},
@@ -140,6 +141,7 @@ impl DefaultSigner {
pub(crate) struct SignerSource {
pub kind: SignerSourceKind,
pub derivation_path: Option<DerivationPath>,
pub legacy: bool,
}
impl SignerSource {
@@ -147,18 +149,52 @@ impl SignerSource {
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 {
Ask,
Prompt,
Filepath(String),
Usb(RemoteWalletLocator),
Stdin,
Pubkey(Pubkey),
}
impl AsRef<str> for SignerSourceKind {
fn as_ref(&self) -> &str {
match self {
Self::Prompt => SIGNER_SOURCE_PROMPT,
Self::Filepath(_) => SIGNER_SOURCE_FILEPATH,
Self::Usb(_) => SIGNER_SOURCE_USB,
Self::Stdin => SIGNER_SOURCE_STDIN,
Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY,
}
}
}
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")]
@@ -181,24 +217,26 @@ pub(crate) fn parse_signer_source<S: AsRef<str>>(
if let Some(scheme) = uri.scheme() {
let scheme = scheme.as_str().to_ascii_lowercase();
match scheme.as_str() {
"ask" => Ok(SignerSource {
kind: SignerSourceKind::Ask,
SIGNER_SOURCE_PROMPT => Ok(SignerSource {
kind: SignerSourceKind::Prompt,
derivation_path: DerivationPath::from_uri_any_query(&uri)?,
legacy: false,
}),
"file" => Ok(SignerSource::new(SignerSourceKind::Filepath(
SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(SignerSourceKind::Filepath(
uri.path().to_string(),
))),
"stdin" => Ok(SignerSource::new(SignerSourceKind::Stdin)),
"usb" => Ok(SignerSource {
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)),
_ => Err(SignerSourceError::UnrecognizedSource),
}
} else {
match source {
"-" => Ok(SignerSource::new(SignerSourceKind::Stdin)),
ASK_KEYWORD => Ok(SignerSource::new(SignerSourceKind::Ask)),
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
_ => match Pubkey::from_str(source) {
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
Err(_) => std::fs::metadata(source)
@@ -259,15 +297,17 @@ pub fn signer_from_path_with_config(
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
match kind {
SignerSourceKind::Ask => {
SignerSourceKind::Prompt => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
Ok(Box::new(keypair_from_seed_phrase(
keypair_name,
skip_validation,
false,
derivation_path,
legacy,
)?))
}
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
@@ -339,18 +379,30 @@ pub fn resolve_signer_from_path(
let SignerSource {
kind,
derivation_path,
legacy,
} = parse_signer_source(path)?;
match kind {
SignerSourceKind::Ask => {
SignerSourceKind::Prompt => {
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
// on disk or to a device
keypair_from_seed_phrase(keypair_name, skip_validation, false, derivation_path).map(|_| None)
keypair_from_seed_phrase(
keypair_name,
skip_validation,
false,
derivation_path,
legacy,
)
.map(|_| None)
}
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),
format!(
"could not read keypair file \"{}\". \
Run \"solana-keygen new\" to create a keypair file: {}",
path, e
),
)
.into()),
Ok(_) => Ok(Some(path.to_string())),
@@ -383,7 +435,7 @@ pub fn resolve_signer_from_path(
}
}
// Keyword used to indicate that the user should be asked for a keypair seed phrase
// Keyword used to indicate that the user should be prompted for a keypair seed phrase
pub const ASK_KEYWORD: &str = "ASK";
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
@@ -404,6 +456,56 @@ pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>>
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
/// Optionally skips validation of seed phrase
/// Optionally confirms recovered public key
@@ -412,6 +514,7 @@ pub fn keypair_from_seed_phrase(
skip_validation: bool,
confirm_pubkey: bool,
derivation_path: Option<DerivationPath>,
legacy: bool,
) -> Result<Keypair, Box<dyn error::Error>> {
let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?;
let seed_phrase = seed_phrase.trim();
@@ -422,8 +525,12 @@ pub fn keypair_from_seed_phrase(
let keypair = if skip_validation {
let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase);
keypair_from_seed_and_derivation_path(&seed, derivation_path)?
if legacy {
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 {
let sanitized = sanitize_seed_phrase(seed_phrase);
let parse_language_fn = || {
@@ -446,7 +553,11 @@ pub fn keypair_from_seed_phrase(
let mnemonic = parse_language_fn()?;
let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = Seed::new(&mnemonic, &passphrase);
keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)?
if legacy {
keypair_from_seed(seed.as_bytes())?
} else {
keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)?
}
};
if confirm_pubkey {
@@ -525,21 +636,24 @@ mod tests {
SignerSource {
kind: SignerSourceKind::Stdin,
derivation_path: None,
legacy: false,
}
));
let ask = "stdin:".to_string();
let stdin = "stdin:".to_string();
assert!(matches!(
parse_signer_source(&ask).unwrap(),
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::Ask,
kind: SignerSourceKind::Prompt,
derivation_path: None,
legacy: true,
}
));
let pubkey = Pubkey::new_unique();
@@ -547,6 +661,7 @@ mod tests {
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
kind: SignerSourceKind::Pubkey(p),
derivation_path: None,
legacy: false,
}
if p == pubkey)
);
@@ -567,12 +682,14 @@ mod tests {
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)
);
@@ -584,6 +701,7 @@ mod tests {
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 {
@@ -594,6 +712,7 @@ mod tests {
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();
@@ -603,24 +722,27 @@ mod tests {
Err(SignerSourceError::IoError(_))
));
let ask = "ask:".to_string();
let prompt = "prompt:".to_string();
assert!(matches!(
parse_signer_source(&ask).unwrap(),
parse_signer_source(&prompt).unwrap(),
SignerSource {
kind: SignerSourceKind::Ask,
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

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"

View File

@@ -107,13 +107,13 @@ mod test {
#[test]
fn compute_websocket_url() {
assert_eq!(
Config::compute_websocket_url(&"http://devnet.solana.com"),
"ws://devnet.solana.com/".to_string()
Config::compute_websocket_url(&"http://api.devnet.solana.com"),
"ws://api.devnet.solana.com/".to_string()
);
assert_eq!(
Config::compute_websocket_url(&"https://devnet.solana.com"),
"wss://devnet.solana.com/".to_string()
Config::compute_websocket_url(&"https://api.devnet.solana.com"),
"wss://api.devnet.solana.com/".to_string()
);
assert_eq!(

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli-output"
description = "Blockchain, Rebuilt for Scale"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -19,13 +19,13 @@ 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.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-client = { path = "../client", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.7" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.7" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.7" }
solana-account-decoder = { path = "../account-decoder", version = "=1.6.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-client = { path = "../client", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.9" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
[package.metadata.docs.rs]

View File

@@ -15,8 +15,8 @@ use {
solana_account_decoder::parse_token::UiTokenAccount,
solana_clap_utils::keypair::SignOnly,
solana_client::rpc_response::{
RpcAccountBalance, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount, RpcSupply,
RpcVoteAccountInfo,
RpcAccountBalance, RpcContactInfo, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount,
RpcSupply, RpcVoteAccountInfo,
},
solana_sdk::{
clock::{Epoch, Slot, UnixTimestamp},
@@ -2283,6 +2283,97 @@ impl fmt::Display for CliTransactionConfirmation {
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliGossipNode {
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identity_label: Option<String>,
pub identity_pubkey: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub gossip_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tpu_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rpc_host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
impl CliGossipNode {
pub fn new(info: RpcContactInfo, labels: &HashMap<String, String>) -> Self {
Self {
ip_address: info.gossip.map(|addr| addr.ip().to_string()),
identity_label: labels.get(&info.pubkey).cloned(),
identity_pubkey: info.pubkey,
gossip_port: info.gossip.map(|addr| addr.port()),
tpu_port: info.tpu.map(|addr| addr.port()),
rpc_host: info.rpc.map(|addr| addr.to_string()),
version: info.version,
}
}
}
fn unwrap_to_string_or_none<T>(option: Option<T>) -> String
where
T: std::string::ToString,
{
unwrap_to_string_or_default(option, "none")
}
fn unwrap_to_string_or_default<T>(option: Option<T>, default: &str) -> String
where
T: std::string::ToString,
{
option
.as_ref()
.map(|v| v.to_string())
.unwrap_or_else(|| default.to_string())
}
impl fmt::Display for CliGossipNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{:15} | {:44} | {:6} | {:5} | {:21} | {}",
unwrap_to_string_or_none(self.ip_address.as_ref()),
self.identity_label
.as_ref()
.unwrap_or(&self.identity_pubkey),
unwrap_to_string_or_none(self.gossip_port.as_ref()),
unwrap_to_string_or_none(self.tpu_port.as_ref()),
unwrap_to_string_or_none(self.rpc_host.as_ref()),
unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
)
}
}
impl QuietDisplay for CliGossipNode {}
impl VerboseDisplay for CliGossipNode {}
#[derive(Serialize, Deserialize)]
pub struct CliGossipNodes(pub Vec<CliGossipNode>);
impl fmt::Display for CliGossipNodes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"IP Address | Node identifier \
| Gossip | TPU | RPC Address | Version\n\
----------------+----------------------------------------------+\
--------+-------+-----------------------+----------------",
)?;
for node in self.0.iter() {
writeln!(f, "{}", node)?;
}
writeln!(f, "Nodes: {}", self.0.len())
}
}
impl QuietDisplay for CliGossipNodes {}
impl VerboseDisplay for CliGossipNodes {}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "1.6.7"
version = "1.6.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -28,30 +28,30 @@ reqwest = { version = "0.11.2", default-features = false, features = ["blocking"
serde = "1.0.122"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "=1.6.7" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-cli-config = { path = "../cli-config", version = "=1.6.7" }
solana-cli-output = { path = "../cli-output", version = "=1.6.7" }
solana-client = { path = "../client", version = "=1.6.7" }
solana-config-program = { path = "../programs/config", version = "=1.6.7" }
solana-faucet = { path = "../faucet", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-account-decoder = { path = "../account-decoder", version = "=1.6.9" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.6.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-cli-config = { path = "../cli-config", version = "=1.6.9" }
solana-cli-output = { path = "../cli-output", version = "=1.6.9" }
solana-client = { path = "../client", version = "=1.6.9" }
solana-config-program = { path = "../programs/config", version = "=1.6.9" }
solana-faucet = { path = "../faucet", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana_rbpf = "=0.2.8"
solana-remote-wallet = { path = "../remote-wallet", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.7" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.7" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.9" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
thiserror = "1.0.21"
tiny-bip39 = "0.7.0"
url = "2.1.1"
[dev-dependencies]
solana-core = { path = "../core", version = "=1.6.7" }
solana-core = { path = "../core", version = "=1.6.9" }
tempfile = "3.1.0"
[[bin]]

View File

@@ -61,7 +61,6 @@ use solana_vote_program::vote_state::VoteState;
use std::{
collections::{BTreeMap, HashMap, VecDeque},
fmt,
net::SocketAddr,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
@@ -1655,40 +1654,14 @@ pub fn process_live_slots(config: &CliConfig) -> ProcessResult {
pub fn process_show_gossip(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
fn format_port(addr: Option<SocketAddr>) -> String {
addr.map(|addr| addr.port().to_string())
.unwrap_or_else(|| "none".to_string())
}
let s: Vec<_> = cluster_nodes
let nodes: Vec<_> = cluster_nodes
.into_iter()
.map(|node| {
format!(
"{:15} | {:44} | {:6} | {:5} | {:21} | {}",
node.gossip
.map(|addr| addr.ip().to_string())
.unwrap_or_else(|| "none".to_string()),
format_labeled_address(&node.pubkey, &config.address_labels),
format_port(node.gossip),
format_port(node.tpu),
node.rpc
.map(|addr| addr.to_string())
.unwrap_or_else(|| "none".to_string()),
node.version.unwrap_or_else(|| "unknown".to_string()),
)
})
.map(|node| CliGossipNode::new(node, &config.address_labels))
.collect();
Ok(format!(
"IP Address | Node identifier \
| Gossip | TPU | RPC Address | Version\n\
----------------+----------------------------------------------+\
--------+-------+-----------------------+----------------\n\
{}\n\
Nodes: {}",
s.join("\n"),
s.len(),
))
Ok(config
.output_format
.formatted_string(&CliGossipNodes(nodes)))
}
pub fn process_show_stakes(

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "1.6.7"
version = "1.6.9"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -24,14 +24,14 @@ semver = "0.11.0"
serde = "1.0.122"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-faucet = { path = "../faucet", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.7" }
solana-account-decoder = { path = "../account-decoder", version = "=1.6.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-faucet = { path = "../faucet", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.9" }
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tungstenite = "0.10.1"
@@ -40,7 +40,7 @@ url = "2.1.1"
[dev-dependencies]
assert_matches = "1.3.0"
jsonrpc-http-server = "17.0.0"
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -7,7 +7,11 @@ use {
rpc_sender::RpcSender,
},
log::*,
reqwest::{self, header::CONTENT_TYPE, StatusCode},
reqwest::{
self,
header::{CONTENT_TYPE, RETRY_AFTER},
StatusCode,
},
std::{
sync::{
atomic::{AtomicU64, Ordering},
@@ -83,14 +87,24 @@ impl RpcSender for HttpSender {
if response.status() == StatusCode::TOO_MANY_REQUESTS
&& too_many_requests_retries > 0
{
let mut duration = Duration::from_millis(500);
if let Some(retry_after) = response.headers().get(RETRY_AFTER) {
if let Ok(retry_after) = retry_after.to_str() {
if let Ok(retry_after) = retry_after.parse::<u64>() {
if retry_after < 120 {
duration = Duration::from_secs(retry_after);
}
}
}
}
too_many_requests_retries -= 1;
debug!(
"Server responded with {:?}, {} retries left",
response, too_many_requests_retries
"Too many requests: server responded with {:?}, {} retries left, pausing for {:?}",
response, too_many_requests_retries, duration
);
// Sleep for 500ms to give the server a break
sleep(Duration::from_millis(500));
sleep(duration);
continue;
}
return Err(response.error_for_status().unwrap_err().into());

View File

@@ -15,6 +15,7 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: i64
pub const JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: i64 = -32007;
pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008;
pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009;
pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010;
pub enum RpcCustomError {
BlockCleanedUp {
@@ -40,6 +41,9 @@ pub enum RpcCustomError {
LongTermStorageSlotSkipped {
slot: Slot,
},
KeyExcludedFromSecondaryIndex {
index_key: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
@@ -117,6 +121,17 @@ impl From<RpcCustomError> for Error {
message: format!("Slot {} was skipped, or missing in long-term storage", slot),
data: None,
},
RpcCustomError::KeyExcludedFromSecondaryIndex { index_key } => Self {
code: ErrorCode::ServerError(
JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX,
),
message: format!(
"{} excluded from account secondary indexes; \
this RPC method unavailable for key",
index_key
),
data: None,
},
}
}
}

View File

@@ -206,6 +206,8 @@ pub struct RpcContactInfo {
pub version: Option<String>,
/// First 4 bytes of the FeatureSet identifier
pub feature_set: Option<u32>,
/// Shred version
pub shred_version: Option<u16>,
}
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot

View File

@@ -1,7 +1,7 @@
[package]
name = "solana-core"
description = "Blockchain, Rebuilt for Scale"
version = "1.6.7"
version = "1.6.9"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-core"
readme = "../README.md"
@@ -52,37 +52,37 @@ serde = "1.0.122"
serde_bytes = "0.11"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "=1.6.7" }
solana-banks-server = { path = "../banks-server", version = "=1.6.7" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.7" }
solana-client = { path = "../client", version = "=1.6.7" }
solana-faucet = { path = "../faucet", version = "=1.6.7" }
solana-ledger = { path = "../ledger", version = "=1.6.7" }
solana-logger = { path = "../logger", version = "=1.6.7" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.6.7" }
solana-metrics = { path = "../metrics", version = "=1.6.7" }
solana-measure = { path = "../measure", version = "=1.6.7" }
solana-net-utils = { path = "../net-utils", version = "=1.6.7" }
solana-perf = { path = "../perf", version = "=1.6.7" }
solana-program-test = { path = "../program-test", version = "=1.6.7" }
solana-runtime = { path = "../runtime", version = "=1.6.7" }
solana-sdk = { path = "../sdk", version = "=1.6.7" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.6.7" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.6.7" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.7" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.6.7" }
solana-streamer = { path = "../streamer", version = "=1.6.7" }
solana-sys-tuner = { path = "../sys-tuner", version = "=1.6.7" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.7" }
solana-version = { path = "../version", version = "=1.6.7" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.7" }
solana-account-decoder = { path = "../account-decoder", version = "=1.6.9" }
solana-banks-server = { path = "../banks-server", version = "=1.6.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.6.9" }
solana-client = { path = "../client", version = "=1.6.9" }
solana-faucet = { path = "../faucet", version = "=1.6.9" }
solana-ledger = { path = "../ledger", version = "=1.6.9" }
solana-logger = { path = "../logger", version = "=1.6.9" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.6.9" }
solana-metrics = { path = "../metrics", version = "=1.6.9" }
solana-measure = { path = "../measure", version = "=1.6.9" }
solana-net-utils = { path = "../net-utils", version = "=1.6.9" }
solana-perf = { path = "../perf", version = "=1.6.9" }
solana-program-test = { path = "../program-test", version = "=1.6.9" }
solana-runtime = { path = "../runtime", version = "=1.6.9" }
solana-sdk = { path = "../sdk", version = "=1.6.9" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.6.9" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.6.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.6.9" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.6.9" }
solana-streamer = { path = "../streamer", version = "=1.6.9" }
solana-sys-tuner = { path = "../sys-tuner", version = "=1.6.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.6.9" }
solana-version = { path = "../version", version = "=1.6.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.6.9" }
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
tempfile = "3.1.0"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tokio_02 = { version = "0.2", package = "tokio", features = ["full"] }
tokio-util = { version = "0.3", features = ["codec"] } # This crate needs to stay in sync with tokio_02, until that dependency can be removed
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.6.7" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.6.9" }
trees = "0.2.1"
[dev-dependencies]

View File

@@ -1,18 +1,20 @@
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use solana_ledger::blockstore::Blockstore;
use solana_measure::measure::Measure;
use solana_runtime::bank::Bank;
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
pub use solana_ledger::blockstore_processor::CacheBlockTimeSender;
use {
crossbeam_channel::{Receiver, RecvTimeoutError},
solana_ledger::blockstore::Blockstore,
solana_measure::measure::Measure,
solana_runtime::bank::Bank,
std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, Builder, JoinHandle},
time::Duration,
},
thread::{self, Builder, JoinHandle},
time::Duration,
};
pub type CacheBlockTimeReceiver = Receiver<Arc<Bank>>;
pub type CacheBlockTimeSender = Sender<Arc<Bank>>;
pub struct CacheBlockTimeService {
thread_hdl: JoinHandle<()>,

View File

@@ -15,6 +15,7 @@
use crate::{
cluster_info_metrics::{submit_gossip_stats, Counter, GossipStats, ScopedTimer},
contact_info::ContactInfo,
crds::Cursor,
crds_gossip::CrdsGossip,
crds_gossip_error::CrdsGossipError,
crds_gossip_pull::{CrdsFilter, ProcessPullStats, CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS},
@@ -1120,20 +1121,13 @@ impl ClusterInfo {
Ok(())
}
/// Get votes in the crds
/// * since - The timestamp of when the vote inserted must be greater than
/// since. This allows the bank to query for new votes only.
///
/// * return - The votes, and the max timestamp from the new set.
pub fn get_votes(&self, since: u64) -> (Vec<CrdsValueLabel>, Vec<Transaction>, u64) {
let mut max_ts = since;
let (labels, txs): (Vec<CrdsValueLabel>, Vec<Transaction>) = self
/// Returns votes inserted since the given cursor.
pub fn get_votes(&self, cursor: &mut Cursor) -> (Vec<CrdsValueLabel>, Vec<Transaction>) {
let (labels, txs): (_, Vec<_>) = self
.time_gossip_read_lock("get_votes", &self.stats.get_votes)
.crds
.get_votes()
.filter(|vote| vote.insert_timestamp > since)
.get_votes(cursor)
.map(|vote| {
max_ts = std::cmp::max(vote.insert_timestamp, max_ts);
let transaction = match &vote.value.data {
CrdsData::Vote(_, vote) => vote.transaction().clone(),
_ => panic!("this should not happen!"),
@@ -1142,7 +1136,7 @@ impl ClusterInfo {
})
.unzip();
inc_new_counter_info!("cluster_info-get_votes-count", txs.len());
(labels, txs, max_ts)
(labels, txs)
}
pub(crate) fn push_duplicate_shred(&self, shred: &Shred, other_payload: &[u8]) -> Result<()> {
@@ -1180,52 +1174,15 @@ impl ClusterInfo {
.map(map)
}
pub fn get_lowest_slot_for_node<F, Y>(
&self,
pubkey: &Pubkey,
since: Option<u64>,
map: F,
) -> Option<Y>
where
F: FnOnce(&LowestSlot, u64) -> Y,
{
self.gossip
.read()
.unwrap()
.crds
.get(&CrdsValueLabel::LowestSlot(*pubkey))
.filter(|x| {
since
.map(|since| x.insert_timestamp > since)
.unwrap_or(true)
pub(crate) fn get_epoch_slots(&self, cursor: &mut Cursor) -> Vec<EpochSlots> {
let gossip = self.gossip.read().unwrap();
let entries = gossip.crds.get_epoch_slots(cursor);
entries
.map(|entry| match &entry.value.data {
CrdsData::EpochSlots(_, slots) => slots.clone(),
_ => panic!("this should not happen!"),
})
.map(|x| map(x.value.lowest_slot().unwrap(), x.insert_timestamp))
}
pub fn get_epoch_slots_since(
&self,
timestamp: u64,
) -> (
Vec<EpochSlots>,
Option<u64>, // Most recent insert timestmap.
) {
let mut max_ts = 0;
let vals: Vec<_> = self
.gossip
.read()
.unwrap()
.crds
.get_epoch_slots_since(timestamp)
.map(|value| {
max_ts = std::cmp::max(max_ts, value.insert_timestamp);
match &value.value.data {
CrdsData::EpochSlots(_, slots) => slots.clone(),
_ => panic!("this should not happen!"),
}
})
.collect();
let max_ts = if vals.is_empty() { None } else { Some(max_ts) };
(vals, max_ts)
.collect()
}
pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> {
@@ -3658,7 +3615,8 @@ mod tests {
);
cluster_info.push_vote(&unrefresh_tower, unrefresh_tx.clone());
cluster_info.flush_push_queue();
let (_, votes, max_ts) = cluster_info.get_votes(0);
let mut cursor = Cursor::default();
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes, vec![unrefresh_tx.clone()]);
// Now construct vote for the slot to be refreshed later
@@ -3679,9 +3637,9 @@ mod tests {
// shouldn't add the vote
cluster_info.refresh_vote(refresh_tx.clone(), refresh_slot);
cluster_info.flush_push_queue();
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes, vec![]);
let (_, votes, _) = cluster_info.get_votes(0);
let (_, votes) = cluster_info.get_votes(&mut Cursor::default());
assert_eq!(votes.len(), 1);
assert!(votes.contains(&unrefresh_tx));
@@ -3690,7 +3648,7 @@ mod tests {
cluster_info.flush_push_queue();
// Should be two votes in gossip
let (_, votes, _) = cluster_info.get_votes(0);
let (_, votes) = cluster_info.get_votes(&mut Cursor::default());
assert_eq!(votes.len(), 2);
assert!(votes.contains(&unrefresh_tx));
assert!(votes.contains(&refresh_tx));
@@ -3716,12 +3674,12 @@ mod tests {
cluster_info.flush_push_queue();
// The diff since `max_ts` should only be the latest refreshed vote
let (_, votes, _) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes.len(), 1);
assert_eq!(votes[0], latest_refresh_tx);
// Should still be two votes in gossip
let (_, votes, _) = cluster_info.get_votes(0);
let (_, votes) = cluster_info.get_votes(&mut Cursor::default());
assert_eq!(votes.len(), 2);
assert!(votes.contains(&unrefresh_tx));
assert!(votes.contains(&latest_refresh_tx));
@@ -3735,10 +3693,9 @@ mod tests {
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
// make sure empty crds is handled correctly
let now = timestamp();
let (_, votes, max_ts) = cluster_info.get_votes(now);
let mut cursor = Cursor::default();
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes, vec![]);
assert_eq!(max_ts, now);
// add a vote
let vote = Vote::new(
@@ -3758,8 +3715,7 @@ mod tests {
cluster_info.push_vote(&tower, tx.clone());
cluster_info.flush_push_queue();
// -1 to make sure that the clock is strictly lower then when insert occurred
let (labels, votes, max_ts) = cluster_info.get_votes(now - 1);
let (labels, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes, vec![tx]);
assert_eq!(labels.len(), 1);
match labels[0] {
@@ -3769,12 +3725,9 @@ mod tests {
_ => panic!("Bad match"),
}
assert!(max_ts >= now - 1);
// make sure timestamp filter works
let (_, votes, new_max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes, vec![]);
assert_eq!(max_ts, new_max_ts);
}
fn new_vote_transaction<R: Rng>(rng: &mut R, slots: Vec<Slot>) -> Transaction {
@@ -3792,8 +3745,8 @@ mod tests {
#[test]
fn test_push_votes_with_tower() {
let get_vote_slots = |cluster_info: &ClusterInfo, now| -> Vec<Slot> {
let (labels, _, _) = cluster_info.get_votes(now);
let get_vote_slots = |cluster_info: &ClusterInfo| -> Vec<Slot> {
let (labels, _) = cluster_info.get_votes(&mut Cursor::default());
let gossip = cluster_info.gossip.read().unwrap();
let mut vote_slots = HashSet::new();
for label in labels {
@@ -3807,7 +3760,6 @@ mod tests {
vote_slots.into_iter().collect()
};
let mut rng = rand::thread_rng();
let now = timestamp();
let keys = Keypair::new();
let contact_info = ContactInfo::new_localhost(&keys.pubkey(), 0);
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
@@ -3818,7 +3770,7 @@ mod tests {
let vote = new_vote_transaction(&mut rng, vec![slot]);
cluster_info.push_vote(&tower, vote);
}
let vote_slots = get_vote_slots(&cluster_info, now);
let vote_slots = get_vote_slots(&cluster_info);
assert_eq!(vote_slots.len(), MAX_LOCKOUT_HISTORY);
for vote_slot in vote_slots {
assert!(vote_slot < MAX_LOCKOUT_HISTORY as u64);
@@ -3829,7 +3781,7 @@ mod tests {
tower.remove(23);
let vote = new_vote_transaction(&mut rng, vec![slot]);
cluster_info.push_vote(&tower, vote);
let vote_slots = get_vote_slots(&cluster_info, now);
let vote_slots = get_vote_slots(&cluster_info);
assert_eq!(vote_slots.len(), MAX_LOCKOUT_HISTORY);
for vote_slot in vote_slots {
assert!(vote_slot <= slot);
@@ -3843,7 +3795,7 @@ mod tests {
tower.remove(5);
let vote = new_vote_transaction(&mut rng, vec![slot]);
cluster_info.push_vote(&tower, vote);
let vote_slots = get_vote_slots(&cluster_info, now);
let vote_slots = get_vote_slots(&cluster_info);
assert_eq!(vote_slots.len(), MAX_LOCKOUT_HISTORY);
for vote_slot in vote_slots {
assert!(vote_slot <= slot);
@@ -3857,23 +3809,17 @@ mod tests {
let keys = Keypair::new();
let contact_info = ContactInfo::new_localhost(&keys.pubkey(), 0);
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
let (slots, since) = cluster_info.get_epoch_slots_since(0);
let slots = cluster_info.get_epoch_slots(&mut Cursor::default());
assert!(slots.is_empty());
assert!(since.is_none());
cluster_info.push_epoch_slots(&[0]);
cluster_info.flush_push_queue();
let (slots, since) = cluster_info.get_epoch_slots_since(std::u64::MAX);
assert!(slots.is_empty());
assert_eq!(since, None);
let (slots, since) = cluster_info.get_epoch_slots_since(0);
let mut cursor = Cursor::default();
let slots = cluster_info.get_epoch_slots(&mut cursor);
assert_eq!(slots.len(), 1);
assert!(since.is_some());
let (slots, since2) = cluster_info.get_epoch_slots_since(since.unwrap() + 1);
let slots = cluster_info.get_epoch_slots(&mut cursor);
assert!(slots.is_empty());
assert_eq!(since2, None);
}
#[test]
@@ -4216,10 +4162,9 @@ mod tests {
cluster_info.flush_push_queue();
cluster_info.push_epoch_slots(&range[16000..]);
cluster_info.flush_push_queue();
let (slots, since) = cluster_info.get_epoch_slots_since(0);
let slots = cluster_info.get_epoch_slots(&mut Cursor::default());
let slots: Vec<_> = slots.iter().flat_map(|x| x.to_slots(0)).collect();
assert_eq!(slots, range);
assert!(since.is_some());
}
#[test]

View File

@@ -1,5 +1,6 @@
use crate::{
cluster_info::{ClusterInfo, GOSSIP_SLEEP_MILLIS},
crds::Cursor,
crds_value::CrdsValueLabel,
optimistic_confirmation_verifier::OptimisticConfirmationVerifier,
optimistically_confirmed_bank_tracker::{BankNotification, BankNotificationSender},
@@ -326,23 +327,18 @@ impl ClusterInfoVoteListener {
verified_vote_label_packets_sender: VerifiedLabelVotePacketsSender,
verified_vote_transactions_sender: VerifiedVoteTransactionsSender,
) -> Result<()> {
let mut last_ts = 0;
loop {
if exit.load(Ordering::Relaxed) {
return Ok(());
}
let (labels, votes, new_ts) = cluster_info.get_votes(last_ts);
let mut cursor = Cursor::default();
while !exit.load(Ordering::Relaxed) {
let (labels, votes) = cluster_info.get_votes(&mut cursor);
inc_new_counter_debug!("cluster_info_vote_listener-recv_count", votes.len());
last_ts = new_ts;
if !votes.is_empty() {
let (vote_txs, packets) = Self::verify_votes(votes, labels);
verified_vote_transactions_sender.send(vote_txs)?;
verified_vote_label_packets_sender.send(packets)?;
}
sleep(Duration::from_millis(GOSSIP_SLEEP_MILLIS));
}
Ok(())
}
#[allow(clippy::type_complexity)]

View File

@@ -1,5 +1,5 @@
use crate::{
cluster_info::ClusterInfo, contact_info::ContactInfo, epoch_slots::EpochSlots,
cluster_info::ClusterInfo, contact_info::ContactInfo, crds::Cursor, epoch_slots::EpochSlots,
serve_repair::RepairType,
};
use itertools::Itertools;
@@ -7,10 +7,7 @@ use solana_runtime::{bank_forks::BankForks, epoch_stakes::NodeIdToVoteAccounts};
use solana_sdk::{clock::Slot, pubkey::Pubkey};
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::{
atomic::{AtomicU64, Ordering},
Arc, RwLock,
},
sync::{Arc, Mutex, RwLock},
};
// Limit the size of cluster-slots map in case
@@ -22,22 +19,26 @@ pub type SlotPubkeys = HashMap<Pubkey, u64>;
#[derive(Default)]
pub struct ClusterSlots {
cluster_slots: RwLock<BTreeMap<Slot, Arc<RwLock<SlotPubkeys>>>>,
since: AtomicU64,
validator_stakes: RwLock<Arc<NodeIdToVoteAccounts>>,
epoch: RwLock<Option<u64>>,
cursor: Mutex<Cursor>,
}
impl ClusterSlots {
pub fn lookup(&self, slot: Slot) -> Option<Arc<RwLock<SlotPubkeys>>> {
self.cluster_slots.read().unwrap().get(&slot).cloned()
}
pub fn update(&self, root: Slot, cluster_info: &ClusterInfo, bank_forks: &RwLock<BankForks>) {
self.update_peers(bank_forks);
let since = self.since.load(Ordering::Relaxed);
let (epoch_slots, since) = cluster_info.get_epoch_slots_since(since);
self.update_internal(root, epoch_slots, since);
let epoch_slots = {
let mut cursor = self.cursor.lock().unwrap();
cluster_info.get_epoch_slots(&mut cursor)
};
self.update_internal(root, epoch_slots);
}
fn update_internal(&self, root: Slot, epoch_slots_list: Vec<EpochSlots>, since: Option<u64>) {
fn update_internal(&self, root: Slot, epoch_slots_list: Vec<EpochSlots>) {
// Attach validator's total stake.
let epoch_slots_list: Vec<_> = {
let validator_stakes = self.validator_stakes.read().unwrap();
@@ -86,9 +87,6 @@ impl ClusterSlots {
cluster_slots.split_off(&key);
}
}
if let Some(since) = since {
self.since.store(since + 1, Ordering::Relaxed);
}
}
pub fn collect(&self, id: &Pubkey) -> HashSet<Slot> {
@@ -206,23 +204,20 @@ mod tests {
fn test_default() {
let cs = ClusterSlots::default();
assert!(cs.cluster_slots.read().unwrap().is_empty());
assert_eq!(cs.since.load(Ordering::Relaxed), 0);
}
#[test]
fn test_update_noop() {
let cs = ClusterSlots::default();
cs.update_internal(0, vec![], None);
cs.update_internal(0, vec![]);
assert!(cs.cluster_slots.read().unwrap().is_empty());
assert_eq!(cs.since.load(Ordering::Relaxed), 0);
}
#[test]
fn test_update_empty() {
let cs = ClusterSlots::default();
let epoch_slot = EpochSlots::default();
cs.update_internal(0, vec![epoch_slot], Some(0));
assert_eq!(cs.since.load(Ordering::Relaxed), 1);
cs.update_internal(0, vec![epoch_slot]);
assert!(cs.lookup(0).is_none());
}
@@ -232,8 +227,7 @@ mod tests {
let cs = ClusterSlots::default();
let mut epoch_slot = EpochSlots::default();
epoch_slot.fill(&[0], 0);
cs.update_internal(0, vec![epoch_slot], Some(0));
assert_eq!(cs.since.load(Ordering::Relaxed), 1);
cs.update_internal(0, vec![epoch_slot]);
assert!(cs.lookup(0).is_none());
}
@@ -242,8 +236,7 @@ mod tests {
let cs = ClusterSlots::default();
let mut epoch_slot = EpochSlots::default();
epoch_slot.fill(&[1], 0);
cs.update_internal(0, vec![epoch_slot], Some(0));
assert_eq!(cs.since.load(Ordering::Relaxed), 1);
cs.update_internal(0, vec![epoch_slot]);
assert!(cs.lookup(0).is_none());
assert!(cs.lookup(1).is_some());
assert_eq!(
@@ -373,7 +366,7 @@ mod tests {
);
*cs.validator_stakes.write().unwrap() = map;
cs.update_internal(0, vec![epoch_slot], None);
cs.update_internal(0, vec![epoch_slot]);
assert!(cs.lookup(1).is_some());
assert_eq!(
cs.lookup(1)
@@ -390,7 +383,7 @@ mod tests {
let cs = ClusterSlots::default();
let mut epoch_slot = EpochSlots::default();
epoch_slot.fill(&[1], 0);
cs.update_internal(0, vec![epoch_slot], None);
cs.update_internal(0, vec![epoch_slot]);
let self_id = solana_sdk::pubkey::new_rand();
assert_eq!(
cs.generate_repairs_for_missing_slots(&self_id, 0),
@@ -404,7 +397,7 @@ mod tests {
let mut epoch_slot = EpochSlots::default();
epoch_slot.fill(&[1], 0);
let self_id = epoch_slot.from;
cs.update_internal(0, vec![epoch_slot], None);
cs.update_internal(0, vec![epoch_slot]);
let slots: Vec<Slot> = cs.collect(&self_id).into_iter().collect();
assert_eq!(slots, vec![1]);
}
@@ -415,7 +408,7 @@ mod tests {
let mut epoch_slot = EpochSlots::default();
epoch_slot.fill(&[1], 0);
let self_id = epoch_slot.from;
cs.update_internal(0, vec![epoch_slot], None);
cs.update_internal(0, vec![epoch_slot]);
assert!(cs
.generate_repairs_for_missing_slots(&self_id, 0)
.is_empty());

View File

@@ -185,19 +185,21 @@ impl ClusterSlotsService {
#[cfg(test)]
mod test {
use super::*;
use crate::cluster_info::Node;
use crate::{cluster_info::Node, crds_value::CrdsValueLabel};
#[test]
pub fn test_update_lowest_slot() {
let node_info = Node::new_localhost_with_pubkey(&Pubkey::default());
let pubkey = Pubkey::new_unique();
let node_info = Node::new_localhost_with_pubkey(&pubkey);
let cluster_info = ClusterInfo::new_with_invalid_keypair(node_info.info);
ClusterSlotsService::update_lowest_slot(&Pubkey::default(), 5, &cluster_info);
ClusterSlotsService::update_lowest_slot(&pubkey, 5, &cluster_info);
cluster_info.flush_push_queue();
let lowest = cluster_info
.get_lowest_slot_for_node(&Pubkey::default(), None, |lowest_slot, _| {
lowest_slot.clone()
})
.unwrap();
let lowest = {
let label = CrdsValueLabel::LowestSlot(pubkey);
let gossip = cluster_info.gossip.read().unwrap();
let entry = gossip.crds.get(&label).unwrap();
entry.value.lowest_slot().unwrap().clone()
};
assert_eq!(lowest.lowest, 5);
}
}

View File

@@ -35,7 +35,7 @@ use solana_sdk::hash::{hash, Hash};
use solana_sdk::pubkey::Pubkey;
use std::{
cmp::Ordering,
collections::{hash_map, BTreeSet, HashMap},
collections::{hash_map, BTreeMap, HashMap},
ops::{Bound, Index, IndexMut},
};
@@ -48,14 +48,16 @@ const MAX_CRDS_VALUES_PER_PUBKEY: usize = 32;
pub struct Crds {
/// Stores the map of labels and values
table: IndexMap<CrdsValueLabel, VersionedCrdsValue>,
pub num_inserts: usize, // Only used in tests.
cursor: Cursor, // Next insert ordinal location.
shards: CrdsShards,
nodes: IndexSet<usize>, // Indices of nodes' ContactInfo.
votes: IndexSet<usize>, // Indices of Vote crds values.
// Indices of EpochSlots crds values ordered by insert timestamp.
epoch_slots: BTreeSet<(u64 /*insert timestamp*/, usize)>,
// Indices of EpochSlots keyed by insert order.
epoch_slots: BTreeMap<u64 /*insert order*/, usize /*index*/>,
// Indices of all crds values associated with a node.
records: HashMap<Pubkey, IndexSet<usize>>,
// Indices of all entries keyed by insert order.
entries: BTreeMap<u64 /*insert order*/, usize /*index*/>,
}
#[derive(PartialEq, Debug)]
@@ -71,21 +73,36 @@ pub enum CrdsError {
/// stored in the Crds
#[derive(PartialEq, Debug, Clone)]
pub struct VersionedCrdsValue {
/// Ordinal index indicating insert order.
ordinal: u64,
pub value: CrdsValue,
/// local time when inserted
pub(crate) insert_timestamp: u64,
/// local time when updated
pub(crate) local_timestamp: u64,
/// value hash
pub(crate) value_hash: Hash,
}
#[derive(Clone, Copy, Default)]
pub struct Cursor(u64);
impl Cursor {
fn ordinal(&self) -> u64 {
self.0
}
// Updates the cursor position given the ordinal index of value consumed.
#[inline]
fn consume(&mut self, ordinal: u64) {
self.0 = self.0.max(ordinal + 1);
}
}
impl VersionedCrdsValue {
fn new(local_timestamp: u64, value: CrdsValue) -> Self {
fn new(value: CrdsValue, cursor: Cursor, local_timestamp: u64) -> Self {
let value_hash = hash(&serialize(&value).unwrap());
VersionedCrdsValue {
ordinal: cursor.ordinal(),
value,
insert_timestamp: local_timestamp,
local_timestamp,
value_hash,
}
@@ -96,12 +113,13 @@ impl Default for Crds {
fn default() -> Self {
Crds {
table: IndexMap::default(),
num_inserts: 0,
cursor: Cursor::default(),
shards: CrdsShards::new(CRDS_SHARDS_BITS),
nodes: IndexSet::default(),
votes: IndexSet::default(),
epoch_slots: BTreeSet::default(),
epoch_slots: BTreeMap::default(),
records: HashMap::default(),
entries: BTreeMap::default(),
}
}
}
@@ -140,7 +158,8 @@ impl Crds {
local_timestamp: u64,
) -> Result<Option<VersionedCrdsValue>, CrdsError> {
let label = value.label();
let value = VersionedCrdsValue::new(local_timestamp, value);
let pubkey = value.pubkey();
let value = VersionedCrdsValue::new(value, self.cursor, local_timestamp);
match self.table.entry(label) {
Entry::Vacant(entry) => {
let entry_index = entry.index();
@@ -153,17 +172,14 @@ impl Crds {
self.votes.insert(entry_index);
}
CrdsData::EpochSlots(_, _) => {
self.epoch_slots
.insert((value.insert_timestamp, entry_index));
self.epoch_slots.insert(value.ordinal, entry_index);
}
_ => (),
};
self.records
.entry(value.value.pubkey())
.or_default()
.insert(entry_index);
self.entries.insert(value.ordinal, entry_index);
self.records.entry(pubkey).or_default().insert(entry_index);
self.cursor.consume(value.ordinal);
entry.insert(value);
self.num_inserts += 1;
Ok(None)
}
Entry::Occupied(mut entry) if overrides(&value.value, entry.get()) => {
@@ -171,15 +187,15 @@ impl Crds {
self.shards.remove(entry_index, entry.get());
self.shards.insert(entry_index, &value);
if let CrdsData::EpochSlots(_, _) = value.value.data {
self.epoch_slots
.remove(&(entry.get().insert_timestamp, entry_index));
self.epoch_slots
.insert((value.insert_timestamp, entry_index));
self.epoch_slots.remove(&entry.get().ordinal);
self.epoch_slots.insert(value.ordinal, entry_index);
}
self.num_inserts += 1;
self.entries.remove(&entry.get().ordinal);
self.entries.insert(value.ordinal, entry_index);
// As long as the pubkey does not change, self.records
// does not need to be updated.
debug_assert_eq!(entry.get().value.pubkey(), value.value.pubkey());
debug_assert_eq!(entry.get().value.pubkey(), pubkey);
self.cursor.consume(value.ordinal);
Ok(Some(entry.insert(value)))
}
_ => {
@@ -228,20 +244,47 @@ impl Crds {
})
}
/// Returns all entries which are Vote.
pub(crate) fn get_votes(&self) -> impl Iterator<Item = &VersionedCrdsValue> {
self.votes.iter().map(move |i| self.table.index(*i))
/// Returns all vote entries inserted since the given cursor.
/// Updates the cursor as the votes are consumed.
pub(crate) fn get_votes<'a>(
&'a self,
cursor: &'a mut Cursor,
) -> impl Iterator<Item = &'a VersionedCrdsValue> {
let since = cursor.ordinal();
self.votes.iter().filter_map(move |i| {
let entry = self.table.index(*i);
if entry.ordinal >= since {
cursor.consume(entry.ordinal);
Some(entry)
} else {
None
}
})
}
/// Returns epoch-slots inserted since (or at) the given timestamp.
pub(crate) fn get_epoch_slots_since(
&self,
timestamp: u64,
) -> impl Iterator<Item = &VersionedCrdsValue> {
let range = (Bound::Included((timestamp, 0)), Bound::Unbounded);
self.epoch_slots
.range(range)
.map(move |(_, i)| self.table.index(*i))
/// Returns epoch-slots inserted since the given cursor.
/// Updates the cursor as the values are consumed.
pub(crate) fn get_epoch_slots<'a>(
&'a self,
cursor: &'a mut Cursor,
) -> impl Iterator<Item = &'a VersionedCrdsValue> {
let range = (Bound::Included(cursor.ordinal()), Bound::Unbounded);
self.epoch_slots.range(range).map(move |(ordinal, index)| {
cursor.consume(*ordinal);
self.table.index(*index)
})
}
/// Returns all entries inserted since the given cursor.
pub(crate) fn get_entries<'a>(
&'a self,
cursor: &'a mut Cursor,
) -> impl Iterator<Item = &'a VersionedCrdsValue> {
let range = (Bound::Included(cursor.ordinal()), Bound::Unbounded);
self.entries.range(range).map(move |(ordinal, index)| {
cursor.consume(*ordinal);
self.table.index(*index)
})
}
/// Returns all records associated with a pubkey.
@@ -386,10 +429,11 @@ impl Crds {
self.votes.swap_remove(&index);
}
CrdsData::EpochSlots(_, _) => {
self.epoch_slots.remove(&(value.insert_timestamp, index));
self.epoch_slots.remove(&value.ordinal);
}
_ => (),
}
self.entries.remove(&value.ordinal);
// Remove the index from records associated with the value's pubkey.
let pubkey = value.value.pubkey();
let mut records_entry = match self.records.entry(pubkey) {
@@ -420,11 +464,11 @@ impl Crds {
self.votes.insert(index);
}
CrdsData::EpochSlots(_, _) => {
self.epoch_slots.remove(&(value.insert_timestamp, size));
self.epoch_slots.insert((value.insert_timestamp, index));
self.epoch_slots.insert(value.ordinal, index);
}
_ => (),
};
self.entries.insert(value.ordinal, index);
let pubkey = value.value.pubkey();
let records = self.records.get_mut(&pubkey).unwrap();
records.swap_remove(&size);
@@ -549,8 +593,7 @@ mod test {
0,
)));
assert_eq!(crds.insert(val.clone(), 0), Ok(None));
assert_eq!(crds.table[&val.label()].insert_timestamp, 0);
assert_eq!(crds.table[&val.label()].ordinal, 0);
let val2 = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::default()));
assert_eq!(val2.label().pubkey(), val.label().pubkey());
@@ -558,20 +601,20 @@ mod test {
crds.update_record_timestamp(&val.label().pubkey(), 2);
assert_eq!(crds.table[&val.label()].local_timestamp, 2);
assert_eq!(crds.table[&val.label()].insert_timestamp, 0);
assert_eq!(crds.table[&val.label()].ordinal, 1);
assert_eq!(crds.table[&val2.label()].local_timestamp, 2);
assert_eq!(crds.table[&val2.label()].insert_timestamp, 0);
assert_eq!(crds.table[&val2.label()].ordinal, 1);
crds.update_record_timestamp(&val.label().pubkey(), 1);
assert_eq!(crds.table[&val.label()].local_timestamp, 2);
assert_eq!(crds.table[&val.label()].insert_timestamp, 0);
assert_eq!(crds.table[&val.label()].ordinal, 1);
let mut ci = ContactInfo::default();
ci.wallclock += 1;
let val3 = CrdsValue::new_unsigned(CrdsData::ContactInfo(ci));
assert_matches!(crds.insert(val3, 3), Ok(Some(_)));
assert_eq!(crds.table[&val2.label()].local_timestamp, 3);
assert_eq!(crds.table[&val2.label()].insert_timestamp, 3);
assert_eq!(crds.table[&val2.label()].ordinal, 2);
}
#[test]
fn test_find_old_records_default() {
@@ -729,7 +772,7 @@ mod test {
Err(_) => (),
}
}
assert_eq!(num_inserts, crds.num_inserts);
assert_eq!(num_inserts, crds.cursor.0 as usize);
assert!(num_inserts > 700);
assert!(num_overrides > 500);
assert!(crds.table.len() > 200);
@@ -744,63 +787,131 @@ mod test {
}
}
fn check_crds_value_indices<R: rand::Rng>(
rng: &mut R,
crds: &Crds,
) -> (
usize, // number of nodes
usize, // number of votes
usize, // number of epoch slots
) {
let size = crds.table.len();
let since = if size == 0 || rng.gen() {
rng.gen_range(0, crds.cursor.0 + 1)
} else {
crds.table[rng.gen_range(0, size)].ordinal
};
let num_epoch_slots = crds
.table
.values()
.filter(|v| v.ordinal >= since)
.filter(|v| matches!(v.value.data, CrdsData::EpochSlots(_, _)))
.count();
let mut cursor = Cursor(since);
assert_eq!(num_epoch_slots, crds.get_epoch_slots(&mut cursor).count());
assert_eq!(
cursor.0,
crds.epoch_slots
.iter()
.last()
.map(|(k, _)| k + 1)
.unwrap_or_default()
.max(since)
);
for value in crds.get_epoch_slots(&mut Cursor(since)) {
assert!(value.ordinal >= since);
match value.value.data {
CrdsData::EpochSlots(_, _) => (),
_ => panic!("not an epoch-slot!"),
}
}
let num_votes = crds
.table
.values()
.filter(|v| v.ordinal >= since)
.filter(|v| matches!(v.value.data, CrdsData::Vote(_, _)))
.count();
let mut cursor = Cursor(since);
assert_eq!(num_votes, crds.get_votes(&mut cursor).count());
assert_eq!(
cursor.0,
crds.table
.values()
.filter(|v| matches!(v.value.data, CrdsData::Vote(_, _)))
.map(|v| v.ordinal)
.max()
.map(|k| k + 1)
.unwrap_or_default()
.max(since)
);
for value in crds.get_votes(&mut Cursor(since)) {
assert!(value.ordinal >= since);
match value.value.data {
CrdsData::Vote(_, _) => (),
_ => panic!("not a vote!"),
}
}
let num_entries = crds
.table
.values()
.filter(|value| value.ordinal >= since)
.count();
let mut cursor = Cursor(since);
assert_eq!(num_entries, crds.get_entries(&mut cursor).count());
assert_eq!(
cursor.0,
crds.entries
.iter()
.last()
.map(|(k, _)| k + 1)
.unwrap_or_default()
.max(since)
);
for value in crds.get_entries(&mut Cursor(since)) {
assert!(value.ordinal >= since);
}
let num_nodes = crds
.table
.values()
.filter(|v| matches!(v.value.data, CrdsData::ContactInfo(_)))
.count();
let num_votes = crds
.table
.values()
.filter(|v| matches!(v.value.data, CrdsData::Vote(_, _)))
.count();
let num_epoch_slots = crds
.table
.values()
.filter(|v| matches!(v.value.data, CrdsData::EpochSlots(_, _)))
.count();
assert_eq!(
crds.table.len(),
crds.get_entries(&mut Cursor::default()).count()
);
assert_eq!(num_nodes, crds.get_nodes_contact_info().count());
assert_eq!(num_votes, crds.get_votes(&mut Cursor::default()).count());
assert_eq!(
num_epoch_slots,
crds.get_epoch_slots(&mut Cursor::default()).count()
);
for vote in crds.get_votes(&mut Cursor::default()) {
match vote.value.data {
CrdsData::Vote(_, _) => (),
_ => panic!("not a vote!"),
}
}
for epoch_slots in crds.get_epoch_slots(&mut Cursor::default()) {
match epoch_slots.value.data {
CrdsData::EpochSlots(_, _) => (),
_ => panic!("not an epoch-slot!"),
}
}
(num_nodes, num_votes, num_epoch_slots)
}
#[test]
fn test_crds_value_indices() {
fn check_crds_value_indices<R: rand::Rng>(
rng: &mut R,
crds: &Crds,
) -> (usize, usize, usize) {
if !crds.table.is_empty() {
let since = crds.table[rng.gen_range(0, crds.table.len())].insert_timestamp;
let num_epoch_slots = crds
.table
.values()
.filter(|value| {
value.insert_timestamp >= since
&& matches!(value.value.data, CrdsData::EpochSlots(_, _))
})
.count();
assert_eq!(num_epoch_slots, crds.get_epoch_slots_since(since).count());
for value in crds.get_epoch_slots_since(since) {
assert!(value.insert_timestamp >= since);
match value.value.data {
CrdsData::EpochSlots(_, _) => (),
_ => panic!("not an epoch-slot!"),
}
}
}
let num_nodes = crds
.table
.values()
.filter(|value| matches!(value.value.data, CrdsData::ContactInfo(_)))
.count();
let num_votes = crds
.table
.values()
.filter(|value| matches!(value.value.data, CrdsData::Vote(_, _)))
.count();
let num_epoch_slots = crds
.table
.values()
.filter(|value| matches!(value.value.data, CrdsData::EpochSlots(_, _)))
.count();
assert_eq!(num_nodes, crds.get_nodes_contact_info().count());
assert_eq!(num_votes, crds.get_votes().count());
assert_eq!(num_epoch_slots, crds.get_epoch_slots_since(0).count());
for vote in crds.get_votes() {
match vote.value.data {
CrdsData::Vote(_, _) => (),
_ => panic!("not a vote!"),
}
}
for epoch_slots in crds.get_epoch_slots_since(0) {
match epoch_slots.value.data {
CrdsData::EpochSlots(_, _) => (),
_ => panic!("not an epoch-slot!"),
}
}
(num_nodes, num_votes, num_epoch_slots)
}
let mut rng = thread_rng();
let keypairs: Vec<_> = repeat_with(Keypair::new).take(128).collect();
let mut crds = Crds::default();
@@ -820,11 +931,11 @@ mod test {
}
Err(_) => (),
}
if k % 64 == 0 {
if k % 16 == 0 {
check_crds_value_indices(&mut rng, &crds);
}
}
assert_eq!(num_inserts, crds.num_inserts);
assert_eq!(num_inserts, crds.cursor.0 as usize);
assert!(num_inserts > 700);
assert!(num_overrides > 500);
assert!(crds.table.len() > 200);
@@ -843,7 +954,7 @@ mod test {
let index = rng.gen_range(0, crds.table.len());
let key = crds.table.get_index(index).unwrap().0.clone();
crds.remove(&key);
if crds.table.len() % 64 == 0 {
if crds.table.len() % 16 == 0 {
check_crds_value_indices(&mut rng, &crds);
}
}
@@ -963,8 +1074,8 @@ mod test {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
fn test_equal() {
let val = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::default()));
let v1 = VersionedCrdsValue::new(1, val.clone());
let v2 = VersionedCrdsValue::new(1, val);
let v1 = VersionedCrdsValue::new(val.clone(), Cursor::default(), 1);
let v2 = VersionedCrdsValue::new(val, Cursor::default(), 1);
assert_eq!(v1, v2);
assert!(!(v1 != v2));
assert!(!overrides(&v1.value, &v2));
@@ -974,17 +1085,22 @@ mod test {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
fn test_hash_order() {
let v1 = VersionedCrdsValue::new(
1,
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::default(),
0,
))),
Cursor::default(),
1, // local_timestamp
);
let v2 = VersionedCrdsValue::new(
{
let mut contact_info = ContactInfo::new_localhost(&Pubkey::default(), 0);
contact_info.rpc = socketaddr!("0.0.0.0:0");
CrdsValue::new_unsigned(CrdsData::ContactInfo(contact_info))
},
Cursor::default(),
1, // local_timestamp
);
let v2 = VersionedCrdsValue::new(1, {
let mut contact_info = ContactInfo::new_localhost(&Pubkey::default(), 0);
contact_info.rpc = socketaddr!("0.0.0.0:0");
CrdsValue::new_unsigned(CrdsData::ContactInfo(contact_info))
});
assert_eq!(v1.value.label(), v2.value.label());
assert_eq!(v1.value.wallclock(), v2.value.wallclock());
@@ -1003,18 +1119,20 @@ mod test {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
fn test_wallclock_order() {
let v1 = VersionedCrdsValue::new(
1,
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::default(),
1,
))),
Cursor::default(),
1, // local_timestamp
);
let v2 = VersionedCrdsValue::new(
1,
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::default(),
0,
))),
Cursor::default(),
1, // local_timestamp
);
assert_eq!(v1.value.label(), v2.value.label());
assert!(overrides(&v1.value, &v2));
@@ -1027,18 +1145,20 @@ mod test {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
fn test_label_order() {
let v1 = VersionedCrdsValue::new(
1,
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&solana_sdk::pubkey::new_rand(),
0,
))),
Cursor::default(),
1, // local_timestamp
);
let v2 = VersionedCrdsValue::new(
1,
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&solana_sdk::pubkey::new_rand(),
0,
))),
Cursor::default(),
1, // local_timestamp
);
assert_ne!(v1, v2);
assert!(!(v1 == v2));

View File

@@ -285,7 +285,7 @@ impl CrdsGossip {
now: u64,
process_pull_stats: &mut ProcessPullStats,
) {
let success = self.pull.process_pull_responses(
self.pull.process_pull_responses(
&mut self.crds,
from,
responses,
@@ -294,7 +294,6 @@ impl CrdsGossip {
now,
process_pull_stats,
);
self.push.push_pull_responses(success, now);
}
pub fn make_timeouts_test(&self) -> HashMap<Pubkey, u64> {
@@ -316,10 +315,6 @@ impl CrdsGossip {
timeouts: &HashMap<Pubkey, u64>,
) -> usize {
let mut rv = 0;
if now > self.push.msg_timeout {
let min = now - self.push.msg_timeout;
self.push.purge_old_pending_push_messages(&self.crds, min);
}
if now > 5 * self.push.msg_timeout {
let min = now - 5 * self.push.msg_timeout;
self.push.purge_old_received_cache(min);

View File

@@ -15,7 +15,7 @@ use crate::{
crds::{Crds, CrdsError},
crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS},
crds_gossip_error::CrdsGossipError,
crds_value::{CrdsValue, CrdsValueLabel},
crds_value::CrdsValue,
ping_pong::PingCache,
};
use itertools::Itertools;
@@ -412,8 +412,7 @@ impl CrdsGossipPull {
mut failed_inserts: Vec<Hash>,
now: u64,
stats: &mut ProcessPullStats,
) -> Vec<(CrdsValueLabel, Hash, u64)> {
let mut success = vec![];
) {
let mut owners = HashSet::new();
for response in responses_expired_timeout {
match crds.insert(response, now) {
@@ -424,17 +423,14 @@ impl CrdsGossipPull {
}
}
for response in responses {
let label = response.label();
let wallclock = response.wallclock();
let owner = response.pubkey();
match crds.insert(response, now) {
Err(CrdsError::InsertFailed(value_hash)) => failed_inserts.push(value_hash),
Err(CrdsError::UnknownStakes) => (),
Ok(old) => {
stats.success += 1;
self.num_pulls += 1;
owners.insert(label.pubkey());
let value_hash = crds.get(&label).unwrap().value_hash;
success.push((label, value_hash, wallclock));
owners.insert(owner);
if let Some(val) = old {
self.purged_values.push_back((val.value_hash, now))
}
@@ -449,7 +445,6 @@ impl CrdsGossipPull {
self.purge_failed_inserts(now);
self.failed_inserts
.extend(failed_inserts.into_iter().zip(std::iter::repeat(now)));
success
}
pub fn purge_failed_inserts(&mut self, now: u64) {
@@ -1240,13 +1235,6 @@ mod test {
);
assert!(rsp.iter().all(|rsp| rsp.is_empty()));
assert!(dest_crds.lookup(&caller.label()).is_some());
assert_eq!(
dest_crds
.lookup_versioned(&caller.label())
.unwrap()
.insert_timestamp,
1
);
assert_eq!(
dest_crds
.lookup_versioned(&caller.label())

View File

@@ -11,10 +11,10 @@
use crate::{
cluster_info::CRDS_UNIQUE_PUBKEY_CAPACITY,
contact_info::ContactInfo,
crds::{Crds, VersionedCrdsValue},
crds::{Crds, Cursor, VersionedCrdsValue},
crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS},
crds_gossip_error::CrdsGossipError,
crds_value::{CrdsValue, CrdsValueLabel},
crds_value::CrdsValue,
weighted_shuffle::weighted_shuffle,
};
use bincode::serialized_size;
@@ -23,10 +23,11 @@ use itertools::Itertools;
use lru::LruCache;
use rand::{seq::SliceRandom, Rng};
use solana_runtime::bloom::{AtomicBloom, Bloom};
use solana_sdk::{hash::Hash, packet::PACKET_DATA_SIZE, pubkey::Pubkey, timing::timestamp};
use solana_sdk::{packet::PACKET_DATA_SIZE, pubkey::Pubkey, timing::timestamp};
use std::{
cmp,
collections::{HashMap, HashSet},
ops::RangeBounds,
};
pub const CRDS_GOSSIP_NUM_ACTIVE: usize = 30;
@@ -46,8 +47,8 @@ pub struct CrdsGossipPush {
pub max_bytes: usize,
/// active set of validators for push
active_set: IndexMap<Pubkey, AtomicBloom<Pubkey>>,
/// push message queue
push_messages: HashMap<CrdsValueLabel, Hash>,
/// Cursor into the crds table for values to push.
crds_cursor: Cursor,
/// Cache that tracks which validators a message was received from
/// bool indicates it has been pruned.
/// This cache represents a lagging view of which validators
@@ -69,7 +70,7 @@ impl Default for CrdsGossipPush {
// Allow upto 64 Crds Values per PUSH
max_bytes: PACKET_DATA_SIZE * 64,
active_set: IndexMap::new(),
push_messages: HashMap::new(),
crds_cursor: Cursor::default(),
received_cache: HashMap::new(),
last_pushed_to: LruCache::new(CRDS_UNIQUE_PUBKEY_CAPACITY),
num_active: CRDS_GOSSIP_NUM_ACTIVE,
@@ -83,8 +84,9 @@ impl Default for CrdsGossipPush {
}
}
impl CrdsGossipPush {
pub fn num_pending(&self) -> usize {
self.push_messages.len()
pub fn num_pending(&self, crds: &Crds) -> usize {
let mut cursor = self.crds_cursor;
crds.get_entries(&mut cursor).count()
}
fn prune_stake_threshold(self_stake: u64, origin_stake: u64) -> u64 {
@@ -163,6 +165,10 @@ impl CrdsGossipPush {
pruned_peers
}
fn wallclock_window(&self, now: u64) -> impl RangeBounds<u64> {
now.saturating_sub(self.msg_timeout)..=now.saturating_add(self.msg_timeout)
}
/// process a push message to the network
pub fn process_push_message(
&mut self,
@@ -172,39 +178,20 @@ impl CrdsGossipPush {
now: u64,
) -> Result<Option<VersionedCrdsValue>, CrdsGossipError> {
self.num_total += 1;
let range = now.saturating_sub(self.msg_timeout)..=now.saturating_add(self.msg_timeout);
if !range.contains(&value.wallclock()) {
if !self.wallclock_window(now).contains(&value.wallclock()) {
return Err(CrdsGossipError::PushMessageTimeout);
}
let label = value.label();
let origin = label.pubkey();
let origin = value.pubkey();
self.received_cache
.entry(origin)
.or_default()
.entry(*from)
.and_modify(|(_pruned, timestamp)| *timestamp = now)
.or_insert((/*pruned:*/ false, now));
match crds.insert(value, now) {
Err(_) => {
self.num_old += 1;
Err(CrdsGossipError::PushMessageOldVersion)
}
Ok(old) => {
let value_hash = crds.get(&label).unwrap().value_hash;
self.push_messages.insert(label, value_hash);
Ok(old)
}
}
}
/// push pull responses
pub fn push_pull_responses(&mut self, values: Vec<(CrdsValueLabel, Hash, u64)>, now: u64) {
for (label, value_hash, wc) in values {
if now > wc.checked_add(self.msg_timeout).unwrap_or(0) {
continue;
}
self.push_messages.insert(label, value_hash);
}
crds.insert(value, now).map_err(|_| {
self.num_old += 1;
CrdsGossipError::PushMessageOldVersion
})
}
/// New push message to broadcast to peers.
@@ -213,7 +200,6 @@ impl CrdsGossipPush {
/// The list of push messages is created such that all the randomly selected peers have not
/// pruned the source addresses.
pub fn new_push_messages(&mut self, crds: &Crds, now: u64) -> HashMap<Pubkey, Vec<CrdsValue>> {
trace!("new_push_messages {}", self.push_messages.len());
let push_fanout = self.push_fanout.min(self.active_set.len());
if push_fanout == 0 {
return HashMap::default();
@@ -221,22 +207,24 @@ impl CrdsGossipPush {
let mut num_pushes = 0;
let mut num_values = 0;
let mut total_bytes: usize = 0;
let mut labels = vec![];
let mut push_messages: HashMap<Pubkey, Vec<CrdsValue>> = HashMap::new();
let cutoff = now.saturating_sub(self.msg_timeout);
let lookup = |label, &hash| -> Option<&CrdsValue> {
let value = crds.lookup_versioned(label)?;
if value.value_hash != hash || value.value.wallclock() < cutoff {
None
} else {
Some(&value.value)
let wallclock_window = self.wallclock_window(now);
let entries = crds
.get_entries(&mut self.crds_cursor)
.map(|entry| &entry.value)
.filter(|value| wallclock_window.contains(&value.wallclock()));
for value in entries {
let serialized_size = serialized_size(&value).unwrap();
total_bytes = total_bytes.saturating_add(serialized_size as usize);
if total_bytes > self.max_bytes {
break;
}
};
let mut push_value = |origin: Pubkey, value: &CrdsValue| {
//use a consistent index for the same origin so
//the active set learns the MST for that origin
let start = origin.as_ref()[0] as usize;
for i in start..(start + push_fanout) {
num_values += 1;
let origin = value.pubkey();
// Use a consistent index for the same origin so the active set
// learns the MST for that origin.
let offset = origin.as_ref()[0] as usize;
for i in offset..offset + push_fanout {
let index = i % self.active_set.len();
let (peer, filter) = self.active_set.get_index(index).unwrap();
if !filter.contains(&origin) || value.should_force_push(peer) {
@@ -245,27 +233,9 @@ impl CrdsGossipPush {
num_pushes += 1;
}
}
};
for (label, hash) in &self.push_messages {
match lookup(label, hash) {
None => labels.push(label.clone()),
Some(value) if value.wallclock() > now => continue,
Some(value) => {
total_bytes += serialized_size(value).unwrap() as usize;
if total_bytes > self.max_bytes {
break;
}
num_values += 1;
labels.push(label.clone());
push_value(label.pubkey(), value);
}
}
}
self.num_pushes += num_pushes;
trace!("new_push_messages {} {}", num_values, self.active_set.len());
for label in labels {
self.push_messages.remove(&label);
}
for target_pubkey in push_messages.keys().copied() {
self.last_pushed_to.put(target_pubkey, now);
}
@@ -400,15 +370,6 @@ impl CrdsGossipPush {
.collect()
}
/// purge old pending push messages
pub fn purge_old_pending_push_messages(&mut self, crds: &Crds, min_time: u64) {
self.push_messages.retain(|k, hash| {
matches!(crds.lookup_versioned(k), Some(versioned) if
versioned.value.wallclock() >= min_time
&& versioned.value_hash == *hash)
});
}
/// purge received push message cache
pub fn purge_old_received_cache(&mut self, min_time: u64) {
self.received_cache.retain(|_, v| {
@@ -430,7 +391,6 @@ impl CrdsGossipPush {
}
Self {
active_set,
push_messages: self.push_messages.clone(),
received_cache: self.received_cache.clone(),
last_pushed_to,
..*self
@@ -879,7 +839,6 @@ mod test {
push.process_push_message(&mut crds, &Pubkey::default(), new_msg, 1),
Ok(None)
);
push.purge_old_pending_push_messages(&crds, 0);
assert_eq!(push.new_push_messages(&crds, 0), expected);
}

View File

@@ -378,7 +378,8 @@ impl ReplayStage {
&my_pubkey,
&vote_account,
&mut progress,
transaction_status_sender.clone(),
transaction_status_sender.as_ref(),
cache_block_time_sender.as_ref(),
&verify_recyclers,
&mut heaviest_subtree_fork_choice,
&replay_vote_sender,
@@ -576,7 +577,6 @@ impl ReplayStage {
&subscriptions,
&block_commitment_cache,
&mut heaviest_subtree_fork_choice,
&cache_block_time_sender,
&bank_notification_sender,
&mut gossip_duplicate_confirmed_slots,
&mut unfrozen_gossip_verified_vote_hashes,
@@ -1216,7 +1216,7 @@ impl ReplayStage {
bank: &Arc<Bank>,
blockstore: &Blockstore,
bank_progress: &mut ForkProgress,
transaction_status_sender: Option<TransactionStatusSender>,
transaction_status_sender: Option<&TransactionStatusSender>,
replay_vote_sender: &ReplayVoteSender,
verify_recyclers: &VerifyRecyclers,
) -> result::Result<usize, BlockstoreProcessorError> {
@@ -1323,7 +1323,6 @@ impl ReplayStage {
subscriptions: &Arc<RpcSubscriptions>,
block_commitment_cache: &Arc<RwLock<BlockCommitmentCache>>,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
cache_block_time_sender: &Option<CacheBlockTimeSender>,
bank_notification_sender: &Option<BankNotificationSender>,
gossip_duplicate_confirmed_slots: &mut GossipDuplicateConfirmedSlots,
unfrozen_gossip_verified_vote_hashes: &mut UnfrozenGossipVerifiedVoteHashes,
@@ -1360,12 +1359,6 @@ impl ReplayStage {
blockstore
.set_roots(&rooted_slots)
.expect("Ledger set roots failed");
Self::cache_block_times(
blockstore,
bank_forks,
&rooted_slots,
cache_block_time_sender,
);
let highest_confirmed_root = Some(
block_commitment_cache
.read()
@@ -1659,7 +1652,8 @@ impl ReplayStage {
my_pubkey: &Pubkey,
vote_account: &Pubkey,
progress: &mut ProgressMap,
transaction_status_sender: Option<TransactionStatusSender>,
transaction_status_sender: Option<&TransactionStatusSender>,
cache_block_time_sender: Option<&CacheBlockTimeSender>,
verify_recyclers: &VerifyRecyclers,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
replay_vote_sender: &ReplayVoteSender,
@@ -1723,7 +1717,7 @@ impl ReplayStage {
&bank,
&blockstore,
bank_progress,
transaction_status_sender.clone(),
transaction_status_sender,
replay_vote_sender,
verify_recyclers,
);
@@ -1758,7 +1752,7 @@ impl ReplayStage {
);
did_complete_bank = true;
info!("bank frozen: {}", bank.slot());
if let Some(transaction_status_sender) = transaction_status_sender.clone() {
if let Some(transaction_status_sender) = transaction_status_sender {
transaction_status_sender.send_transaction_status_freeze_message(&bank);
}
bank.freeze();
@@ -1784,6 +1778,7 @@ impl ReplayStage {
.send(BankNotification::Frozen(bank.clone()))
.unwrap_or_else(|err| warn!("bank_notification_sender failed: {:?}", err));
}
blockstore_processor::cache_block_time(&bank, cache_block_time_sender);
let bank_hash = bank.hash();
if let Some(new_frozen_voters) =
@@ -2477,36 +2472,6 @@ impl ReplayStage {
}
}
fn cache_block_times(
blockstore: &Arc<Blockstore>,
bank_forks: &Arc<RwLock<BankForks>>,
rooted_slots: &[Slot],
cache_block_time_sender: &Option<CacheBlockTimeSender>,
) {
if let Some(cache_block_time_sender) = cache_block_time_sender {
for slot in rooted_slots {
if blockstore
.get_block_time(*slot)
.unwrap_or_default()
.is_none()
{
if let Some(rooted_bank) = bank_forks.read().unwrap().get(*slot) {
cache_block_time_sender
.send(rooted_bank.clone())
.unwrap_or_else(|err| {
warn!("cache_block_time_sender failed: {:?}", err)
});
} else {
error!(
"rooted_bank {:?} not available in BankForks; block time not cached",
slot
);
}
}
}
}
}
pub fn get_unlock_switch_vote_slot(cluster_type: ClusterType) -> Slot {
match cluster_type {
ClusterType::Development => 0,
@@ -2531,6 +2496,7 @@ pub(crate) mod tests {
cluster_info::Node,
consensus::test::{initialize_state, VoteSimulator},
consensus::Tower,
crds::Cursor,
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
progress_map::ValidatorStakeInfo,
replay_stage::ReplayStage,
@@ -3403,7 +3369,7 @@ pub(crate) mod tests {
&bank,
&mut entries,
true,
Some(TransactionStatusSender {
Some(&TransactionStatusSender {
sender: transaction_status_sender,
enable_cpi_and_log_storage: false,
}),
@@ -4823,7 +4789,8 @@ pub(crate) mod tests {
&mut voted_signatures,
has_new_vote_been_rooted,
);
let (_, votes, max_ts) = cluster_info.get_votes(0);
let mut cursor = Cursor::default();
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes.len(), 1);
let vote_tx = &votes[0];
assert_eq!(vote_tx.message.recent_blockhash, bank0.last_blockhash());
@@ -4852,7 +4819,7 @@ pub(crate) mod tests {
);
// No new votes have been submitted to gossip
let (_, votes, _max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert!(votes.is_empty());
// Tower's latest vote tx blockhash hasn't changed either
assert_eq!(tower.last_vote_tx_blockhash(), bank0.last_blockhash());
@@ -4873,7 +4840,7 @@ pub(crate) mod tests {
&mut voted_signatures,
has_new_vote_been_rooted,
);
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes.len(), 1);
let vote_tx = &votes[0];
assert_eq!(vote_tx.message.recent_blockhash, bank1.last_blockhash());
@@ -4895,7 +4862,7 @@ pub(crate) mod tests {
&mut last_vote_refresh_time,
);
// No new votes have been submitted to gossip
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert!(votes.is_empty());
assert_eq!(tower.last_vote_tx_blockhash(), bank1.last_blockhash());
assert_eq!(tower.last_voted_slot().unwrap(), 1);
@@ -4931,7 +4898,7 @@ pub(crate) mod tests {
&mut last_vote_refresh_time,
);
assert!(last_vote_refresh_time.last_refresh_time > clone_refresh_time);
let (_, votes, max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert_eq!(votes.len(), 1);
let vote_tx = &votes[0];
assert_eq!(
@@ -4986,7 +4953,7 @@ pub(crate) mod tests {
has_new_vote_been_rooted,
&mut last_vote_refresh_time,
);
let (_, votes, _max_ts) = cluster_info.get_votes(max_ts);
let (_, votes) = cluster_info.get_votes(&mut cursor);
assert!(votes.is_empty());
assert_eq!(
vote_tx.message.recent_blockhash,

View File

@@ -693,7 +693,7 @@ mod tests {
..ProcessOptions::default()
};
let (bank_forks, cached_leader_schedule) =
process_blockstore(&genesis_config, &blockstore, Vec::new(), opts).unwrap();
process_blockstore(&genesis_config, &blockstore, Vec::new(), opts, None).unwrap();
let leader_schedule_cache = Arc::new(cached_leader_schedule);
let bank_forks = Arc::new(RwLock::new(bank_forks));

View File

@@ -45,7 +45,7 @@ use solana_metrics::inc_new_counter_info;
use solana_perf::packet::PACKET_DATA_SIZE;
use solana_runtime::{
accounts::AccountAddressFilter,
accounts_index::{AccountIndex, IndexKey},
accounts_index::{AccountIndex, AccountSecondaryIndexes, IndexKey},
bank::Bank,
bank_forks::{BankForks, SnapshotConfig},
commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots},
@@ -124,7 +124,7 @@ pub struct JsonRpcConfig {
pub enable_bigtable_ledger_storage: bool,
pub enable_bigtable_ledger_upload: bool,
pub max_multiple_accounts: Option<usize>,
pub account_indexes: HashSet<AccountIndex>,
pub account_indexes: AccountSecondaryIndexes,
pub rpc_threads: usize,
pub rpc_bigtable_timeout: Option<Duration>,
pub minimal_api: bool,
@@ -358,11 +358,11 @@ impl JsonRpcRequestProcessor {
check_slice_and_encoding(&encoding, data_slice_config.is_some())?;
let keyed_accounts = {
if let Some(owner) = get_spl_token_owner_filter(program_id, &filters) {
self.get_filtered_spl_token_accounts_by_owner(&bank, &owner, filters)
self.get_filtered_spl_token_accounts_by_owner(&bank, &owner, filters)?
} else if let Some(mint) = get_spl_token_mint_filter(program_id, &filters) {
self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)
self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)?
} else {
self.get_filtered_program_accounts(&bank, program_id, filters)
self.get_filtered_program_accounts(&bank, program_id, filters)?
}
};
let result =
@@ -1558,7 +1558,7 @@ impl JsonRpcRequestProcessor {
));
}
let mut token_balances: Vec<RpcTokenAccountBalance> = self
.get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![])
.get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![])?
.into_iter()
.map(|(address, account)| {
let amount = TokenAccount::unpack(&account.data())
@@ -1606,7 +1606,8 @@ impl JsonRpcRequestProcessor {
}));
}
let keyed_accounts = self.get_filtered_spl_token_accounts_by_owner(&bank, owner, filters);
let keyed_accounts =
self.get_filtered_spl_token_accounts_by_owner(&bank, owner, filters)?;
let accounts = if encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect()
} else {
@@ -1658,13 +1659,13 @@ impl JsonRpcRequestProcessor {
];
// Optional filter on Mint address, uses mint account index for scan
let keyed_accounts = if let Some(mint) = mint {
self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)
self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)?
} else {
// Filter on Token Account state
filters.push(RpcFilterType::DataSize(
TokenAccount::get_packed_len() as u64
));
self.get_filtered_program_accounts(&bank, &token_program_id, filters)
self.get_filtered_program_accounts(&bank, &token_program_id, filters)?
};
let accounts = if encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect()
@@ -1692,7 +1693,7 @@ impl JsonRpcRequestProcessor {
bank: &Arc<Bank>,
program_id: &Pubkey,
filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> {
) -> Result<Vec<(Pubkey, AccountSharedData)>> {
let filter_closure = |account: &AccountSharedData| {
filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
@@ -1704,16 +1705,24 @@ impl JsonRpcRequestProcessor {
.account_indexes
.contains(&AccountIndex::ProgramId)
{
bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| {
// The program-id account index checks for Account owner on inclusion. However, due
// to the current AccountsDb implementation, an account may remain in storage as a
// zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later
// updates. We include the redundant filters here to avoid returning these
// accounts.
account.owner == *program_id && filter_closure(account)
})
if !self.config.account_indexes.include_key(program_id) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: program_id.to_string(),
}
.into());
}
Ok(
bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| {
// The program-id account index checks for Account owner on inclusion. However, due
// to the current AccountsDb implementation, an account may remain in storage as a
// zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later
// updates. We include the redundant filters here to avoid returning these
// accounts.
account.owner == *program_id && filter_closure(account)
}),
)
} else {
bank.get_filtered_program_accounts(program_id, filter_closure)
Ok(bank.get_filtered_program_accounts(program_id, filter_closure))
}
}
@@ -1723,7 +1732,7 @@ impl JsonRpcRequestProcessor {
bank: &Arc<Bank>,
owner_key: &Pubkey,
mut filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> {
) -> Result<Vec<(Pubkey, AccountSharedData)>> {
// The by-owner accounts index checks for Token Account state and Owner address on
// inclusion. However, due to the current AccountsDb implementation, an account may remain
// in storage as a zero-lamport AccountSharedData::Default() after being wiped and reinitialized in
@@ -1745,13 +1754,22 @@ impl JsonRpcRequestProcessor {
.account_indexes
.contains(&AccountIndex::SplTokenOwner)
{
bank.get_filtered_indexed_accounts(&IndexKey::SplTokenOwner(*owner_key), |account| {
account.owner == spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
})
})
if !self.config.account_indexes.include_key(owner_key) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: owner_key.to_string(),
}
.into());
}
Ok(bank.get_filtered_indexed_accounts(
&IndexKey::SplTokenOwner(*owner_key),
|account| {
account.owner == spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
})
},
))
} else {
self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters)
}
@@ -1763,7 +1781,7 @@ impl JsonRpcRequestProcessor {
bank: &Arc<Bank>,
mint_key: &Pubkey,
mut filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> {
) -> Result<Vec<(Pubkey, AccountSharedData)>> {
// The by-mint accounts index checks for Token Account state and Mint address on inclusion.
// However, due to the current AccountsDb implementation, an account may remain in storage
// as be zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later
@@ -1784,13 +1802,21 @@ impl JsonRpcRequestProcessor {
.account_indexes
.contains(&AccountIndex::SplTokenMint)
{
bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| {
account.owner == spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
})
})
if !self.config.account_indexes.include_key(mint_key) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: mint_key.to_string(),
}
.into());
}
Ok(
bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| {
account.owner == spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
})
}),
)
} else {
self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters)
}
@@ -2818,6 +2844,7 @@ pub mod rpc_full {
rpc: valid_address_or_none(&contact_info.rpc),
version,
feature_set,
shred_version: Some(my_shred_version),
})
} else {
None // Exclude spy nodes
@@ -3351,9 +3378,9 @@ pub mod rpc_full {
let mut slot = first_slot;
for identity in slot_leaders {
slot += 1;
if let Some(ref filter_by_identity) = filter_by_identity {
if identity != *filter_by_identity {
slot += 1;
continue;
}
}
@@ -3363,6 +3390,7 @@ pub mod rpc_full {
entry.1 += 1; // Increment blocks_produced
}
entry.0 += 1; // Increment leader_slots
slot += 1;
}
Ok(new_response(
@@ -3894,7 +3922,7 @@ pub mod tests {
.expect("actual response deserialization");
let expected = format!(
r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null, "featureSet": null}}],"id":1}}"#,
r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "shredVersion": 0, "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null, "featureSet": null}}],"id":1}}"#,
leader_pubkey,
rpc_port::DEFAULT_RPC_PORT
);
@@ -5787,6 +5815,83 @@ pub mod tests {
assert_eq!(confirmed_block.rewards.unwrap(), vec![]);
}
#[test]
fn test_get_block_production() {
let bob_pubkey = solana_sdk::pubkey::new_rand();
let roots = vec![0, 1, 3, 4, 8];
let RpcHandler {
io,
meta,
block_commitment_cache,
leader_pubkey,
..
} = start_rpc_handler_with_tx_and_blockstore(&bob_pubkey, roots);
block_commitment_cache
.write()
.unwrap()
.set_highest_confirmed_root(8);
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getBlockProduction","params":[]}"#;
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let block_production: RpcBlockProduction =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(
block_production.by_identity.get(&leader_pubkey.to_string()),
Some(&(9, 5))
);
assert_eq!(
block_production.range,
RpcBlockProductionRange {
first_slot: 0,
last_slot: 8
}
);
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockProduction","params":[{{"identity": "{}"}}]}}"#,
leader_pubkey
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let block_production: RpcBlockProduction =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(
block_production.by_identity.get(&leader_pubkey.to_string()),
Some(&(9, 5))
);
assert_eq!(
block_production.range,
RpcBlockProductionRange {
first_slot: 0,
last_slot: 8
}
);
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockProduction","params":[{{"range": {{"firstSlot": 0, "lastSlot": 4}}, "identity": "{}"}}]}}"#,
bob_pubkey
);
let res = io.handle_request_sync(&req, meta);
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let block_production: RpcBlockProduction =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(
block_production.by_identity.get(&leader_pubkey.to_string()),
None
);
assert_eq!(
block_production.range,
RpcBlockProductionRange {
first_slot: 0,
last_slot: 4
}
);
}
#[test]
fn test_get_confirmed_blocks() {
let bob_pubkey = solana_sdk::pubkey::new_rand();

View File

@@ -46,7 +46,7 @@ use solana_ledger::{
use solana_measure::measure::Measure;
use solana_metrics::datapoint_info;
use solana_runtime::{
accounts_index::AccountIndex,
accounts_index::AccountSecondaryIndexes,
bank::Bank,
bank_forks::{BankForks, SnapshotConfig},
commitment::BlockCommitmentCache,
@@ -115,6 +115,7 @@ pub struct ValidatorConfig {
pub poh_verify: bool, // Perform PoH verification during blockstore processing at boo
pub cuda: bool,
pub require_tower: bool,
pub tower_path: Option<PathBuf>,
pub debug_keys: Option<Arc<HashSet<Pubkey>>>,
pub contact_debug_interval: u64,
pub contact_save_interval: u64,
@@ -124,7 +125,7 @@ pub struct ValidatorConfig {
pub no_poh_speed_test: bool,
pub poh_pinned_cpu_core: usize,
pub poh_hashes_per_batch: u64,
pub account_indexes: HashSet<AccountIndex>,
pub account_indexes: AccountSecondaryIndexes,
pub accounts_db_caching_enabled: bool,
pub warp_slot: Option<Slot>,
pub accounts_db_test_hash_calculation: bool,
@@ -170,6 +171,7 @@ impl Default for ValidatorConfig {
poh_verify: true,
cuda: false,
require_tower: false,
tower_path: None,
debug_keys: None,
contact_debug_interval: DEFAULT_CONTACT_DEBUG_INTERVAL_MILLIS,
contact_save_interval: DEFAULT_CONTACT_SAVE_INTERVAL_MILLIS,
@@ -179,7 +181,7 @@ impl Default for ValidatorConfig {
no_poh_speed_test: true,
poh_pinned_cpu_core: poh_service::DEFAULT_PINNED_CPU_CORE,
poh_hashes_per_batch: poh_service::DEFAULT_HASHES_PER_BATCH,
account_indexes: HashSet::new(),
account_indexes: AccountSecondaryIndexes::default(),
accounts_db_caching_enabled: false,
warp_slot: None,
accounts_db_test_hash_calculation: false,
@@ -932,7 +934,7 @@ fn post_process_restored_tower(
validator_identity: &Pubkey,
vote_account: &Pubkey,
config: &ValidatorConfig,
ledger_path: &Path,
tower_path: &Path,
bank_forks: &BankForks,
) -> Tower {
let mut should_require_tower = config.require_tower;
@@ -1011,7 +1013,7 @@ fn post_process_restored_tower(
Tower::new_from_bankforks(
&bank_forks,
&ledger_path,
tower_path,
&validator_identity,
&vote_account,
)
@@ -1074,7 +1076,9 @@ fn new_banks_from_ledger(
.expect("Failed to open ledger database");
blockstore.set_no_compaction(config.no_rocksdb_compaction);
let restored_tower = Tower::restore(ledger_path, &validator_identity);
let tower_path = config.tower_path.as_deref().unwrap_or(ledger_path);
let restored_tower = Tower::restore(tower_path, &validator_identity);
if let Ok(tower) = &restored_tower {
reconcile_blockstore_roots_with_tower(&tower, &blockstore).unwrap_or_else(|err| {
error!("Failed to reconcile blockstore with tower: {:?}", err);
@@ -1115,7 +1119,10 @@ fn new_banks_from_ledger(
process_options,
transaction_history_services
.transaction_status_sender
.clone(),
.as_ref(),
transaction_history_services
.cache_block_time_sender
.as_ref(),
)
.unwrap_or_else(|err| {
error!("Failed to load ledger: {:?}", err);
@@ -1172,7 +1179,7 @@ fn new_banks_from_ledger(
&validator_identity,
&vote_account,
&config,
&ledger_path,
tower_path,
&bank_forks,
);

View File

@@ -415,7 +415,10 @@ fn network_run_push(
}
total = network_values
.par_iter()
.map(|v| v.lock().unwrap().push.num_pending())
.map(|node| {
let gossip = node.gossip.lock().unwrap();
gossip.push.num_pending(&gossip.crds)
})
.sum();
trace!(
"network_run_push_{}: now: {} queue: {} bytes: {} num_msgs: {} prunes: {} stake_pruned: {} delivered: {}",

View File

@@ -3,8 +3,11 @@
extern crate log;
use rayon::iter::*;
use solana_core::cluster_info::{ClusterInfo, Node};
use solana_core::gossip_service::GossipService;
use solana_core::{
cluster_info::{ClusterInfo, Node},
crds::Cursor,
gossip_service::GossipService,
};
use solana_runtime::bank_forks::BankForks;
use solana_perf::packet::Packet;
@@ -305,12 +308,11 @@ pub fn cluster_info_scale() {
let mut num_push_total = 0;
let mut num_pushes = 0;
let mut num_pulls = 0;
let mut num_inserts = 0;
for node in nodes.iter() {
//if node.0.get_votes(0).1.len() != (num_nodes * num_votes) {
let has_tx = node
.0
.get_votes(0)
.get_votes(&mut Cursor::default())
.1
.iter()
.filter(|v| v.message.account_keys == tx.message.account_keys)
@@ -319,7 +321,6 @@ pub fn cluster_info_scale() {
num_push_total += node.0.gossip.read().unwrap().push.num_total;
num_pushes += node.0.gossip.read().unwrap().push.num_pushes;
num_pulls += node.0.gossip.read().unwrap().pull.num_pulls;
num_inserts += node.0.gossip.read().unwrap().crds.num_inserts;
if has_tx == 0 {
not_done += 1;
}
@@ -329,7 +330,6 @@ pub fn cluster_info_scale() {
warn!("num_push_total: {}", num_push_total);
warn!("num_pushes: {}", num_pushes);
warn!("num_pulls: {}", num_pulls);
warn!("num_inserts: {}", num_inserts);
success = not_done < (nodes.len() / 20);
if success {
break;
@@ -347,7 +347,6 @@ pub fn cluster_info_scale() {
node.0.gossip.write().unwrap().push.num_total = 0;
node.0.gossip.write().unwrap().push.num_pushes = 0;
node.0.gossip.write().unwrap().pull.num_pulls = 0;
node.0.gossip.write().unwrap().crds.num_inserts = 0;
}
}

View File

@@ -47,6 +47,7 @@ mod tests {
use solana_runtime::{
accounts_background_service::{AbsRequestSender, SnapshotRequestHandler},
accounts_db,
accounts_index::AccountSecondaryIndexes,
bank::{Bank, BankSlotDelta},
bank_forks::{ArchiveFormat, BankForks, SnapshotConfig},
genesis_utils::{create_genesis_config, GenesisConfigInfo},
@@ -106,7 +107,7 @@ mod tests {
&[],
None,
None,
HashSet::new(),
AccountSecondaryIndexes::default(),
false,
);
bank0.freeze();
@@ -163,7 +164,7 @@ mod tests {
old_genesis_config,
None,
None,
HashSet::new(),
AccountSecondaryIndexes::default(),
false,
)
.unwrap();

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-crate-features"
version = "1.6.7"
version = "1.6.9"
description = "Solana Crate Features"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"

View File

@@ -19,6 +19,10 @@ module.exports = {
crossorigin: "anonymous",
},
],
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh'],
},
themeConfig: {
navbar: {
logo: {
@@ -52,6 +56,10 @@ module.exports = {
label: "Learn",
position: "left",
},
{
type: 'localeDropdown',
position: 'right',
},
{
href: "https://discordapp.com/invite/pquxPsq",
label: "Chat",

50
docs/i18n/zh/code.json Normal file
View File

@@ -0,0 +1,50 @@
{
"⛏ Start Building": {
"message": "⛏ 开始开发",
"description": "start-building"
},
"Get started building your decentralized app or marketplace.": {
"message": "开始开发您的去中心化应用或市场。",
"description": "get-started-building"
},
"🎛 Run a Validator Node": {
"message": "🎛 运行一个验证节点",
"description": "run-validator"
},
"Validate transactions, secure the network, and earn rewards.": {
"message": "验证交易,保护网络并获得奖励。",
"description": "validate-transactions"
},
"🏛 Create an SPL Token": {
"message": "🏛 创建一个 SPL 代币",
"description": "create-spl"
},
"Launch your own SPL Token, Solana's equivalent of ERC-20.": {
"message": "启动您自己的 SPL 代币,类似于 Solana 区块链的 ERC-20。",
"description": "erc-20"
},
"🏦 Integrate an Exchange": {
"message": "🏦 集成一个交易所。",
"description": "integrate-exchange"
},
"Follow our extensive integration guide to ensure a seamless user experience.": {
"message": "请遵循我们通用集成指南,来确保用户体验。",
"description": "integration-guide"
},
"📲 Manage a Wallet": {
"message": "📲 管理钱包",
"description": "manage-wallet"
},
"Create a wallet, check your balance, and learn about wallet options.": {
"message": "创建一个钱包,检查余额,了解钱包选项。",
"description": "wallet-options"
},
"🤯 Learn How Solana Works": {
"message": "🤯 了解 Solana 工作原理",
"description": "learn-how"
},
"Get a high-level understanding of Solana's architecture.": {
"message": "对 Solana 架构进行深入了解。",
"description": "high-level"
}
}

View File

@@ -0,0 +1,110 @@
{
"version.label": {
"message": "下一步",
"description": "The label for version current"
},
"sidebar.docs.category.About": {
"message": "关于",
"description": "The label for category About in sidebar docs"
},
"sidebar.docs.category.Wallets": {
"message": "钱包",
"description": "The label for category Wallets in sidebar docs"
},
"sidebar.docs.category.Web Wallets": {
"message": "网页钱包",
"description": "The label for category Web Wallets in sidebar docs"
},
"sidebar.docs.category.Hardware Wallets": {
"message": "硬件钱包",
"description": "The label for category Hardware Wallets in sidebar docs"
},
"sidebar.docs.category.Command-line Wallets": {
"message": "命令行钱包",
"description": "The label for category Command-line Wallets in sidebar docs"
},
"sidebar.docs.category.Staking": {
"message": "质押",
"description": "The label for category Staking in sidebar docs"
},
"sidebar.docs.category.Command Line": {
"message": "命令行",
"description": "The label for category Command Line in sidebar docs"
},
"sidebar.docs.category.Developing": {
"message": "开发中",
"description": "The label for category Developing in sidebar docs"
},
"sidebar.docs.category.Programming Model": {
"message": "编程模型",
"description": "The label for category Programming Model in sidebar docs"
},
"sidebar.docs.category.Clients": {
"message": "客户端",
"description": "The label for category Clients in sidebar docs"
},
"sidebar.docs.category.Builtins": {
"message": "内置模式",
"description": "The label for category Builtins in sidebar docs"
},
"sidebar.docs.category.Deployed Programs": {
"message": "部署程序",
"description": "The label for category Deployed Programs in sidebar docs"
},
"sidebar.docs.category.Integrating": {
"message": "集成",
"description": "The label for category Integrating in sidebar docs"
},
"sidebar.docs.category.Validating": {
"message": "验证",
"description": "The label for category Validating in sidebar docs"
},
"sidebar.docs.category.Incenvitized Testnet": {
"message": "激励测试网",
"description": "The label for category Incenvitized Testnet in sidebar docs"
},
"sidebar.docs.category.Registration": {
"message": "注册",
"description": "The label for category Registration in sidebar docs"
},
"sidebar.docs.category.Participation": {
"message": "参与",
"description": "The label for category Participation in sidebar docs"
},
"sidebar.docs.category.Clusters": {
"message": "集群",
"description": "The label for category Clusters in sidebar docs"
},
"sidebar.docs.category.Architecture": {
"message": "架构",
"description": "The label for category Architecture in sidebar docs"
},
"sidebar.docs.category.Cluster": {
"message": "集群",
"description": "The label for category Cluster in sidebar docs"
},
"sidebar.docs.category.Validator": {
"message": "验证节点",
"description": "The label for category Validator in sidebar docs"
},
"sidebar.docs.category.Design Proposals": {
"message": "设计提案",
"description": "The label for category Design Proposals in sidebar docs"
},
"sidebar.docs.category.Implemented": {
"message": "实现",
"description": "The label for category Implemented in sidebar docs"
},
"sidebar.docs.category.Economic Design": {
"message": "经济设计",
"description": "The label for category Economic Design in sidebar docs"
},
"sidebar.docs.category.Validation Client Economics": {
"message": "验证客户端经济学",
"description": "The label for category Validation Client Economics in sidebar docs"
},
"sidebar.docs.category.Accepted": {
"message": "已接受",
"description": "The label for category Accepted in sidebar docs"
}
}

View File

@@ -0,0 +1,16 @@
---
title: 命令行指南
---
在本节中我们将描述如何使用Solana命令行工具创建_钱包_来发送和接收SOL代币以及通过委托质押来参与到集群中。
为了与Solana集群进行交互我们需要使用其命令行界面也称为CLI。 命令行是Solana核心团队首次部署新功能所用到的工具。 命令行界面不一定是最容易使用的但它提供了对Solana帐户的最直接、灵活和安全的访问。
## 准备工作
要开始使用 Solana 命令行 (CLI) 工具:
- [安装 Solana 工具](cli/install-solana-cli-tools.md)
- [选择一个集群](cli/choose-a-cluster.md)
- [创建一个钱包](wallet-guide/cli.md)
- [查看 CLI 协议](cli/conventions.md)

View File

@@ -0,0 +1,37 @@
---
title: 连接到一个集群
---
查看 [Solana 集群](../clusters.md) 获取关于可用集群的通用信息。
## 配置命令行工具
您可以通过下述指令来检查集群正在运行的 Solana 命令行工具 (CLI)
```bash
solana config get
```
通过 `solana config set` 命令来制定某个集群。 设置一个目标集群后,未来的任何子命令都会从该集群发送/接收信息。
例如,要指定 Devnet 集群,请运行:
```bash
solana config set --url https://api.devnet.solana.com
```
## 确保版本相匹配
虽然严格来说没有必要,但是当 CLI 版本与运行在集群中的软件版本相匹配时,一般来说 CLI 版本能够发挥最大作用。 查看本地安装的 CLI 版本,请运行:
```bash
solana --version
```
查看集群版本,请运行:
```bash
solana cluster-version
```
确保本地的 CLI 版本比群集版本新或者至少相同。

View File

@@ -0,0 +1,59 @@
---
title: 使用 Solana CLI 命令
---
在运行 Solana CLI 命令之前,让我们先来熟悉一下所有的命令。 首先Solana CLI 实际上是你可能会用到操作的命令集合。 您可以通过运行以下操作查看所有可能命令的列表:
```bash
solana --help
```
需要研究特定的命令,请运行:
```bash
solana <COMMAND> --help
```
您可以将文本 `<COMMAND>` 替换为你想了解的命令名称。
命令使用信息通常包含诸如 `<AMOUNT>``<ACCOUNT_ADDRESS>``<KEYPAIR>` 等字段。 每个字段都是 _type_ 文本的占位符,您可以使用它执行命令。 例如,您可以将 `<AMOUNT>` 替换为诸如 `42``100.42` 等数字。 您可以将 `<ACCOUNT_ADDRESS>` 替换为公钥的 base58 编码,例如 `9grmKMwTiZwUHSExjtbFzHLPTdWoXgcg1bZkhvwTrTww`
## 密钥对协议
许多使用 CLI 工具的命令需要一个 `<KEYPAIR>` 值。 密钥对应使用的值取决于您创建的 [命令行钱包的类型](../wallet-guide/cli.md)。
例如,显示钱包地址(也称为密钥)CLI 帮助文档显示:
```bash
solana-keygen pubkey <KEYPAIR>
```
下面我们来展示根据钱包类型来解决应该在 `<KEYPAIR>` 中插入什么的问题。
#### 纸钱包
在纸质钱包中,密钥对源自助记词和你在钱包创建时输入的可选密码。 若要让纸钱包密钥在任意地方显示 `<KEYPAIR>` 文本在示例或帮助文档中,请输入单词 `ASK`,然后程序会强制您在运行命令时输入种子单词。
显示纸钱包的钱包地址:
```bash
solana-keygen pubkey
```
#### 文件系统钱包
有了一个文件系统钱包,密钥对就会存储在您的计算机上的文件中。 将 `<KEYPAIR>` 替换为对密钥文件的完整文件路径。
例如,如果文件系统密钥对文件位置是 `/home/solana/my_wallet.json`,请输入来显示地址:
```bash
solana-keygen pubkey /home/solana/my_wallet.json
```
#### 硬软件钱包
如果您选择了硬件钱包,请使用您的 [密钥对链接](../wallet-guide/hardware-wallets.md#specify-a-hardware-wallet-key),比如 `blap://ledger?key=0`
```bash
solana-keygen pubkey usb://ledger?key=0
```

View File

@@ -0,0 +1,149 @@
---
title: 委托您的质押
---
通过 [ 获取 SOL ](transfer-tokens.md) 以后,您可以通过 _stake_ 将它委托给一个验证节点。 质押Stake就是在 _stake account_ 中的代币。 Solana 根据质押权重为验证节点分配投票权重,权重会影响它们在区块链中决定下一个有效交易区块。 然后 Solana 会按周期生成新的 SOL 来奖励质押者和验证节点。 您委托的代币越多,获得的奖励就越高。
## 创建一个质押账户
要委托代币,您首先要将代币转入一个质押帐户。 而要创建一个帐户,您需要一个密钥对: 它的公钥将作为 [质押账户地址](../staking/stake-accounts.md#account-address)。 此处无需密码或加密;此密钥对将在创建密钥账户后被丢弃。
```bash
solana-keygen new --no-passphrase -o stake-account.json
```
输出结果将在文本 `pubkey:` 后面包括该地址。
```text
pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
```
复制公钥并将它安全地存储起来。 在后续创建质押账户的操作中您将随时需要用到它。
创建一个质押账户:
```bash
solana create-stake-account --from <KEYPAIR> stake-account.json <AMOUNT> \
--stake-authority <KEYPAIR> --withdraw-authority <KEYPAIR> \
--fee-payer <KEYPAIR>
```
`<AMOUNT>` 的代币从 `<KEYPAIR>` 转到了 stake-account.json 公钥的一个新质押账户。
现在可以丢弃 stake-account.json 文件了。 要授权额外的操作,您可以通过 `--stake-authority``--rap-authority` 密钥对,而无需使用 stak-account.json。
使用 `solana stake-account` 命令查看新的质押账户:
```bash
solana stake-account <STAKE_ACCOUNT_ADDRESS>
```
结果大概呈这样:
```text
Total Stake: 5000 SOL
Stake account is undelegated
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
```
### 设置质押和取款权限
创建账号时,如果需要设置 [质押和提现权限](../staking/stake-accounts.md#understanding-account-authorities),您可以通过 `--stake-authority` and `--withdraw-authority` 选项或 `solana stake-authorize` 命令来实现。 例如,要设置一个新的质押权限,请运行:
```bash
solana stake-authorize <STAKE_ACCOUNT_ADDRESS> \
--stake-authority <KEYPAIR> --new-stake-authority <PUBKEY> \
--fee-payer <KEYPAIR>
```
这将针对已有的质押账号 `<STAKE_ACCOUNT_ADDRESS>`,通过现有的质押权限 `<KEYPAIR>` 来授权一个新的质押权限 `<PUBKEY>`
### 高级功能:派生质押账户地址
当委托质押时,你需要将所有密钥账户中的代币委托给某一个验证节点。 而要委托给多个验证节点,您就需要多个质押账户。 为每个帐户创建一个新密钥对并管理那些地址可能比较繁琐。 好在您可以通过 `--seed` 选项来派生多个质押地址:
```bash
solana create-stake-account --from <KEYPAIR> <STAKE_ACCOUNT_KEYPAIR> --seed <STRING> <AMOUNT> \
--stake-authority <PUBKEY> --withdraw-authority <PUBKEY> --fee-payer <KEYPAIR>
```
`<STRING>` 是一个最多32字节的任意字符串通常情况下是一个对应该派生账户的数字。 第一个账户是"0",第二个是 "1",以此类推。 `<STAKE_ACCOUNT_KEYPAIR>` 公钥发挥基本地址的作用。 该命令将从基础地址和种子字符串中派生一个新地址。 要查看派生出哪个质押地址,请使用 `solana create-address-with-seed`命令:
```bash
solana create-address-with-seed --from <PUBKEY> <SEED_STRING> STAKE
```
`<PUBKEY>` is the public key of the `<STAKE_ACCOUNT_KEYPAIR>` passed to `solana create-stake-account`.
该命令将输出派生地址,可以用于质押操作中的 `<STAKE_ACCOUNT_ADDRESS>` 参数。
## 委托您的质押
想要委托您的质押给某个验证节点,您首先需要它的投票帐号地址。 您可以通过 `solana validators` 命令来查询所有验证节点列表和他们的投票账户:
```bash
solana 验证节点
```
每行的第一列包含验证节点的身份,第二列是投票帐户地址。 选择一个验证节点,并在 `solana delegate-stake` 中使用它的投票帐户地址:
```bash
solana delegate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <VOTE_ACCOUNT_ADDRESS> \
--fee-payer <KEYPAIR>
```
质押权限 `<KEYPAIR>` 对地址 `<STAKE_ACCOUNT_ADDRESS>` 进行帐户授权操作。 该质押被委托给投票账户地址 `<VOTE_ACCOUNT_ADDRESS>`
委托质押后,使用 `solana stake-account` 查看质押账户的变化:
```bash
solana stake-account <STAKE_ACCOUNT_ADDRESS>
```
您将在输出中看到“Delegated Stake”和“Delegated Vote Account Address”两个新字段。 结果大概呈这样:
```text
Total Stake: 5000 SOL
Credits Observed: 147462
Delegated Stake: 4999.99771712 SOL
Delegated Vote Account Address: CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1
Stake activates starting from epoch: 42
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
```
## 取消质押
质押委托以后,您可以使用 `solana deactivate-stake` 命令来取消委托的质押:
```bash
solana deactivate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> \
--fee-payer <KEYPAIR>
```
质押权限 `<KEYPAIR>` 对地址 `<STAKE_ACCOUNT_ADDRESS>` 进行帐户授权操作。
请注意,质押需要几个 epoch 才能“冷却cool down”。 在冷却期间进行重新质押的操作将会失败。
## 提现质押
使用 `solana withdraw-stake` 命令将代币转移出质押帐户:
```bash
solana withdraw-stake --withdraw-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <RECIPIENT_ADDRESS> <AMOUNT> \
--fee-payer <KEYPAIR>
```
其中,`<STAKE_ACCOUNT_ADDRESS>` 是现有的质押帐户,质押权限 `<KEYPAIR>` 是提现权限, 而 `<AMOUNT>` 是要转账给接收账户 `<RECIPIENT_ADDRESS>` 的代币数量。
## 拆分质押
在现有质押不能取款的时候,您可能想将质押分配给另外的验证节点。 无法取回的原因可能是处于质押、冷却或锁定的状态。 若要将代币从现有质押账户转移到一个新的帐户,请使用 `solana split-stake` 命令:
```bash
solana split-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <NEW_STAKE_ACCOUNT_KEYPAIR> <AMOUNT> \
--fee-payer <KEYPAIR>
```
其中,`<STAKE_ACCOUNT_ADDRESS>` 是现有的质押帐户,质押权限 `<KEYPAIR>` 是质押账户的权限, `<NEW_STAKE_ACCOUNT_KEYPAIR>` 是新账户的密钥对,`<AMOUNT>` 是要转账给新账户的代币数量。
若要将质押账户拆分到派生账户地址,请使用 `--seed` 选项。 详情请参阅 [衍生质押账户地址](#advanced-derive-stake-account-addresses)。

View File

@@ -0,0 +1,131 @@
---
title: 安装 Solana 工具包
---
取决于您喜欢的工作公式,在电脑上安装 Solana 工具的方法有多种:
- [使用 Solana 的安装工具 (最简单的方法)](#use-solanas-install-tool)
- [下载预置的二进制文件](#download-prebuilt-binaries)
- [通过源代码安装](#build-from-source)
## 通过 Solana 安装工具
### MacOS & Linux
- 打开您最喜欢的终端应用
- 通过运行下述指令,安装 Solana 版本[LATEST_SOLANA_RELEASE_VERSION](https://github.com/solana-labs/solana/releases/tag/LATEST_SOLANA_RELEASE_VERSION) 到您的机器:
```bash
sh -c "$(curl -sSfL https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/install)"
```
- 您可以用 `LATEST_SOLANA_RELEASE_VERSION` 发布标签替换想要的软件版本,或者使用以下三个通道名称之一: `stable``beta``edge`
- 以下输出表示更新成功:
```text
downloading LATEST_SOLANA_RELEASE_VERSION installer
Configuration: /home/solana/.config/solana/install/config.yml
Active release directory: /home/solana/.local/share/solana/install/active_release
* Release version: LATEST_SOLANA_RELEASE_VERSION
* Release URL: https://github.com/solana-labs/solana/releases/download/LATEST_SOLANA_RELEASE_VERSION/solana-release-x86_64-unknown-linux-gnu.tar.bz2
Update successful
```
- 根据您的系统,安装程序消息的结束可能稍有不同
```bash
请更新您的 PATH 环境变量来包含 Solana 程序:
```
- 如果您收到上述消息,复制并粘贴下面的推荐命令来更新 `PATH`
- 通过运行以下命令来确认您已经安装了想要的 `solana` 版本:
```bash
solana --version
```
- 安装成功后,就可以通过 `solana-install update` 随时更新 Solana 软件到新版本。
---
### Windows 系统
- 以管理员身份打开命令提示(`cmd.exe`)
- 在 Windows 搜索栏中搜索命令提示。 当命令提示应用出现后,右键单击并选择“以管理员打开”。 如果弹出窗口请求“允许此应用进行设备更改?”,请点击是。
- 复制并粘贴以下命令,然后按回车下载 Solana 安装程序到临时目录:
```bash
curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/solana-install-init-x86_64-pc-windows-msvc.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs
```
- 复制并粘贴以下命令,然后按 Enter 安装最新版本的 Solana 软件。 如果系统弹出安全提示窗口,请选择允许程序运行。
```bash
C:\solana-install-tmp\solana-install-init.exe LATEST_SOLANA_RELEASE_VERSION
```
- 安装程序完成后,请按 Enter 键。
- 关闭命令提示窗口,并以普通用户身份重新打开
- 在搜索栏中搜索“Command Prompt”然后点击命令提示应用图标无需以管理员身份运行)
- 通过运行以下命令来确认您已经安装了想要的 `solana` 版本:
```bash
solana --version
```
- 安装成功后,就可以通过 `solana-install update` 随时更新 Solana 软件到新版本。
## 下载预置二进制文件
如果您不想通过 `solana-install` 来管理安装,您也可以手动下载并安装二进制安装包。
### Linux 系统
打开 [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86_64-unknown-linux-msvc.tar.bz2** 地址,下载二进制文件,然后提取文件:
```bash
tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
```
### MacOS 系统
打开 [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86_64-apple-darwin.tar.bz2** 地址,下载二进制文件,然后提取文件:
```bash
tar jxf solana-release-x86_64-apple-darwin.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
```
### Windows 系统
- 打开 [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86_64-pc-windows-msvc.tar.bz2** 地址,下载二进制文件,然后提取文件:
- 打开命令提示并导航到提取二进制文件的目录并运行:
```bash
cd solana-release/
set PATH=%cd%/bin;%PATH%
```
## 通过源代码安装
如果您无法使用预构建的二进制文件或者想通过源代码安装,请打开 [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86_64-unknown-linux-msvc.tar.bz2** 地址,下载二进制文件,然后提取文件: 提取代码并生成二进制文件:
```bash
./scripts/cargo-install-all.sh .
export PATH=$PWD/bin:$PATH
```
然后你可以运行以下命令来获得与预置二进制文件相同的结果:
```bash
solana-install init
```

View File

@@ -0,0 +1,71 @@
---
title: 管理质押账户
---
如果想将质押分配到多个不同的验证节点,您需要为每个验证节点创建一个单独的质押帐户。 如果你按照约定创建了一个种子seed为"0"的质押帐户那么第二个则是“1”第三个是“2”以此类推然后您可以使用 `solana-stock-account` 工具对所有的账户进行单次调用。 您可以用它来汇总所有帐户的余额,将帐户移动到一个新钱包,或设置新的权限。
## 使用方法
### 创建一个质押账户
在质押公钥上创建一个派生的质押帐户并转账进去:
```bash
solana-stake-accounts new <FUNDING_KEYPAIR> <BASE_KEYPAIR> <AMOUNT> \
--stake-authority <PUBKEY> --withdraw-authority <PUBKEY> \
--fee-payer <KEYPAIR>
```
### 账户统计
统计派生账户的数量:
```bash
solana-stake-accounts count <BASE_PUBKEY>
```
### 获取质押账户余额
汇总派生抵押账户的余额:
```bash
solana-stake-accounts balance <BASE_PUBKEY> --num-accounts <NUMBER>
```
### 获取质押账户地址
列出来自给定公钥的每一个质押账户地址:
```bash
solana-stake-accounts addresses <BASE_PUBKEY> --num-accounts <NUMBER>
```
### 设置新权限
为生成的每个抵押帐户设置新权限:
```bash
solana-stake-accounts authorize <BASE_PUBKEY> \
--stake-authority <KEYPAIR> --withdraw-authority <KEYPAIR> \
--new-stake-authority <PUBKEY> --new-withdraw-authority <PUBKEY> \
--num-accounts <NUMBER> --fee-payer <KEYPAIR>
```
### 重定向质押账户
重定向质押账户:
```bash
solana-stake-accounts rebase <BASE_PUBKEY> <NEW_BASE_KEYPAIR> \
--stake-authority <KEYPAIR> --num-accounts <NUMBER> \
--fee-payer <KEYPAIR>
```
对每个质押账户进行原子级别重置并授权,请使用 'move' 命令:
```bash
solana-stake-accounts move <BASE_PUBKEY> <NEW_BASE_KEYPAIR> \
--stake-authority <KEYPAIR> --withdraw-authority <KEYPAIR> \
--new-stake-authority <PUBKEY> --new-withdraw-authority <PUBKEY> \
--num-accounts <NUMBER> --fee-payer <KEYPAIR>
```

View File

@@ -0,0 +1,125 @@
---
title: 发送和接收代币
---
该网页展示了如何通过命令行钱包,使用命令行工具接收和发送 SOL代币例如 [纸钱包](../wallet-guide/paper-wallet.md) [文件系统钱包](../wallet-guide/file-system-wallet.md), 或[硬件钱包](../wallet-guide/hardware-wallets.md). 在开始之前,请确认您已经创建了一个钱包,并且可以访问其地址 (pubkey) 和签名密钥对。 请查看我们的[约定来输入不同钱包类型的密钥对](../cli/conventions.md#keypair-conventions).
## 测试您的钱包
在与其他人分享公钥前,您可能需要首先确认密钥的有效性,并确保真正拥有相应的私钥。
在这个例子中,我们将在第一个钱包的基础上再创建另一个钱包,然后转入一些代币。 这个步骤确保您可以在该钱包正常发送和接收代币。
该测试将通过我们的开发者测试网称为devnet。 测试网发行的代币**并没有**实际价值,所以无需担心资产损失。
#### 获取一些空投代币,开始操作
首先在测试网给您的钱包_空投_ 一些虚拟代币。
```bash
solana airdrop 10 <RECIPIENT_ACCOUNT_ADDRESS> --url https://api.devnet.solana.com
```
其中,用您的 base58-encoded 公钥/钱包地址替换此处的 `<RECIPIENT_ACCOUNT_ADDRESS>`文本。
#### 检查钱包余额
通过检查帐户余额确认空投已经成功。 输出值应当为 `10 SOL`:
```bash
solana balance <ACCOUNT_ADDRESS> --url https://api.devnet.solana.com
```
#### 创建第二个钱包地址
我们需要一个新地址来接收代币。 创建第二个密钥对并记录其公钥:
```bash
solana-keygen new --no-passphrase --no-outfile
```
输出将在文本 `pubkey:` 后面包括该地址。 复制该地址。 我们在下一步中要用到它。
```text
pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
```
您还可以通过下述方式创建任何类型的一个(或多个)钱包: [paper](../wallet-guide/paper-wallet#creating-multiple-paper-wallet-addresses), [file system](../wallet-guide/file-system-wallet.md#creating-multiple-file-system-wallet-addresses), 或者 [hardware](../wallet-guide/hardware-wallets.md#multiple-addresses-on-a-single-hardware-wallet).
#### 将代币从您的第一个钱包转到第二个地址
接下来,通过发送来证明你拥有空投代币。 Solana 集群只有在您用交易发送方公钥对应的私钥签名时,才会接受交易。
```bash
solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --url https://api.devnet.solana.com --fee-payer <KEYPAIR>
```
其中,用第一个钱包的秘钥对的路径替换 `<KEYPAIR>`,用第二个钱包地址替换 `<RECIPIENT_ACCOUNT_ADDRESS>`
使用 `solana balance` 确认余额已经更新:
```bash
solana balance <ACCOUNT_ADDRESS> --url http://api.devnet.solana.com
```
其中 `<ACCOUNT_ADDRESS>` 是您密钥对的公钥或收件人的公钥。
#### 转账测试的完整示例
```bash
$ solana-keygen new --outfile my_solana_wallet.json # 创建第一个文件系统钱包
产生新的密钥对
为了增加安全性,输入一个密码(空白表示不设置密码)
将新密钥对写入 my_solana_wallet.json
==========================================================================
pubkey: DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK # 第一个钱包的地址
==========================================================================
保存恢复密钥对的助记词:
width enhance concert vacant ketchup eternal spy craft spy guard tag punch # 如果这是一个真实的钱包,不要将这次单词分享到网络上!
==========================================================================
$ solana airdrop 10 DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://api.devnet.solana.com # 空投 10 个 SOL 到我的钱包地址/公钥
正在从 35.233.193.70:9900 请求 10 SOL
10 SOL
$ solana balance DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://api.devnet.solana.com # 检查钱包余额
10 SOL
$ solana-keygen new --no-outfile # 创建第二个钱包即纸钱包
生成新的密钥对
为了增加安全性,输入一个密码(空白表示不设置密码)
====================================================================
pubkey: 7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv # 这是第二个钱包即纸钱包的地址
=======================================================
保存助记词(用于恢复新秘钥对):
clump panic cousin hurt coast charge engage fall eager urge win love # 如果这是一个真实的钱包,切记不要将这次单词分享到网络上!
====================================================================
$ solana transfer --from my_solana_wallet.json 7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv 5 --url https://api.devnet.solana.com --fee-payer my_solana_wallet.json # 发送代币到纸钱包的公钥地址
3gmXvykAd1nCQQ7MjosaHLf69Xyaqyq1qw2eu1mgPyYXd5G4v1rihhg1CiRw35b9fHzcftGKKEu4mbUeXY2pEX2z # 该笔交易的签名
$ solana balance DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://api.devnet.solana.com
4.999995 SOL # 由于需要 0.000005 SOL 的交易费用,发送金额要稍微小于 5 SOL
$ solana balance 7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv --url https://api.devnet.solana.com
5 SOL # 第二个钱包现在已经接收到第一个钱包发送的 5 SOL
```
## 接收代币
首先您需要一个地址让别人来发送代币。 在 Solana 区块链,钱包地址就是密钥对的公钥。 生成密钥对的方法有好几种。 这些方法取决于您选择如何存储密钥对。 密钥对存储在钱包里。 在接收代币之前,您需要通过 [来创建一个钱包](../wallet-guide/cli.md) 完成该步骤后,您就能获得每个密钥对生成的公钥。 公钥是一个 base58 字符的长字节。 其长度从 32 到 44 个字符不等。
## 发送代币
如果您已经持有 SOL 并想要向其他人发送代币,您将需要密钥对的路径, 他们的 base58 编码公钥和准备发送的代币。 上述条件准备好了以后,您可以使用 `solana transfer` 命令来发送代币:
```bash
solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT> --fee-payer <KEYPAIR>
```
使用 `solana balance` 确认余额已经更新:
```bash
solana balance <ACCOUNT_ADDRESS>
```

View File

@@ -0,0 +1,63 @@
---
title: CLI 使用参考
---
[solana-cli crate](https://crates.io/crates/solana-cli) 为 Solana 提供了一个命令行界面工具
## 示例:
### 获取公钥
```bash
// 命令
$solana-keygen pubkey
// 返回
<PUBKEY>
```
### 空投 SOL/Lamports
```bash
// 命令
$ solana airdrop 2
// 返回
"2.0000000 SOL"
```
### 获取余额
```bash
// 命令
$ solana balance
// 返回
"3.00050001 SOL"
```
### 确认交易
```bash
// 命令
$ solana confirm <TX_SIGNATURE>
// 返回
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
```
### 部署程序
```bash
// 命令
$ solana deploy <PATH>
// 返回
<PROGRAM_ID>
```
## 使用方法
###
```text
```

View File

@@ -0,0 +1,128 @@
---
title: 集群基准
---
Solana git 仓库涵盖了配置本地测试网可能用到的所有脚本。 根据实现的目标,您可能想配置一个全新、增强性能的不同版本的多节点测试网,那么它可能比单纯的仅支持 Rust 的单节点测试节点要复杂得多。 如果您正在尝试开发高级功能(例如智能合约),那么利用一些已有的配置,直接使用 Rust 支持的单节点模型就好。 如果您正在对交易流程进行性能优化,请考虑增强的单节点 demo。 如果你在着手共识算法的工作,那么你将至少需要一个 Rust 的多节点 demo。 如果您想要复制 TPS 性能表,请运行强化的多节点 demo。
对于上述的四种变型,您可能需要最新的 Rust 工具链和 Solana 源代码:
首先,请设置 Solana [README](https://github.com/solana-labs/solana#1-install-rustc-cargo-and-rustfmt) 中提到的 Rust、Cargo 和系统安装包。
请检查 github 代码:
```bash
git clone https://github.com/solana-labs/solana.git
cd solana
```
演示代码有时在我们添加新的低级功能时会失败,所以如果这是您第一次运行 demo为了提高成功的概率请在继续操作之前先查看 [latest release](https://github.com/solana-labs/solana/releases)
```bash
TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
git checkout $TAG
```
### 设置配置
确保在任何节点启动之前都能建立例如投票程序之类的重要程序。 请注意,为了良好的性能,我们在这里使用版本构建的方式。 如果你想要调试构建,只需使用 `cargo build` 并省略 `NDEBUG=1` 命令的一部分。
```bash
cargo build --release
```
运行下面的脚本来初始化网络的创世账本。
```bash
NDEBUG=1 ./multinode-demo/setup.sh
```
### 水龙头
为了验证程序和客户端正常工作,我们需要打开一个水龙头来领取一些测试代币。 水龙头按照 Milton Friedman 的风格“空投”\(免费代币给请求客户\),然后用于测试交易。
打开水龙头:
```bash
NDEBUG=1 ./multinode-demo/faucet.sh
```
### 单节点测试网
在启动验证节点之前,请确保您获取了想要启动验证节点的机器的 IP 地址,并确保 udp 端口 8000-1000 处于打开状态。
现在在独立的 shell 中启动验证节点:
```bash
NDEBUG=1 ./multinode-demo/bootstrap-validator.sh
```
等待几秒钟进行初始化。 当准备好接收交易时它会打印“leader ready...”。 如果领导者没有任何测试代币,它将从水龙头请求一些。 在领导者启动之前,水龙头不用一直运行。
### 多节点测试网
如果要运行一个多节点测试网,在启动一个领导者节点后,需要在独立 shell 中加入一些额外的验证节点:
```bash
NDEBUG=1 ./multinode-demo/validator-x.sh
```
如果要在 Linux 上运行增强性能验证节点,必须在系统中安装 [CUDA 10.0](https://developer.nvidia.com/cuda-downloads)
```bash
./fetch-perf-libs.sh
NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/bootstrap-validator.sh
NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/validator.sh
```
### 测试网客户端演示
现在您的单节点或多节点测试网已启动并正常运行了,接下来我们发送一些交易!
在另一个 shell 启动客户端:
```bash
NDEBUG=1 ./multinode-demo/bench-tps.sh # runs against localhost by default
```
刚刚发生了什么? 客户端演示将尽最快的速度将 500,000 笔交易发送到测试网。 然后客户端定期连接测试网,看看它当时处理了多少笔交易。 请注意,这个 demo 故意将大量 UDP 数据包发送给网络,因此网络几乎会丢失很大一部分。 这确保了试验网有机会达到 710k TPS。 客户端 demo 在确保测试网不再处理任何其他交易后就停止运行。 您应该看到一些 TPS 数值出现在屏幕上。 在多节点变体中,您也会看到每个验证节点的 TPS 测量值。
### 测试网调试
代码中有一些非常有用的调试消息,您可以在每个模块和每个级别的基础上启用它们。 在运行一个领导者或验证节点之前,请设置正常的 RUST_LOG 环境变量。
例如:
- 要在任意位置启用 `info` 以及只能在 solana::banking_stage 模块中启用 `debug`
```bash
export RUST_LOG=solana=info,solana::banking_stage=debug
```
- 启用 BPF 程序日志记录:
```bash
export RUST_LOG=solana_bpf_loader=trace
```
一般来说,我们正在使用 `debug` 处理不经常的调试消息, `trace` 处理可能频繁的消息, `info` 用于与性能相关的记录。
您也可以通过 GDB 附加到一个运行过程。 领导者进程命名为 _solana-validator_:
```bash
sudo gdb
attach <PID>
set logging on
thread apply all bt
```
这将把所有线程堆栈跟踪转储到 gdb.txt
## 开发者测试网
在此示例中,我们将把客户端连接到公共测试网。 在测试网上运行验证器,您需要打开 udp 端口 `8000-1000`。
```bash
NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint devnet.solana.com:8001 --faucet devnet.solana.com:9900 --duration 60 --tx_count 50
```
您可以在 [metrics dashboard](https://metrics.solana.com:3000/d/monitor/cluster-telemetry?var-testnet=devnet) 上观察客户端交易的影响

View File

@@ -0,0 +1,64 @@
---
title: 生成分叉
---
本节描述了由于 [轮换领导者](leader-rotation.md) 而自然产生分叉的情况。
## 概览
节点会变为领导者并生成编码更改的 PoH。 群集可以通过综合领导者 _**可能**_ 生成的内容,来容忍与任何领导者的连接中断,但不会进行任何的状态更改。 因此可能的分叉数量仅限于“有/无”跳过在领导者轮换时,在插槽边界可能出现的分叉列表。 在任何指定的插槽上,只接受一名领导者的交易。
## 消息流
1. 交易被当前的领导者吸纳。
2. 领导者过滤有效的交易。
3. 领导人执行有效交易的状态更新。
4. 以当前 PoH 插槽为基础的领导者软件包交易记录。
5. 领导者将条目传送到验证节点 \(嵌入签名的碎片\)
1. PoH 流包括滴答计数;空白条目显示了领导者的主动性和集群时间的流逝。
2. 领导者传输首先是必要的滴答目录,以便 PoH 返回领导者最近观察到的上一个领导者插槽。
6. 验证节点将条目重新传输给他们集合中的对等点和未来的下游节点。
7. 验证节点验证交易并在其状态下执行它们。
8. 验证节点计算状态的哈希值。
9. 在特定时间, 例如特定的 PoH 滴答计数, 验证节点将投票传给领导者。
1. 投票是指计算状态的 PoH 滴答计数的哈希签名。
2. 投票也是通过 Gossip 进行传播的。
10. 领导者进行与任何其他交易相同的选票,并将投票广播到集群。
11. 验证节点观察他们的投票和该集群的所有选票。它
## 分区,分叉
在 PoH 滴答计数时,可能出现与投票相对应的分叉。 下一位领导者可能没有观察到最后一次投票,可能会用生成的虚拟 PoH 条目开始它们的插槽。 这些空白滴答是由集群中的所有节点按集群配置的每次滴答哈希 `Z` 生成的。
投票时间段中只能有两个版本的 PoH `T` 滴答的 PoH 并且有当前领导者生成的条目或仅进行滴答的 PoH。 "只滴答"的 PoH 版本可以被视为一个虚拟账本,集群中的所有节点都可以从上一个插槽的最后一次滴答产生。
验证节点可以在其它方面忽略分叉\(例如从错误的领导者\),或罚没产生分叉的领导者。
验证节点根据贪婪算法进行投票,以最大限度提高他们在 [Tower BFT](../implemented-proposals/tower-bft.md) 中描述的奖励。
### 验证节点视图
#### 时间进度
下面图表代表了验证器关于 PoH 流的视图,以及一段时间内可能的分叉。 L1、L2 等是领导者插槽, `E`s 代表领导者插槽中的领导者条目。 `x`s 代表仅滴答,时间流向图表下方。
![生成分叉](/img/fork-generation.svg)
注意,在同一个槽位的两个分叉上显示 `E` 是一个可罚没条件,因此一个验证节点观察 `E3``E3` 可以对 L3 进行罚没,该插槽安全地选择 `x`。 一旦验证节点承认了某个分叉,在该滴答下方的其他分叉可以被丢弃了。 对于任何插槽,验证节点只需要考虑一个单一的“有条目”链或一个由一位领导者提出的“只滴答”的链。 但由于多个虚拟条目与上一个插槽相连接,它们可能会重叠。
#### 时间分隔
将领导者轮换对 PoH 滴答计数视为集群编码状态的时间分隔非常有用。 下表列出了上述分叉树作为一个时间分隔的帐本。
| 领导者插槽 | L1 | L2 | L3 | L4 | L5 |
|:------- |:-- |:-- |:-- |:-- |:-- |
| 数据 | E1 | E2 | E3 | E4 | E5 |
| 自上次起的滴答 | | | | x | xx |
请注意,领导者插槽 L3 期间只接受来自领导者 L3 的数据。 如果 L3 没有观察到 L2 的数据L3 的数据可能包括回到 L2 以外的另一个插槽。 L4 和 L5 的传输包括“ticks to prev” PoH 条目。
网络数据流的这种结构允许节点将其准确保存到账本,用于重新播放、重新启动和检查点。
### 领导者视图
当新的领导者开始一个插槽时,它必须首先发送将新插槽与最近观察到并投票的插槽链接所需的任何 PoH \(ticks\)。 领导者提议的分叉将把目前的插槽连接到前一个分叉,而前一个分叉是该分叉的虚拟分叉。

View File

@@ -0,0 +1,88 @@
---
title: 领导者轮换
---
在任何时候,集群都只需要一个验证节点来生成账本条目。 由于每次只有一个领导者,所有验证节点都能够重复相同的账本副本。 然而,一次只有一名领导者的缺点是,恶意领导者可能会审查选票和交易。 由于审查无法同网络丢弃数据包区分开,因此集群不能简单地选择某一个节点来长期担任领导者角色。 相反,集群通过轮换来决定哪个节点担任领导者,从而最大限度减少恶意领导者的影响。
每个验证节点使用下文描述的同一种算法来选择预期的领导者。 当验证节点收到一个新的签名账本条目时,可以肯定某条目是来自预期的领导者。 分配给每位领导者的插槽顺序称为 _leader schedule领导者安排表_
## 领导者轮换计划
一个验证节点会拒绝未经过 _插槽领导者_ 签名的区块。 所有插槽领导者的身份列表称为 _领导者安排表_。 领导者安排表是通过本地定期重新计算产生的。 它指派插槽领导者持续一段称为 _epoch纪元_ 的时间。 安排表必须早于它分配的时间段, 这样它保证了计算计划的账本状态最后能够确定。 该持续时间称为 _领导者安排表偏移时间_。 Solana 将偏移时间设置为直到下一个 epoch 的插槽持续时间。 也就是说,一个 epoch 的领导者计划通过上一个 epoch 开始时的账本状态来计算得到。 一个纪元的偏移量是比较随意的,并且假定时间足够长,使所有验证节点都将在生成下一个计划之前确定其账本状态。 集群可以选择缩短偏移时间,来缩短质押变化与领导者计划更新之间的时间。
在没有分区的情况下运行时间比一个 epoch 长的时候,只有在根分叉的 epoch 边界才能生成安排表。 由于安排表用于下一个纪元,因此在下一个纪元之前,任何质押给根分叉的新质押都不会被激活。 用于生成领导者计划的区块是跨过纪元边界的第一个区块。
如果分区比一个 epoch 时间短,集群将按以下方式运作:
1. 验证节点在投票时不断更新自己的根分叉。
2. 每次在纪元边缘的插槽高度的时候,验证节点将更新其领导者安排表。
例如:
Epoch 持续时间为 100 个插槽。 根分叉从在插槽高度 99 处计算的分叉更新为在插槽高度 102 处计算出来的。 由于故障,跳过了插槽高度分别为 100、101 的分叉。 新的领导者计划通过分叉在高度为 102 的位置计算出来。 在插槽 200 中它处于活动状态,直到再次更新。
不会存在共识不一致的情况,因为当群集的根节点通过 102 时,对群集进行投票的每个验证节点都跳过了 100 和 101。 无论采用哪种投票方式,所有验证节点都将提交为 102 或 102 后代的根。
### 带有纪元分区的领导者轮换计划。
领导者安排表偏移的持续时间与群集对正确的领导者安排表不一致的可能性有直接关系。
考虑以下几种假设情况:
两个分区正在生成每个区块的一半。 但它们两个都不是最终的大多数分叉。 两者都将跨过纪元 100 和 200而实际上并没有形成一个根因此将在整个集群范围内承诺一个新的领导者安排表。
在这种不稳定的情况下,存在多个有效的领导者计划。
- 这时候,系统将为直系父级在上一个纪元的每个分叉生成一个领导者安排表。
- 领导者安排表在后代分叉的下一个纪元开始后才有效,直到更新为止。
每个分区的日程表将在一个分区持续超过一个纪元之后发生变化。 因此,纪元持续时间应当设置为远大于插槽时间和分叉提交到根的预期长度。
在观察集群足够长的时间后,可以根据中位数分区持续时间及其标准偏差来选择领导者安排表偏移量。 例如,偏移量长于中位数分区持续时间再加上六个标准偏差,则可以将群集中账本计划不一致的可能性降低到百万分之一。
## 生产创世领导者安排表
创世配置声明第一个纪元的第一个领导者。 该领导者计划在前两个纪元结束,因为领导者计划也在下一个时期的插槽 0 中生成。 前两个纪元的长度也可以在创世配置中指定。 前几个纪元的最小长度必须大于或等于 [Tower BFT](../implemented-proposals/tower-bft.md) 中定义的最大回滚深度。
## 创世领导者安排表算法
领导者时间表通过预定义的种子生成。 过程如下:
1. 定期使用 PoH 滴答高度 \(单调递增的计数器\) 产生稳定的伪随机算法种子。
2. 在该高度下,对银行中所有具有领导者身份且已在集群配置的滴答中进行投票的抵押帐户进行抽样。 该示例被称为 _活动集合activate set_
3. 按质押权重对活动集进行排序。
4. 使用随机种子选择按质押加权的节点,来创建质押加权排序。
5. 在群集配置的一定滴答后,该排序开始生效。
## 安排表攻击矢量
### 种子
所选择的种子是可预测的,但是不存在偏差。 没有任何可怕的攻击会影响其结果。
### 活动集
领导者可以通过审查验证人的投票来使活动集偏差。 领导者可能通过两种方式来审查活动集:
- 忽略验证节点的投票
- 拒绝使用验证节点的票对区块进行投票
为了减少审查的可能性在_活动集采样期间_网络将在领导者安排表偏移量边界上计算活动集。 有效设置的采样持续时间足够长,从而让多个领导者收集到投票。
### 质押
领导者可以审查新的质押交易,或拒绝验证有新质押的区块。 这种攻击类似于对验证节点选票的审查。
### 验证节点操作密钥丢失
领导者和验证节点应使用临时密钥进行操作,而质押所有者授权验证节点通过委托来处理其质押。
群集应该能够能通过领导者和验证者丢失的所有临时密钥进行恢复,这可能是所有节点共享出现的常见软件漏洞。 质押所有人应该能够通过共同签署验证节点的投票直接进行表决,即使质押已经委托给验证节点。
## 追加条目
领导者时间表的生命周期称为一个 _纪元epoch_。 纪元又划分为多个 _插槽_,每个插槽的持续时间 `T` 称为 PoH 滴答时间。
领导者在其插槽期间发送条目。 在 `T` 滴答时间后,所有验证节点都将切换到下一个预定的领导者。 验证节点必须忽略在领导者指定的插槽之外发送的条目。
下一位领导者必须观察所有的 `T` 滴答,以便其建立自己的条目。 如果没有观察到条目\(领导者掉线\) 或条目无效 \(领导者故障或作恶\) 则下一个领导者必须滴答来填充前一个领导者的插槽。 请注意,下一位领导者应并行执行维修请求,并推迟发送滴答,直到确信其他验证节点也没有观察到上一位领导者的条目。 如果领导者错误地产生自己的滴答,那么跟随它的领导者必须替换其所有滴答。

View File

@@ -0,0 +1,36 @@
---
title: 管理分叉
---
在插槽边界处,账本是可能会产生分叉的。 产生的数据结构形成一个称为 _块存储blockstore_ 的树。 验证器解释blockstore时它必须维护区块链中每个分叉的状态。 我们称每个实例为 _活跃分叉active fork_。 验证节点有责任对这些分叉进行权衡,从而最终选择一个分叉。
验证节点通过向该分叉的插槽负责人投票来选择一个分叉。 投票将让验证节点提交一个称为 _锁定期lockout period_的持续时间。 在该锁定期到期之前,验证节点无法对其他分叉进行投票。 随后,对同一分叉的每次投票都会将把锁定期延长一倍。 经过一些群集配置的投票数\(当前为32 \) 后锁定期的持续时间达到了所谓的_最长锁定期max lockout_。 在达到最长锁定期之前,验证节点可以选择一直等待,直到锁定期结束然后再投票给另一个分叉。 当它在另一个分叉上投票时,所执行称为 _回滚rollback_ 的操作,此时状态会及时回滚到共享检查点,然后跳转到刚刚投票分叉的初始位置。 分叉能够回滚的最大限度称为 _回滚深度rollback depth_。 回滚深度是达到最长锁定期所需的票数。 每当验证节点投票时,超出回滚深度的所有检查点都将无法访问。 也就是说,在任何情况下,验证节点回滚的限度都不用超过回滚深度。 因此,它可以安全地 _修剪prune_ 无法到达的分叉,并将超出回滚深度的所有检查点 _局限squash_ 于根检查点中。
## 活跃分叉
活跃的分叉是一系列检查点,其长度至少比回滚深度长一倍。 最短分叉的长度刚好为回滚深度的一倍。 例如:
![分叉](/img/forks.svg)
以下序列是 _活跃分叉_
- {4, 2, 1}
- {5, 2, 1}
- {6, 3, 1}
- {7, 3, 1}
## 修剪和挤压
验证节点可以对树中的任何检查点进行投票。 在上图中,就是除了树叶以外的每个节点。 投票后,验证节点修剪从比回滚深度更远的距离派生的节点,然后通过将其可以挤压到根部的任何节点,利用机会来最小化其内存使用量。
从上面的示例开始,回滚深度为 2考虑对 5 投票与对 6 投票。 首先,对 5 进行投票:
![修剪后的分叉](/img/forks-pruned.svg)
新的根为 2任何不为 2 后代的活跃分叉都会被修剪。
或者,对 6 进行投票:
![分叉](/img/forks-pruned2.svg)
该树的根始终为 1因为从 6 开始的活跃分叉距它仅有 2 个检查点。

View File

@@ -0,0 +1,42 @@
---
title: Solana 集群
---
Solana 集群是一组验证人,共同为客户交易服务并保持账本的完整性。 可能存在着多个集群。 当两个集群都来自同一个创世区块时,它们会趋向收敛。 否则,他们就会忽略了另一个集群的存在。 发送到错误集群的交易会慢慢被拒绝。 在本章节,我们将讨论创建集群、节点加入集群、共享账本、确保账本被复制以及处理有漏洞和恶意的节点。
## 创建一个集群
在启动任何验证节点之前首先需要创建一个_创世配置_。 该配置引用了两个公钥一个_铸造_和一个_引导验证节点_。 拥有引导验证节点私钥的验证节点负责将第一个条目附加到账本。 它使用铸造帐户初始化其内部状态。 该帐户将保存由创世配置定义的原生代币数。 然后,第二个验证节点联系引导验证节点,来注册为一个 _验证节点_。 然后,其他验证节点将在集群的任何已注册成员中注册。
验证节点会收到领导者的所有条目,并提交投票以确认这些条目的有效性。 投票后,验证节点需要存储这些条目。 一旦验证节点发现存在足够多的副本,它将删除自身的副本。
## 加入一个集群
验证节点通过发送到_控制台control plane_的注册消息进入集群。 控制台使用 _八卦gossip_ 协议实现,这意味着节点可以向任何现有节点注册,并期望其注册传播到集群中的所有节点。 所有节点同步所需的时间与参与群集节点数的平方成正比。 从算法上讲,人们都认为它非常慢,但是牺牲时间所换来的,就是一个节点可以确保它最终拥有与每个其他节点相同的信息,并且任何一个节点都无法审查该信息。
## 将交易发送到集群
客户端将交易发送到任何验证节点的交易处理单元\(TPU\) 端口。 如果该节点处于验证节点角色,则它将交易转发给指定的领导者。 如果处于领导者角色则该节点将传入的事务捆绑在一起对其打上时间戳来创建一个_条目entry_然后将其推送到集群的 _数据中心data plane_。 进入数据中心后,交易将由验证节点进行验证,从而将交易有效地添加到账本中。
## 确认交易
Solana集群能够在亚秒级的时间内_确认confirmation_ 最多150个节点并计划扩展到成千上万个节点。 一旦完全实施,确认时间预计只会随着验证节点数量的对数而增加,而对数的基数又很高。 例如,如果基数为一千,则意味着对于前一千个节点,确认将是三个网络跃点的持续时间加上一个最慢验证节点进行投票所花费的时间。 对于接下来的一百万个节点,确认仅增加一个网络跃点。
Solana将确认定义为从领导者为新条目添加时间戳到达成账本的绝大多数投票为止的持续时间。
八卦网络增长到一定规模后,就会变得太慢而无法实现亚秒级确认。 将消息发送到所有节点所花费的时间与节点数的平方成正比。 如果区块链想要获得低确认率并尝试使用八卦网络来做到这一点,它将被迫集中到少数几个节点上。
可以使用以下技术组合来实现可扩展的确认:
1. 使用VDF样本对交易打上时间戳并签名。
2. 将交易分为几批,将每笔交易发送到单独的节点,同时
每个节点都与对等节点共享其批次。
3. 递归地重复上一步,直到所有节点都具有所有批次。
Solana以固定的时间间隔称为_插槽_轮换领导者。 每个领导者只能在其分配的时段内产生条目。 领导者因此对交易加上时间戳记,以便验证节点可以查找指定领导者的公钥。 然后,领导者对时间戳进行签名,以便验证节点验证签名,证明签名者是指定领导者公钥的所有者。
接下来,将交易分成批处理,以便节点可以将交易发送给多方,而无需进行多份复制。 例如如果领导者需要将60笔交易发送到6个节点则它将把60笔交易的集合分成10笔交易的批次并向每个节点发送一个交易。 这能够让领导者将60笔交易放在网络上而不是每个节点60笔交易。 接着,每个节点都与对等节点共享其批次。 一旦节点收集了全部6个批次它将重建60个交易的原始集合。
一批交易只能进行多次拆分,直至它变得非常小,信息头成为网络带宽的主要负载。 在撰写本文时该方法最多可扩展至约150个验证节点。 为了扩展到成千上万个验证节点,每个节点可以将与引导者节点相同的技术应用于另一组相同大小的节点。 我们称这种技术为 [_涡轮区块传播Turbine Block Propogation_](turbine-block-propagation.md)。

View File

@@ -0,0 +1,25 @@
---
title: 性能指标
---
Solana群集性能的衡量标准是网络可以维持的每秒平均交易数\(TPS\)。 并且,交易需要多长时间才能由群集的大多数\(确认时间\) 确认。
每个群集节点都维护各种计数器,它们随着某些事件增加。 这些计数器会定期上传到一个云数据库。 Solana的指标仪表板获取这些计数器并计算性能指标将其显示在仪表板上。
## TPS
每个节点库运行时都维护一个已处理的交易计数。 仪表板首先计算集群中所有启用了指标的节点之间的交易中位数。 然后将中位数集群交易计数在2秒的时间段内取平均值并显示在TPS时间序列图中。 仪表板还显示了TPS均值、最大TPS和总交易计数统计信息这些统计信息都是根据交易次数的中位数计算得出的。
## 确认时间
每个验证器节点都维护一个可见的活跃账本分叉列表。 当节点已接收并处理了与该分叉相对应的所有条目时,该分叉被视为冻结。 当分叉获得了累积的绝大多数投票,并且其中一个子分叉被冻结时,该分叉将被视为已确认。
节点为每个新的分叉分配一个时间戳,并计算确认分叉所花费的时间。 此时间在性能指标中即为验证节点确认时间。 性能仪表板将每个验证节点的确认时间的平均值显示为时间序列图。
## 硬件设置
验证节点软件已部署到配有1TB pd-ssd磁盘和2x Nvidia V100 GPU的GCP n1-standard-16实例。 它们部署在us-west-1地区。
solana-bench-tps从网络在配有n1-standard-16 CPU实例的客户机的网络收敛之后开始计算该客户机参数如下 `--tx\_count=50000 --thread-batch-sleep 1000`
在bench-tps重新开始阶段的5分钟内TPS和确认时间指标从仪表板编号中获取。

View File

@@ -0,0 +1,18 @@
---
title: Solana 集群 RPC 端点
---
Solana 维护专用的 API 节点来完成 [JSON RPC](developing/clients/jsonrpc-api.md) 对每个公共集群的请求,第三方同样可以提供托管 API 节点服务。 以下为目前可用的公共 RPC 端点,推荐给每个公共集群:
## Devnet开发者网络
- `https://api.devnet.solana.com` - 单个 Solana 托管的 api 节点;限定频率
## Testnet测试网
- `https://api.testnet.solana.com` - 单个 Solana 托管的 api 节点;限定频率
## Mainnet Beta主网 Beta
- `https://api.mainnet-beta.solana.com` - Solana 托管的 api 节点集群,由负载平衡器支持;限定频率
- `https://solana-api.projectserum.com` - Project Serum 托管的 api 节点

View File

@@ -0,0 +1,194 @@
---
title: 质押委托和奖励
---
质押者因帮助验证账本而获得奖励。 他们通过将其质押委托给验证节点来做到这一点。 这些验证节点会进行繁重的工作,广播账本,并将选票发送到每个节点的投票帐户(其中质押者可以委托其质押)。 当出现分叉时,集群的其余部分使用那些质押加权投票来选择一个区块。 验证节点和质押者都需要某种经济激励来发挥作用。 验证节点的硬件需要得到补偿,质押者需要获得奖励来降低其质押风险。 该部分设计详见[质押奖励](../implemented-proposals/staking-rewards.md)。 本章节将描述其实现的基本机制。
## 基本设计
通常的想法是验证节点有一个投票帐户。 投票帐户跟踪验证节点的投票,对验证节点产生的信用进行计数,并提供其他任何针对验证节点的状态。 投票帐户无法悉知委托给它的任何质押,账号本身也没有质押。
单独的质押帐户 \(由质押者创建\) 命名了一个将质押委托的投票帐户。 所产生的奖励与质押的lamports数量成正比。 质押帐户仅由质押人拥有。 此帐户中存储的某些部分Lamport属于质押。
## 被动委托
无需控制投票帐户或向该帐户提交投票的身份进行交互操作,任何数量的质押帐户都可以委托给一个投票帐户。
可以通过将投票帐户pubkey作为 `StakeState::Stake::voter_pubkey` 的所有质押帐户的总和来计算分配给Vote帐户的总质押。
## 投票和质押账户
奖励过程分为两个链上程序。 投票程序解决了让质押处于罚没的问题状态。 质押计划是奖励池的托管人,提供被动委托。 当显示质押者的代表已参与验证账本时Stake程序负责向质押者和投票者支付奖励。
### 投票状态
VoteState是验证节点已提交给网络的所有投票的当前状态。 VoteState包含以下状态信息
- `投票` - 提交的投票数据结构。
- `积分` - 该投票程序在其整个生命期内产生的奖励总额。
- `root_slot` - 达到奖励所需的全部锁定的最后一个插槽。
- `佣金` - VoteState从质押者的Stake帐户获得的任何奖励中抽取的佣金。 这是奖励的百分比上限。
- Account::lamports - 佣金累计获得的lamports。 这些并不算作质押。
- `authorized_voter` - 只有该身份能提交投票。 此字段只能通过身份认证进行修改。
- `node_pubkey` - 在这个帐户中投票的 Solana 节点。
- `authorized_withdrawer` - 负责该账户lamports 实体的身份,独立于帐户地址和授权的投票签名者。
### VoteInstruction::Initialize\(VoteInit\)
- `account[0]` - RW - 选票状态。
`VoteInit` 带有新投票帐户的 `node_pubkey`, `authorized_porer`, `authorized_withdrawer`, 和 `commission`
其他投票状态成员处于默认状态。
### VoteInstruction::Authorize\(Pubkey, VoteAuthorize\)
根据VoteAuthorize参数\(`投票者``提款者`\),使用新的授权投票人或提款人更新帐户。 交易必须由投票帐户当前的` 授权投票人``授权提款人`签名。
- `account[0]` - RW - VoteState。 `VoteState::authorized_voter``authorized_withdrawer` 设置为 `Pubkey`.
### VoteInstruction::Vote\(Vote\)
- `account[0]` - RW - The VoteState。 `VoteState::lockouts``VoteState::credit` 是根据投票锁规则更新的,参考 [Tower BFT](../implemented-proposals/tower-bft.md)。
- `account[1]` - RO - `sysvar::slot_hash` 需要验证投票反对的一些 N 最近插槽及其哈希列表。
- `account[2]` - RO - `sysvar::clock` 当前的网络时间,以 slot、epoch 等表示。
### 质押状态StakeState
StakeState 通常为这个四种形式之一StakeState::Uninitialized、StakeState::Initialized、StakeState::Stake 以及 StakeState::RewardsPool。 质押中仅使用前三种形式,但是只有 StakeState::Stake 非常有趣。 所有奖励池都是在创始时创建的。
### StakeState::Stake
StakeState:: Stake 是**质押者**的当前委托首选项,并包含以下状态信息:
- Account::lamports - 可用于质押的 lamports。
- `stake` - 产生奖励的 \(受到预热和冷却的影响\)质押,总是小于或等于 Account::lampport。
- `voter_pubkey` - 把 lamport 委托给 VoteState 实例的 pubkey 。
- `credits_observed` - 在程序的整个生命周期内获得的总积分。
- `activated` - 激活/委托质押的epoch。 所有质押将在预热后计算在内。
- `deactivated` - 停用此质押的epoch在完全停用帐户之前需要一些冷却epoch才能提取质押。
- `authorized_staker` - 必须签名委托,激活和停用交易的实体的公钥。
- `authorized_withdrawer` - 负责该帐户实体的身份,独立于帐户的地址和授权质押者。
### StakeState::RewardsPool
为避免单个网络范围内的兑换锁定或争用在预先确定的密钥下创世的一部分包括256个奖励池每个密钥具有std::u64::MAX信用额度以便能够根据积分值满足赎回要求。
质押和奖励池是同一`质押`程序所拥有的帐户。
### StakeInstruction::DelegateStake
质押账户从初始化形式转移到StakeState::Stake形式或者从已停用(即完全冷却)的StakeState::Stake转变为激活的StakeState::Stake。 这是质押者选择其质押账户Lamport委托给的投票帐户和验证节点节点的方式。 交易必须由质押的`授权质押者`签名。
- `account[0]` - RW - StakeState::Stake 实例。 `StakeState::Stake::credits_observed` 已初始化到 `VoteState::credits``StakeState::Stake::voter_pubkey` 已初始化到 `account[1]`。 如果这是首次委托质押,`StakeState::Stake::stake` 会被初始化到账户的 lamports余额`StakeState::Stake::activated` 被初始化到 Bank epoch并且 `StakeState::Stake::deactivated` 被初始化到 std::u64::MAX
- `account[1]` - R - VoteState 实例。
- `account[2]` - R - sysvar::clock 账户,包含有关当银行时间的信息。
- `account[3]` - R - sysvar::stakehistory 帐户,包含有关质押历史的信息。
- `account[4]` - R - stake::Config 帐户,负责预热,冷却和罚没配置。
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)
根据质押授权参数\(`质押者``提款人`\),使用新的授权质押者或提款人更新帐户。 交易必须由质押帐户当前的`授权质押人``授权提款者`签名。 任何质押锁定必须已到期,或者锁定托管人也必须签名交易。
- `account[0]` - RW - StakeState。
`StakeState::authorized_staker``authorized_withdrawer` 已设置为 `Pubkey`
### StakeInstruction::Deactivate
质押持有者可能希望从网络中提款。 为此,他必须首先停用自己的质押,然后等待冷却。 交易必须由质押的`授权质押者`签名。
- `account[0]` - RW - 读写正在停用的 StakeState::Stake 实例。
- `account[1]` - R - 带有当前时间的 Bank 的 sysvar::clock 帐户。
StakeState::Stake::deactivated 停用设置为当前时间+冷却时间。 到那个epoch帐户的质押将减少到零并且Account::lamports将可以提款。
### StakeInstruction::Withdraw\(u64\)
Lamports会随着时间在一个质押账户中累积超过已激活质押的任何多余部分都可以提取。 交易必须由质押的`授权提款人`签名。
- `account[0]` - RW - 需要取款的 StakeState::Stake。
- `account[1]` - RW - 应当计入已提取Lamport的帐户。
- `account[2]` - R - 带有当前时间的 Bank sysvar::clock 账户,用于计算质押。
- `account[3]` - R - 来自 Bank 的 sysvar::stake_history 帐户,具有质押预热/冷却历史记录。
## 这种设计的好处
- 所有质押者进行一次投票。
- 清除积分变量对于索取奖励不是必需的。
- 每个委派的质押都可以独立索取奖励。
- 当委托质押要求奖励时,将交纳工作佣金。
## 示例通话流
![被动质押调用流](/img/passive-staking-callflow.png)
## 质押Staking奖励
此处概述了验证者奖励制度的具体机制和规则。 通过将质押委托给正确投票的验证人来赚取奖励。 投票不正确会使验证者的质押面临[slashing罚没](../proposals/slashing.md)的风险。
### 基础知识
网络获得一部分网络[通胀](../terminology.md#inflation)奖励。 可用于支付时间奖励的Lamports数量是固定的并且必须根据它们的相对权重和参与度在所有质押节点之间平均分配。 加权单位称为[积分point](../terminology.md#point))。
一个epoch结束以后才能获得该epoch的奖励。
在每个epoch结束时将在该epoch期间获得的总积分求和并用于划分epoch通货膨胀的奖励求出一个积分值。 该值记录在将时间映射到点值的[sysvar](../terminology.md#sysvar)中。
在赎回期间质押计划会计算每个epoch的质押所赚取的点数再乘以该epoch的点值然后根据奖励账户的佣金设置将该金额的Lamports从奖励账户转移到质押和投票账户中。
### 经济学
一个epoch的积分取决于总的网络参与度。 如果参与epoch缩短则那些参与epoch的分值会更提高。
### 赚取积分
验证节点对于超出最大锁定范围的每一个正确投票都会获得一票积分,即,每次验证节点的投票帐户从其锁定列表中退出某个版位时,该投票就将成为该节点的根。
委派给该验证程序的抵押人根据其所持质押比例获得积分。 所获得的积分是投票信用和质押的乘积。
### 质押预热、冷却与取回
质押委托以后就不会立即生效。 他们必须首先经过一个预热期。 在此期间,质押的某些部分被视为“有效”,其余部分被视为“激活”。 变化发生在epoch边界上。
质押程序将更改总网络质押的速率限制在质押程序的`config::warmup_rate`中\(在当前实现中设置为每个epoch 25\)。
每个epoch可以预热的质押数量是前一个epoch的总有效质押总激活质押以及质押程序配置的预热率的函数。
冷却时间的工作方式相同。 取消抵押以后,某些部分将被视为“有效”,也被视为“停用”。 随着质押冷却,质押继续获得奖励并有罚没风险,但也可以取回。
引导质押则不需预热。
奖励是针对该epoch的“有效”质押部分进行支付的。
#### 预热示例
考虑在第 N 个 epoch 激活了 1,000 个单一质押的情况,网络预热率为 20第 N 个 epoch 的静态总网络质押为 2,000 个。
在第 N + 1 个阶段可激活的网络数量为400 \(2000的20%\),在第 N 个阶段,此示例质押是唯一激活的质押,因此有权使用所有的预热质押。
| epoch | 有效的 | 激活中 | 总有效 | 总激活中 |
|:----- | ----:| -----:| -----:| -----:|
| N-1 | | | 2,000 | 0 |
| N | 0 | 1,000 | 2,000 | 1,000 |
| N+1 | 400 | 600 | 2,400 | 600 |
| N+2 | 880 | 120 | 2,880 | 120 |
| N+3 | 1000 | 0 | 3,000 | 0 |
如果在epochN激活了2个质押(X和Y)他们将按其质押的比例获得20的一部分。 在每个epoch每个质押的有效和激活是前一个epoch的状态的函数。
| epoch | 有效 X | 激活 X | 有效 Y | 激活 Y | 总有效 | 总激活中 |
|:----- | ----:| -----:| ----:| ----:| -----:| -----:|
| N-1 | | | | | 2,000 | 0 |
| N | 0 | 1,000 | 0 | 200 | 2,000 | 1,200 |
| N+1 | 333 | 667 | 67 | 133 | 2,400 | 800 |
| N+2 | 733 | 267 | 146 | 54 | 2,880 | 321 |
| N+3 | 1000 | 0 | 200 | 0 | 3,200 | 0 |
### 提现
任何时候都只能提取超过有效+激活质押的Lamports。 这意味着在预热期间,实际上无法取回任何抵押。 在冷却期间,超过有效质押的任何代币都可能被取回\(activating == 0\)。 由于赚取的奖励会自动添加到质押中,因此通常只有在停用后才可以提现。
### 锁定
质押账户支持锁定的概念,直到指定的时间,提款账户的余额才能提现。 锁定指定为一个epoch高度即在可提取质押账户余额之前网络必须达到的最小epoch高度除非交易也由指定的托管人签署。 此信息在创建质押帐户时收集并存储在质押帐户状态的Lockup字段中。 更改授权的质押者或提款人也会受到锁定,因为这样的操作实际上就是转移代币。

View File

@@ -0,0 +1,28 @@
---
title: 同步
---
快速、可靠的同步是 Solana 实现超高吞吐量的最大原因。 同步到包含大量交易的传统区块链被称为区块。 通过在区块上同步,某笔交易只能在一个被称为“区块时间”的时间范围内完成。 工作量证明共识的区块时间通常非常大 \(~10分钟\) 才能最大限度地减少多个验证节点同时生成一个新有效区块的概率。 权益证明共识则不存在这样的限制,但它缺乏可靠的时间戳,验证节点无法决定进入区块的顺序。 最常见的做法是通过 [挂钟时间戳](https://en.bitcoin.it/wiki/Block_timestamp) 来标记每个区块。 由于时钟偏差和网络延迟的波动,时间戳只能保持一两小时的准确性。 要是该系统顺利运行,它们会增长区块时间,以便合理地确定每个区块上的中位时间戳总在不断增加。
Solana 采取了非常独特的做法,称为 _历史证明__PoH_。 带有加密证明“时间戳”的领导节点能够证明自上次确认以来,确实已经过了一段时间。 所有哈希到证明中的数据肯定都是在证明之前发生的。 然后该节点将新区块分享给验证节点,它们能够验证这些证据。 区块可以按照任何顺序甚至延迟好几年才传到验证节点那里。 通过这种可靠的同步保证Solana 能够将区块分解成更小的批量交易,称为 _条目entries_。 在达成任何共识之前,条目都会实时传输给验证节点。
在技术的角度Solana 从来都没有发送 _区块_ 但是会使用这个词语来描述验证节点对条目进行投票,最终取得 _确认_。 这样Solana 的确认时间就可以达到媲美两天苹果设备之间传输的速度。 当前的实现区块时间为 800 毫秒。
在这个模式下,条目很快就能传输到验证节点,因为领导节点可以将一组有效的交易归入条目。 验证节点早在投票其有效性之前就处理了这些条目。 以乐观的方式处理交易, 从收到最后一个条目到节点可以投票的期间,实际上不存在延迟。 如果对某个事件 **无法** 达成共识,节点只需要简单地回滚其状态。 这种优化处理技术早在 1981 年就发明了,被称为 [Optimistic Concurrency Control最优同值控制](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.65.4735)。 它也可以应用到区块链结构中,其中一个集群对代表整个账本的哈希进行投票,直到达到某个 _区块高度_。 Solana 通过使用最后一个条目的 PoH 哈希来轻松实现这一点。
## 与 VDF 的关系
Solana 在 2017 年 11 月首次介绍了历史证明技术在区块链中的使用。 次年 6 月, 斯坦福阐述了类似的技术,称为 [verifiable delay function可验证延迟函数](https://eprint.iacr.org/2018/601.pdf) 或简称为 _VDF_
VDF 的一个优势是验证所需时间非常短。 Solana 验证其延迟函数的方法它与创建所需的时间成正比。 拆分超过 4000 个核心 GPU它足以满足 Solana 的性能需要,但如果您请教上述论文的作者,他们可能会跟说 Solana 的方法在算法上很慢 \([因此](https://github.com/solana-labs/solana/issues/388)\),不应该叫作 VDF。 我们的观点是 VDF 一词应当指可证明的延迟方程,而不一定要满足某些性能特征。 在这个问题解决之前Solana 很可能会继续在其特定的应用程序 VDF 使用 PoH 一词。
PoH 与 VDF 之间的另一个区别是VDF 仅用于跟踪时间。 另一方面PoH 的哈希区块链包括应用程序观察到的任何数据的哈希部分。 这些数据是一把双刃剑。 一方面,数据提供了“证明历史”――数据在哈希之后肯定是存在的。 另一方面这意味着当数据被哈希的_时候_应用程序可以通过更改它来操纵区块链。 因此PoH 区块链并不是一个很好的随机数来源,但是没有这种数据的 VDF 却非常合适。 例如Solana 的 [领导人轮换算法](synchronization.md#leader-rotation),只来源于 VDF _高度_ ,而不是该高度的哈希值。
## 与共识机制的关系
历史证明不是一个共识机制,它用于改进 Solana 权益证明共识的性能。 它还用于改进数据平面协议的性能。
## 关于历史证明的更多信息
- [水时钟类比](https://medium.com/solana-labs/proof-of-history-explained-by-a-water-clock-e682183417b8)
- [历史证明概述](https://medium.com/solana-labs/proof-of-history-a-clock-for-blockchain-cf47a61a9274)

View File

@@ -0,0 +1,88 @@
---
title: 涡轮Turbine区块传播
---
Solana 集群使用一个叫做 _Turbine_ 的多层区块传播机制来广播所有节点的交易,该过程只产生少量的重复信息。 群集将自身分成小的节点集合,叫做 _neighborhoods邻居_。 每个节点负责与其附近的其他节点分享它收到的任何数据,同时将数据传播到其他邻居的小节点。 这样,每个节点只需要与少数节点通信。
在其插槽中,领导人节点会在第一个邻居的验证节点\(层 0\) 之间分配碎片。 每个验证节点在其邻居之间共享各自的数据,但也在下一层\(层 1\) 中将碎片转化为某个邻居的一个节点。 每个 1 层节点与邻居节点共享数据, 并在下一层重新传输到节点,以此类推,直到集群中的所有节点都收到了所有碎片。
## 邻居分配 - 权重选择
为了使数据扩散发挥作用,整个集群必须就如何将划分邻居达成一致。 为了实现这一点,所有公认的验证节点 \(TVU 节点\) 都按质押权重排列并存储在一个列表中。 然后以不同的方式索引这该列表,来查明邻居边界并相互重新传送。 例如,领导者只需选择第一个节点来构建层 0。 最高权重的质押持有者会让得票最多的节点优先成为领导者。 0 层和下层节点使用相同的逻辑来找到他们的邻居和下一个层节点。
为了减少攻击向量的可能性,每个碎片都向邻居发送一个随机树。 每个节点使用代表集群的同一批节点。 随机树是通过碎片集生成的它使用领导者ID、插槽和碎片索引来生成种子。
## 层和邻居结构
当前领导者将其初始广播到最多 `DATA_PLANE_FANOUT` 的节点。 如果该层 0 小于集群中的节点数,则数据平面扫描机制在下面添加图层。 随后的图层遵循这些约束来决定图层容量:每个邻居包含的 `DATA_PLANE_FANOUT` 节点。 0 层从带有扩散节点的 1 个邻居开始计算。 每个附加层中的节点数量增加了一个扩散因素。
如上文所述, 图层中的每个节点只能向邻居广播它的碎片,并且在某些下层邻居中只能播放一个节点, 不适用于集群中的每个 TVU 节点。 有一个理解该问题的好方法0 层从带有扩散节点的 1 个邻居开始计算1 层增加扩散邻居, 每个带有扩散节点和 2 层的节点都会有 `扩散 * 在 1 层的节点数量`,以此类推。
这样,每个节点进行通信最大只能达到 `2 * DATA_PLANE_FANOUT - 1` 个节点。
下面的图表显示了领导人如何在 0 层向 0 令居发送两个扩散的碎片,以及 0 邻居的节点如何彼此分享他们的数据。
![领导者向 0 层中的 0 号邻居发送碎片](/img/data-plane-seeding.svg)
下面的图表显示了 Neighborhood 0 如何向 Neighborhood 1和2 进行扩散。
![Neighborhood 0 向Neighborhood 1 和 2 号散播](/img/data-plane-fanout.svg)
最后,下图显示两个层集群都有 2 个扩散。
![具有两个扩散的两层集群](/img/data-plane.svg)
### 设置值
`DATA_PLANE_FANOUT` - 确定层 0 的大小。 后续的层由 `DATA_PLANE_FANOUT` 系数生成。 邻居的节点数量等于扩散值。 某个邻居要在添加新邻居之前被填满即如果某个邻居没有满它_必须_是最后一个。
目前,集群启动的时候就进行了配置。 未来这些参数可能会托管在链上,从而能够随着群组大小的变化而自行调整。
## 计算所需的 FEC 比率
Turbine 依赖于验证器之间数据包的再传输。 由于重新传输,任何网络宽包丢失都会加重, 同时每次访问的数据包未能到达目的地的概率也会增加。 FEC 比率需要考虑网络宽数据包丢失和传播深度。
碎片组是可以使用重建彼此的数据和编码数据集。 每个碎片组都有失败的可能性,其概率大致相当于数据包错误率超过 FEC。 如果验证节点无法重建碎片组,那就无法构建区块,它就必须先修复区块。
碎片组失败的概率可以通过二项分布计算。 如果 FEC 率是 `16:4`, 那么群组大小则为 20而且至少要有 4 个碎片失败才会导致整个组失败。 这等于 20 个碎片中的四条或更多个体失败的概率之和。
涡轮区块成功的概率为:
- 数据包失败的概率为: `P = 1 - (1 - network_packet_loss_rate)^2`
- FEC 率: `K:M`
- 尝试次数: `N = K + M`
- 碎片组失败率: `S = SUM of i=0 -> M for binomial(prob_failure = P, trials = N, failures = i)`
- 每个区块的碎片: `G`
- 区块成功率: `B = (1 - S) ^(G / N)`
- `i` 的精确结果的双向分布被定义为 `(N choose i) * P^i * (1 - P)^(N-i)`
例如:
- 网络丢包率为 15%。
- 50k Tps 的网络每秒产生 6400个碎片。
- FEC 率增加了每个块按 FEC 笔录的总碎片数。
其中 FEC 率为: `16:4`
- `G = 8000`
- `P=1 - 0.85 * 0.85 = 1 - 0.7225 = 0.2775`
- `S = i 的总和 =0 -> 4 为二项分布(prob_fail= 0.2775, trials = 20, fails = i) = 0.689414`
- `B = (1 - 0.689) ^(8000 / 20) = 10^-203`
其中 FEC 率为: `16:16`
- `G = 12800`
- `S = i 的总和 =0 -> 32 为二项分布(prob_fail= 0.2775, trials = 64, fails = i) = 0.002132`
- `B = (1 - 0.002132) ^(12800 / 32) = 0.42583`
其中 FEC 率为: `32:32`
- `G = 12800`
- `S = i 的总和 =0 -> 32 为二项分布(prob_fail= 0.2775, trials = 64, fails = i) = 0.000048`
- `B = (1 - 0.000048) ^(12800 / 64) = 0.99045`
## 邻居Neighborhoods
以下图表显示不同层中两个邻居之间的相互作用。 若要使邻居瘫痪,就需要从上面的邻居建立足够的节点\(擦除代码+1\)。 由于每个邻居都会从上层一个邻居的多个节点收到碎片, 因此上层网络出现大规模故障,才会产生不完整的数据。
![邻居的内部工作](/img/data-plane-neighborhood.svg)

View File

@@ -0,0 +1,59 @@
---
title: 安全投票签名
---
验证节点接收当前领导者的条目并提交这些条目的确认票数。 这个投票对安全提出了一种挑战,因为违反共识规则的伪造票可以用来罚没验证节点的质押。
验证节点通过一个采用不对称密钥来验证签名结果的交易,来对其选定的分叉进行投票。 其他实体可以使用验证节点的公钥验证此签名。 如果验证节点密钥被用来签名不正确的数据\(例如) 对账本的多个分叉进行投票\,那么节点的质押或资源可能会受到损害。
解决这个风险的办法是将单独的 _投票签名者_ 服务分割出来,评估每次投票以确保它没有违反罚没条件。
## 验证节点,投票签名者和质押者
当一个验证节点收到多个区块的同一个插槽时,它会跟踪所有可能的分叉,直到确定一个“最佳”的。 验证节点通过投票选择最好的分叉,通过投票签名者尽量减少投票造成违反共识和导致质押被罚没的可能性。
投票签名者对验证节点提议的投票进行评估,并且只在不违反罚没条件的情况下进行投票。 一个投票签名者只需要保持它所签署的投票和其余集群成员所签署的选票处于最低状态。 它不需要处理全部的交易。
质押者是一个对其质押资产具有控制权的实体。 质押者可以将其质押委托给投票签署人。 一旦某个质押被委托,投票人将代表所有受委托质押行驶投票权,同时为他们带来奖励。
目前,验证节点和投票签名者之间是 1:1 的关系,质押人将他们的全部质押委托给某一个的签名者。
## 签名服务
投票签名服务由 JSON RPC 服务器和请求处理器组成。 启动时,服务会在配置端口启动 RPC 服务器,等待验证节点请求。 请求类型包括如下几种:
1. 注册一个新的验证节点
- 请求必须包含验证节点身份\(公钥\)
- 请求必须通过验证节点的私钥签名
- 如果无法验证请求签名,服务就会丢弃该请求
- 服务为验证节点创建了一个新的投票权不对称密钥,并返回公钥作为响应
- 如果验证节点试图再次注册,服务会从原有的密钥对返回公钥
2. 签名投票
- 请求必须包含投票交易和所有验证数据
- 请求必须通过验证节点的私钥签名
- 如果无法验证请求签名,服务就会丢弃该请求
- 服务验证投票数据
- 服务返回交易的签名
## 验证节点投票
某个验证节点在启动时创建一个新的投票帐户,并通过提交一个新的“投票登记表”交易,在集群中进行注册。 集群中的其他节点处理该笔交易,并在活动集合中包含新的验证节点。 随后,验证节点在每次投票事件中提交了由验证节点投票私钥签名的“新投票”交易。
### 配置
验证节点通过签名服务的网络端点\(IP/port\) 配置。
### 注册
在启动时,验证节点使用 JSON RPC 注册其签名服务。 RPC 调用返回验证节点的投票公钥。 验证节点创建一个新的“投票注册”交易并把公钥包含进去,将其提交给集群。
### 投票收集
验证节点在上次投票期间查找集群中所有节点提交的票数。 然后把该信息提交给签名服务,并附有新的投票签名请求。
### 新的投票签名
验证节点创建了一笔“新投票”交易,并通过 JSON RPC 将其发送到签名服务。 RPC 请求还包括了投票检查数据。 如果成功RPC 调用将返回投票的签名。 如果失败RPC 调用将返回故障编码。

View File

@@ -0,0 +1,149 @@
---
title: Solana 集群
---
Solana维护着几个不同用途的集群。
在开始之前,请确保已首先安装了[Solana命令行工具](cli/install-solana-cli-tools.md)
浏览器:
- [http://explorer.solana.com/](https://explorer.solana.com/)。
- [http://solanabeach.io/](http://solanabeach.io/).
## Devnet开发者网络
- Devnet可以作为希望将Solana进行测试的任何人用户代币持有者应用开发者或验证者的游乐场。
- 应用程序开发人员应针对Devnet。
- 潜在的验证者应首先针对Devnet。
- Devnet和Mainnet Beta之间的主要区别
- Devnet代币是**不是真实的**
- Devnet包含用于空投的代币龙头用于应用程序测试
- Devnet可能会重置账本
- Devnet通常运行比Mainnet Beta更新的软件版本
- Devnet的八卦入口点`entrypoint.devnet.solana.com8001`
- Devnet的指标环境变量
```bash
export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=devnet,u=scratch_writer,p=topsecret"
```
- Devnet RPC URL`https://api.devnet.solana.com`
##### 示例 `solana` 命令行配置
```bash
solana config set --url https://api.devnet.solana.com
```
##### 示例 `solana-validator` 命令行
```bash
$ solana-validator \
--identity ~/validator-keypair.json \
--vote-account ~/vote-account-keypair.json \
--trusted-validator dv1LfzJvDF7S1fBKpFgKoKXK5yoSosmkAdfbxBo1GqJ \
--no-untrusted-rpc \
--ledger ~/validator-ledger \
--rpc-port 8899 \
--dynamic-port-range 8000-8010 \
--entrypoint entrypoint.devnet.solana.com:8001 \
--expected-genesis-hash EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG \
--wal-recovery-mode skip_any_corrupted_record \
--limit-ledger-size
```
`--trusted-validator`由 Solana 运行
## Testnet测试网
- Testnet是我们在实时群集上重点测试最新发布功能的地方尤其侧重于网络性能稳定性和验证程序行为。
- 集群[Tour de SOL](tour-de-sol.md)计划在Testnet上运行在该计划中我们接受恶意行为和对网络的攻击以帮助我们发现和消除错误或网络漏洞。
- Testnet代币**不是真实的**
- Testnet可能会重置账本。
- Testnet包括用于空投的代币水龙头用于应用程序测试
- Testnet通常运行比Devnet和Mainnet Beta都更新的软件版本
- 测试网 Gossip 入口: `entrypoint.testnet.solana.com:8001`
- Testnet的指标环境变量
```bash
export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=tds,u=testnet_write,p=c4fa841aa918bf8274e3e2a44d77568d9861b3ea"
```
- Testnet 的 RPC URL: `https://api.testnet.solana.com`
##### 示例 `solana` 命令行配置
```bash
solana config set --url https://api.testnet.solana.com
```
##### 示例 `solana-validator` 命令行
```bash
$ solana-validator \
--identity ~/validator-keypair.json \
--vote-account ~/vote-account-keypair.json \
--trusted-validator 5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on \
--trusted-validator ta1Uvfb7W5BRPrdGnhP9RmeCGKzBySGM1hTE4rBRy6T \
--trusted-validator Ft5fbkqNa76vnsjYNwjDZUXoTWpP7VYm3mtsaQckQADN \
--trusted-validator 9QxCLckBiJc783jnMvXZubK4wH86Eqqvashtrwvcsgkv \
--no-untrusted-rpc \
--ledger ~/validator-ledger \
--rpc-port 8899 \
--dynamic-port-range 8000-8010 \
--entrypoint entrypoint.testnet.solana.com:8001 \
--expected-genesis-hash 4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY \
--wal-recovery-mode skip_any_corrupted_record \
--limit-ledger-size
```
`--trusted-validator` 的身份是:
- `5D1fNXzv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on` - testnet.solana.com (Solana)
- `ta1Uvfb7W5BRPrdGnhP9RmeCKzBySGM1hTE4rBRy6T` - Break RPC 节点 (Solana)
- `Ft5fbkqNa76vnsjYNwjDZUXoTWpP7VYm3mtsaQckQADN` - Certus One
- `9QxCLckBiJc783jnMvXZubK4wH86Eqqvashtrwvcsgkv` - Algo|Stake
## Mainnet Beta主网 Beta
适用于早期代币持有者和启动合作伙伴,未经许可的持久集群。 当前,奖励和通货膨胀被禁用。
- 在Mainnet Beta上发行的代币是**真实的**SOL
- 如果您通过我们的硬币清单拍卖等方式支付了购买/发行代币的费用则这些代币将在Mainnet Beta上转移。
- 注意:如果您使用的是非命令行钱包,例如集群[Solflare](wallet-guide/solflare.md)则该钱包将始终连接到Mainnet Beta。
- Mainnet Beta 的 Gossip 入口: `entrypoint.mainnet-beta.solana.com:8001`
- Mainnet Beta的指标环境变量
```bash
export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=mainnet-beta,u=mainnet-beta_write,p=password"
```
- Mainnet Beta 的 RPC URL `https://api.mainnet-beta.solana.com`
##### 示例 `solana` 命令行配置
```bash
solana config set --url https://api.mainnet-beta.solana.com
```
##### 示例 `solana-validator` 命令行
```bash
$ solana-validator \
--identity ~/validator-keypair.json \
--vote-account ~/vote-account-keypair.json \
--trusted-validator 7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2 \
--trusted-validator GdnSyH3YtwcxFvQrVVJMm1JhTS4QVX7MFsX56uJLUfiZ \
--trusted-validator DE1bawNcRJB9rVm3buyMVfr8mBEoyyu73NBovf2oXJsJ \
--trusted-validator CakcnaRDHka2gXyfbEd2d3xsvkJkqsLw2akB3zsN1D2S \
--no-untrusted-rpc \
--ledger ~/validator-ledger \
--rpc-port 8899 \
--private-rpc \
--dynamic-port-range 8000-8010 \
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint2.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint3.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint4.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint5.mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--wal-recovery-mode skip_any_corrupted_record \
--limit-ledger-size
```
所有的四个 `--trusted-validator可信验证节点` 由 Solana 运行

View File

@@ -0,0 +1,115 @@
---
title: 后向兼容政策
---
随着Solana开发人员生态系统的发展围绕打破API和行为更改影响为Solana构建的应用程序和工具的明确期望也将越来越高。 在一个完美的世界中Solana的开发可以以非常快的速度继续进行而不会给现有开发人员造成任何问题。 但是,将需要做出一些妥协,因此,本文档尝试澄清和整理新发行版的过程。
### 期望值
- Solana软件版本包括APISDK和CLI工具(带有一些[exceptions](#exceptions))。
- Solana软件版本遵循语义版本控制更多详细信息请参见下文。
- 适用于`MINOR`版本的软件将与同一`MAJOR`版本上的所有软件兼容。
### 弃用过程
1. 在任何`PATCH``MINOR`版本中功能、API、端点等都可以标记为已弃用。
2. 根据代码升级的难度,某些功能将在几个发布周期内被弃用。
3. 在未来的`MAJOR`版本中,不赞成使用的功能将以不兼容的方式删除。
### 发布时间
Solana RPC API、Rust SDK、CLI工具和BPF程序SDK均已更新并随每个Solana软件版本一起提供并且应始终在特定`MINOR`版本的`PATCH`更新之间兼容。
#### 发布频道
- `edge`软件,其中包含最先进的功能,没有向后兼容策略
- 在Solana Tour de SOL测试网集群上运行的`beta`软件
- 在Solana Mainnet Beta和Devnet集群上运行的`stable`软件
#### 主要版本(x.0.0)
`MAJOR`版本(例如2.0.0)可能包含重大更改并删除了以前不推荐使用的功能。 客户端SDK和工具将开始使用在先前`MAJOR`版本中启用的新功能和端点。
#### 次要版本(1.x.0)
新功能和建议实施已添加到_new_版本的`MINOR`版本(例如1.4.0)中并且首先在Solana的Tour de SOL测试网集群上运行。 在测试网上运行时,`MINOR`版本被认为在`beta`发布渠道中。 在对这些更改进行了修补并证明是可靠的之后,`MINOR`版本将升级到`stable`发布渠道并部署到Mainnet Beta集群。
#### 修补程序版本(1.0.x)
低风险功能,不间断的更改以及安全性和错误修复作为`PATCH`版本发行版(例如1.0.11)的一部分提供。 补丁可以同时应用于`beta``stable`发布渠道。
### RPC API
补丁发布:
- Bug修复
- 安全修复
- 端点/功能弃用
次要版本:
- 新的RPC端点和功能
主要版本:
- 删除过时的功能
### Rust Crates
* [`solana-sdk`](https://docs.rs/solana-sdk/) - 用于创建交易和解析帐户状态的Rust SDK
* [`solana-program`](https://docs.rs/solana-program/) - 用于编写程序的Rust SDK
* [`solana-client`](https://docs.rs/solana-client/) - 用于连接RPC API的Rust客户端
* [`solana-cli-config`](https://docs.rs/solana-cli-config/) - 用于管理Solana CLI配置文件的Rust客户端
补丁发布:
- Bug修复
- 安全修复
- 性能提升
次要版本:
- 新的 API
主要版本:
- 删除过时的API
- 向后不兼容的行为更改
### CLI 工具
补丁发布:
- 错误和安全修复
- 性能提升
- 弃用子命令/参数
次要版本:
- 新的子命令
主要版本:
- 切换到先前主要版本中引入的新RPC API端点/配置。
- 删除过时的功能
### Runtime功能
Solana新的runtime功能已进行功能切换并手动激活。 Runtime功能包括引入新的本机程序sysvars和syscalls并改变他们的行为。 功能激活与群集无关因此可以在Mainnet-beta上激活之前在Testnet上建立置信度。
发布过程如下:
1. 新版本中包含新的runtime功能默认情况下已禁用
2. 一旦有足够的抵押验证程序升级到新版本就可以通过指令手动激活runtime功能开关
3. 该功能在下一个纪元开始时生效
### 基础架构变更
#### 公共API节点
Solana提供了公开可用的RPC API节点供所有开发人员使用。 Solana团队将尽最大努力将任何更改传达给主机端口限速行为可用性等。 但是我们建议开发人员依靠自己的验证器节点来阻止对Solana操作的节点的依赖。
#### 本地集群脚本和Docker映像
重大更改将仅限于`MAJOR`版本更新。 `MINOR``PATCH`更新应始终向后兼容。
### 例外
#### Web3 JavaScript SDK
Web3.JS SDK还遵循语义版本控制规范但与Solana软件版本分开提供。
#### 攻击向量
如果在现有代码中发现了新的攻击媒介,则可以根据问题的严重性来规避上述过程,以便快速部署修复程序。

View File

@@ -0,0 +1,5 @@
---
title: Web3 JavaScript API
---
请查看 [solana-web3](https://solana-labs.github.io/solana-web3.js/)。

View File

@@ -0,0 +1,72 @@
---
title: "调试"
---
Solana程序在链上运行因此在链外调试可能会很困难。 为了使调试程序更容易开发人员可以编写单元测试以通过Solana运行时直接测试其程序的执行情况或者运行允许RPC客户端与其程序进行交互的本地集群。
## 运行单元测试 {#running-unit-tests}
- [用Rust进行测试](developing-rust.md#how-to-test)
- [用C进行测试](developing-c.md#how-to-test)
## 记录 {#logging}
在程序执行期间,运行时,程序日志状态和错误消息均会出现。
有关如何从程序登录的信息,请参阅特定语言的文档:
- [从Rust程序记录日志](developing-rust.md#logging)
- [从C程序记录日志](developing-c.md#logging)
运行本地集群时,只要通过`RUST_LOG`日志掩码启用了日志日志就会写入stdout。 从程序开发的角度来看,仅关注运行时和程序日志,而不关注其余的集群日志会有所帮助。 为了专注于程序特定的信息,建议使用以下日志掩码:
`export
RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=info,solana_bpf_loader=debug,solana_rbpf=debug`
直接来自程序(而不是runtime) 的日志消息将以以下形式显示:
`Program log: <user defined message>`
## 错误处理 {#error-handling}
可以通过事务错误传达的信息量是有限的,但是有很多可能的失败点。 以下是可能的故障点,有关预期发生哪些错误以及在何处获取更多信息的信息:
- BPF加载程序可能无法解析程序这应该不会发生因为加载程序已经对程序的帐户数据进行了_最终处理_。
- `InstructionError::InvalidAccountData`将作为交易错误的一部分返回。
- BPF加载程序可能无法设置程序的执行环境
- `InstructionError::Custom(0x0b9f_0001)`将作为交易错误的一部分返回。 "0x0b9f_0001"是[`VirtualMachineCreationFailed`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/programs/bpf_loader/src/lib.rs#L44)的十六进制表示形式。
- BPF加载程序可能在程序执行过程中检测到致命错误(紧急情况,内存冲突,系统调用错误等)。
- `InstructionError::Custom(0x0b9f_0002)`将作为交易错误的一部分返回。 "0x0b9f_0002"是[`VirtualMachineFailedToRunProgram`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/programs/bpf_loader/src/lib.rs#L46)的十六进制表示。
- 程序本身可能返回错误
- `InstructionError::Custom(<user defined value>)`将被返回。 “用户定义的值”不得与任何[内置运行时程序错误](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/program_error.rs#L87)相冲突 。 程序通常使用枚举类型来定义从零开始的错误代码,因此它们不会冲突。
如果出现`VirtualMachineFailedToRunProgram`错误,则将有关失败原因的详细信息写入[程序的执行日志](debugging.md#logging)。
例如,涉及堆栈的访问冲突将如下所示:
`BPF程序4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM失败out of bounds
memory store (insn #615), addr 0x200001e38/8`
## 监控计算预算消耗 {#monitoring-compute-budget-consumption}
程序可以记录停止程序执行之前将允许的剩余计算单元数。 程序可以使用这些日志来包装希望分析的操作。
- [从Rust程序记录剩余的计算单元](developing-rust.md#compute-budget)
- [从C程序记录剩余的计算单元](developing-c.md#compute-budget)
有关更多信息,请参见[计算预算](developing/programming-model/runtime.md#compute-budget)。
## ELF转储 {#elf-dump}
可以将BPF共享对象的内部信息转储到文本文件中以更深入地了解程序的组成及其在运行时的工作方式。
- [创建Rust程序的转储文件](developing-rust.md#elf-dump)
- [创建C程序的转储文件](developing-c.md#elf-dump)
## 指令追踪 {#instruction-tracing}
在执行期间可以将运行时BPF解释器配置为记录每个执行的BPF指令的跟踪消息。 对于诸如精确指出导致内存访问冲突的运行时上下文之类的事情,这可能非常有用。
跟踪日志与[ELF转储](#elf-dump)一起可以提供更多参考(尽管跟踪会产生很多信息)。
要在本地集群中打开BPF解释器跟踪消息请将`RUST_LOG`中的`solana_rbpf`级别配置为`trace`。 例如:
`export RUST_LOG=solana_rbpf=trace`

View File

@@ -0,0 +1,11 @@
---
title: "部署"
---
![SDK 工具](/img/sdk-tools.svg)
如上图所示,程序作者创建了一个程序,将它编译成包含 BFF 字节代码的 ELF 共享对象,然后包含一笔特殊的 _deploy_ 交易,将其上传到 Solana 集群。 群集通过一个 _program ID_ 将其提供给客户端。 程序 ID 是部署时指定的 _地址_,用于在后续交易中引用程序。
一旦部署成功,持有程序的账户将被标记为可执行,并且其账户数据变得永久不可篡改。 如果程序需要更改(功能、补丁等...),新程序必须部署到一个新程序 ID。
Solana 命令行接口支持部署程序,更多信息请见 [`deploy`](cli/usage.md#deploy-program) 命令行使用 文档。

View File

@@ -0,0 +1,131 @@
---
title: "用 C 语言开发"
---
Solana 支持使用 C 和 C++ 语言编写链上的程序。
## 项目布局 {#project-layout}
C 项目规定如下:
```
/src/<program name>
/makefile
```
`makefile` 应该包含以下内容:
```bash
OUT_DIR := <path to place to resulting shared object>
include ~/.local/share/solana/install/active_release/bin/sdk/bpf/c/bpf.mk
```
Bpf-sdk可能不在上面指定的确切位置但是如果您根据[如何开发](#how-to-build)来设置环境,那么就是这样。
来看一下的 C 程序的[helloworld](https://github.com/solana-labs/example-helloworld/tree/master/src/program-c)示例。
## 如何开发 {#how-to-build}
首先设置环境:
- 从https://rustup.rs安装最新的Rust稳定版本
- 从https://docs.solana.com/cli/install-solana-cli-tools安装最新的Solana命令行工具
然后使用make构建
```bash
make -C <program directory>
```
## 如何测试 {#how-to-test}
Solana 使用 [Criterion](https://github.com/Snaipe/Criterion) 测试框架,并且在每次构建程序时都会执行测试,[如何开发](#how-to-build)。
要添加测试,请在源文件`test_<program
name>.c`旁边创建一个新文件,并使用标准测试用例填充它。 有关示例,请参见[helloworld C测试](https://github.com/solana-labs/example-helloworld/blob/master/src/program-c/src/helloworld/test_helloworld.c)或[Criterion文档](https://criterion.readthedocs.io/en/master),获取编写测试用例的信息。
## 程序入口点 {#program-entrypoint}
程序导出一个已知的入口点符号在调用程序时Solana运行时将查找并调用该入口点符号。 Solana支持多个[BPF加载程序版本](overview.md#versions),它们之间的入口点可能会有所不同。 程序必须为相同的加载器编写并部署。 有关更多详细信息,请参见[概览](overview#loaders)。
当前有两个受支持的加载器:[BPF加载器](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader.rs#L17)和[已弃用BFT加载器](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader_deprecated.rs#L14)。
它们都有相同的原始入口点定义,以下是运行时查找和调用的原始符号:
```c
extern uint64_t entrypoint(const uint8_t *input)
```
该入口点采用通用字节数组,其中包含序列化的程序参数(程序ID帐户指令数据等)。 为了反序列化参数,每个加载器都包含其自己的[帮助器函数](#Serialization)。
请参阅 [使用入口点的简单实例](https://github.com/solana-labs/example-helloworld/blob/bc0b25c0ccebeff44df9760ddb97011558b7d234/src/program-c/src/helloworld/helloworld.c#L37),来看看它们是如何配合使用的。
### 序列化 {#serialization}
请参阅[helloworld对反序列化功能的使用](https://github.com/solana-labs/example-helloworld/blob/bc0b25c0ccebeff44df9760ddb97011558b7d234/src/program-c/src/helloworld/helloworld.c#L43)。
每个加载程序都提供一个帮助程序功能,该功能将程序的输入参数反序列化为 C 类型:
- [BPF加载器反序列化](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/bpf/c/inc/solana_sdk.h#L304)
- [BPF 加载器已弃用的反序列化](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/bpf/c/inc/deserialize_deprecated.h#L25)
某些程序可能希望自己执行序列化,并且可以通过提供其自己的[原始入口点](#program-entrypoint)实现来实现。 请注意,提供的反序列化功能会将引用保留回序列化字节数组,以引用允许程序修改的变量(lamport帐户数据)。 这样做的原因是,在返回时,加载程序将读取这些修改,以便可以将其提交。 如果程序实现其自己的反序列化功能,则需要确保将程序希望进行的所有修改都写回到输入字节数组中。
有关加载程序如何序列化程序输入的详细信息,请参见[Input Parameter Serialization](overview.md#input-parameter-serialization)文档。
## 数据类型 {#data-types}
加载程序的反序列化助手函数将填充[SolParameters](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/bpf/c/inc/solana_sdk.h#L276)结构:
```c
/**
* 程序进入点输入数据被反序列化的结构。
*/
typef structt volt_
SolAccountInfo* ka; /** 指向SolAccountInfo阵列的指针 必须已经
指向一个 SolAccountInfos */
uint64_t ka_num; /** `ka`中的 SolAccountInfo 条目数 */
const uint8_t *数据; /** 指示数据指针*/
uint64_t data_len; /** 指令数据字节长度 */
const SolPubkey *program_id; /** 当前正在执行的程序 */
} Solameters;
```
“ ka”是指令引用帐户的有序数组并表示为[SolAccountInfo](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/bpf/c/inc/solana_sdk.h#L173)结构。 帐户在数组中的位置表示其含义例如在转移lamports时一条指令可以将第一个帐户定义为源将第二个帐户定义为目的地。
`AccountInfo`结构的成员是只读的,但`lamports``data`除外。 程序都可以根据[runtime执行策略](developing/programming-model/accounts.md#policy)对两者进行修改。 当一条指令多次引用相同的帐户时,数组中可能有重复的`SolAccountInfo`条目,但它们都指向原来的输入字节数组。 程序应谨慎处理这些情况,以避免对同一缓冲区的读/写重叠。 如果程序实现其自己的反序列化功能,则应注意适当地处理重复帐户。
`数据`是正在处理的[指令的指令数据](developing/programming-model/transactions.md#instruction-data)中的通用字节数组。
`program_id`是当前正在执行的程序的公钥。
## 堆Heap{#heap}
C 程序可以通过系统调用[`calloc`](https://github.com/solana-labs/solana/blob/c3d2d2134c93001566e1e56f691582f379b5ae55/sdk/bpf/c/inc/solana_sdk.h#L245)或者通过虚拟的 32 Kb heap 区域顶部实现它们自己的堆地址 x300000000。 堆区域也被 `calloc` 使用,因此如果一个程序实现了自己的堆,它不应该同时调用 `calloc`
## 日志 {#logging}
运行时提供了两个系统调用,这些系统调用将获取数据并将其记录到程序日志中。
- [`sol_log(const char*)`](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/bpf/c/inc/solana_sdk.h#L128)
- [`sol_log_64(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)`](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/bpf/c/inc/solana_sdk.h#L134)
[调试](debugging.md#logging) 章节有更多关于程序日志工作的信息。
## 计算预算 {#compute-budget}
使用系统调用[`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/bpf/c/inc/solana_sdk.h#L140)记录包含剩余编号的消息暂停执行之前程序可能消耗的计算单元数。
相关的更多信息,请参见[计算预算](developing/programming-model/runtime.md#compute-budget)。
## ELF转储 {#elf-dump}
可以将BPF共享对象的内部信息转储到文本文件中以更深入地了解程序的组成及其在运行时的工作方式。 转储将包含ELF信息以及所有符号和实现它们的指令的列表。 一些BPF加载程序的错误日志消息将引用发生错误的特定指令号。 可以在ELF转储中查找这些引用以标识有问题的指令及其上下文。
创建一个转储文件:
```bash
$ cd <program directory>
$ make dump_<program name>
```
## 示例 {#examples}
[Solana 程序库github](https://github.com/solana-labs/solana-program-library/tree/master/examples/c)代码库包含了 C 语言的例子集合。

View File

@@ -0,0 +1,272 @@
---
title: "用Rust开发"
---
Solana 支持使用[Rust](https://www.rust-lang.org/) 编程语言编写链上的程序。
## 项目布局 {#project-layout}
Solana Rust程序遵循典型的[Rust项目布局](https://doc.rust-lang.org/cargo/guide/project-layout.html)
```
/inc/
/src/
/Cargo.toml
```
但也必须包括:
```
/Xargo.toml
```
必须包含:
```
[target.bpfel-unknown-unknown.dependencies.std]
features = []
```
Solana Rust 程序可能会直接依赖于对方,以便在进行 [交叉程序调用](developing/programming-model/calling-between-programs.md#cross-program-invocations)时获得指令协助。 这样做时,重要的是不要拉入依赖程序的入口点符号,因为它们可能与程序本身的符号冲突。 为避免这种情况,程序应在 `Cargo.toml` 中定义一个 ` exclude_entrypoint `功能,并使用它来排除入口点。
- [定义特性](https://github.com/solana-labs/solana-program-library/blob/a5babd6cbea0d3f29d8c57d2ecbbd2a2bd59c8a9/token/program/Cargo.toml#L12)
- [排除入口点](https://github.com/solana-labs/solana-program-library/blob/a5babd6cbea0d3f29d8c57d2ecbbd2a2bd59c8a9/token/program/src/lib.rs#L12)
然后,当其他程序将此程序作为依赖项包括在内时,它们应该使用`exclude_entrypoint`功能来实现这一点。
- [不将入口点包含在内](https://github.com/solana-labs/solana-program-library/blob/a5babd6cbea0d3f29d8c57d2ecbbd2a2bd59c8a9/token-swap/program/Cargo.toml#L19)
## 项目依赖关系 {#project-dependencies}
至少Solana Rust程序必须引入[solana-program](https://crates.io/crates/solana-program)。
Solana BPF程序具有某些[限制](#Restrictions),可能会阻止将某些箱体作为依赖项包含进来或需要特殊处理。
例如:
- 要求架构的箱体Crates是官方工具链支持箱体的子集。 除非解决了这个问题并且没有将BPF添加到那些体系结构检查中否则没有解决方法。
- 箱体可能取决于Solana确定性程序环境中不支持的`rand`。 要包含`rand`相关的箱体,请参考[在 Rand 开发](#depending-on-rand)。
- 即使程序本身未包含堆栈溢出代码,箱体也可能会使堆栈溢出。 有关的更多信息,请参见[Stack](overview.md#stack)。
## 如何开发 {#how-to-build}
首先设置环境:
- 从https://rustup.rs/安装最新的Rust稳定版本
- 从https://docs.solana.com/cli/install-solana-cli-tools安装最新的Solana命令行工具
正常的cargo构建可用于针对您的主机构建程序该程序可用于单元测试
```bash
$ cargo build
```
要为可部署到集群的Solana BPF目标构建一个特定的程序例如SPL代币请执行以下操作
```bash
$ cd <the program directory>
$ cargo build-bpf
```
## 如何测试 {#how-to-test}
通过直接行使程序功能,可以通过传统的`cargo test`机制对Solana程序进行单元测试。
为了帮助在更接近实时集群的环境中进行测试,开发人员可以使用[`program-test`](https://crates.io/crates/solana-program-test)箱体。 `程序测试`箱体将启动运行时的本地实例,并允许测试发送多个事务,同时在测试期间保持状态。
有关更多信息,请参见[在sysvar示例中测试](https://github.com/solana-labs/solana-program-library/blob/master/examples/rust/sysvar/tests/functional.rs)来学习如何包含一条指令syavar帐户由程序发送和处理。
## 程序入口点 {#project-entrypoint}
程序导出一个已知的入口点符号在调用程序时Solana运行时将查找并调用该入口点符号。 Solana支持多个[BPF加载程序版本](overview.md#versions),它们之间的入口点可能会有所不同。 程序必须为相同的加载器编写并部署。 有关更多详细信息,请参见[概览](overview#loaders)。
当前有两个受支持的加载器:[BPF加载器](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader.rs#L17)和[已弃用BFT加载器](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader_deprecated.rs#L14)。
它们都有相同的原始入口点定义,以下是运行时查找和调用的原始符号:
```rust
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
```
该入口点采用通用字节数组,其中包含序列化的程序参数(程序ID帐户指令数据等)。 为了反序列化参数,每个加载程序都包含其自己的包装宏,该宏导出原始入口点,反序列化参数,调用用户定义的指令处理函数并返回结果。
您可以在此处找到入口点宏:
- [BPF加载程序的入口点宏](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/entrypoint.rs#L46)
- [BPF 加载器不推荐使用的入口点宏](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/entrypoint_deprecated.rs#L37)
入口点宏调用的程序定义的指令处理功能必须具有以下形式:
```rust
pub type ProcessInstruction =
fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult;
```
请参阅 [使用入口点的简单实例](https://github.com/solana-labs/example-helloworld/blob/c1a7247d87cd045f574ed49aec5d160aefc45cf2/src/program-rust/src/lib.rs#L15),来看看它们是如何配合使用的。
### 参数反序列化 {#parameter-deserialization}
每个加载程序都提供一个帮助程序功能该功能将程序的输入参数反序列化为Rust类型。 入口点宏会自动调用反序列化帮助器:
- [BPF加载器反序列化](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/entrypoint.rs#L104)
- [BPF 加载器已弃用的反序列化](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/entrypoint_deprecated.rs#L56)
某些程序可能希望自己执行反序列化,并且可以通过提供其自己的[原始入口点](#program-entrypoint)实现来实现。 请注意,提供的反序列化功能会将引用保留回序列化字节数组,以引用允许程序修改的变量(lamport帐户数据)。 这样做的原因是,在返回时,加载程序将读取这些修改,以便可以将其提交。 如果程序实现其自己的反序列化功能,则需要确保将程序希望进行的所有修改都写回到输入字节数组中。
有关加载程序如何序列化程序输入的详细信息,请参见[Input Parameter Serialization](overview.md#input-parameter-serialization)文档。
### 数据类型 {#data-types}
加载程序的入口点宏使用以下参数调用程序定义的指令处理器功能:
```rust
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8]
```
程序ID是当前正在执行的程序的公钥。
帐户是指令引用的帐户的有序切片,并表示为[AccountInfo](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/account_info.rs#L10)结构。 帐户在数组中的位置表示其含义例如在转移lamports时一条指令可以将第一个帐户定义为源将第二个帐户定义为目的地。
`AccountInfo`结构的成员是只读的,但`lamports``data`除外。 程序都可以根据[runtime执行策略](developing/programming-model/accounts.md#policy)对两者进行修改。 这两个成员都受`RustRefCell`构造的保护,因此必须借用它们以对其进行读写。 这样做的原因是它们都指向原始输入字节数组,但是帐户片中可能有多个条目指向同一帐户。 使用`RefCell`确保程序不会通过多个`AccountInfo`结构意外地对相同的基础数据执行重叠的读/写操作。 如果程序实现其自己的反序列化功能,则应注意适当地处理重复帐户。
指令数据是正在处理的[指令的指令数据](developing/programming-model/transactions.md#instruction-data)中的通用字节数组。
## 堆Heap{#heap}
Rust程序通过定义自定义[`global_allocator`](https://github.com/solana-labs/solana/blob/8330123861a719cd7a79af0544617896e7f00ce3/sdk/program/src/entrypoint.rs#L50)直接实现堆。
程序可以根据其特定需求实现自己的`global_allocator`。 相关的更多信息,请参考[自定义heap示例](#examples)。
## 限制 {#restrictions}
链上Rust程序支持Rust的大多数libstdlibcore和liballoc以及许多第三方包装箱。
由于这些程序在资源受限的单线程环境中运行,因此存在一定的局限性,并且必须是确定性的:
- 无法访问
- `rand`
- `std::fs`
- `std::net`
- `std::os`
- `std::future`
- `std::net`
- `std::process`
- `std::sync`
- `std::task`
- `std::thread`
- `std::time`
- 有限的访问权:
- `std::hash`
- `std::os`
- 二进制代码在周期和调用深度上在计算上都非常昂贵,应该尽量避免。
- 应该避免字符串格式化,因为它在计算上也很昂贵。
- 不支持 `println!``print!`应该使用Solana [logging helpers](#logging)。
- 运行时对程序在一条指令的处理过程中可以执行的指令数施加了限制。 相关的更多信息,请参见[计算预算](developing/programming-model/runtime.md#compute-budget)。
## 在Rand开发 {#depending-on-rand}
程序必须确定性地运行,因此不能使用随机数。 有时,即使程序不使用任何随机数功能,程序也可能依赖于自己的`rand`。 如果程序依赖于`rand`则编译将失败因为对Solana没有对`get-random`进行支持。 报错通常如下所示:
```
error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets
--> /Users/jack/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.14/src/lib.rs:257:9
|
257 | / compile_error!("\
258 | | target is not supported, for more information see: \
259 | | https://docs.rs/getrandom/#unsupported-targets\
260 | | ");
| |___________^
```
要解决此依赖性问题,请将以下依赖性添加到程序的`Cargo.toml`中:
```
getrandom = { version = "0.1.14", features = ["dummy"] }
```
## 日志 {#logging}
Rust的`println`宏在计算上很昂贵,不被支持。 而是提供了辅助宏[`msg!`](https://github.com/solana-labs/solana/blob/6705b5a98c076ac08f3991bb8a6f9fcb280bf51e/sdk/program/src/log.rs#L33)。
`msg!` 有两种形式:
```rust
msg!("A string");
```
或者
```rust
msg!(0_64, 1_64, 2_64, 3_64, 4_64);
```
两者都将输出结果到程序日志。 如果程序愿意,他们可以使用`format!`来模拟`println!`
```rust
msg!("Some variable: {:?}", variable);
```
[debugging](debugging.md#logging)章节提供了有关使用程序日志的更多信息,[Rust示例](#examples)包含一个日志记录示例。
## 恐慌Panicking{#panicking}
默认情况下Rust 的`panic!``assert!`和内部恐慌结果被打印到[程序日志](debugging.md#logging)。
```
INFO solana_runtime::message_processor] Finalized account CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ
INFO solana_runtime::message_processor] Call BPF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ
INFO solana_runtime::message_processor] Program log: Panicked at: 'assertion failed: `(left == right)`
left: `1`,
right: `2`', rust/panic/src/lib.rs:22:5
INFO solana_runtime::message_processor] BPF program consumed 5453 of 200000 units
INFO solana_runtime::message_processor] BPF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ failed: BPF program panicked
```
### 自定义恐慌处理器 {#custom-panic-handler}
程序可以通过提供自己的实现来覆盖默认的紧急处理程序。
首先在程序的`Cargo.toml`中定义`custom-panic`功能。
```toml
[features]
default = ["custom-panic"]
custom-panic = []
```
然后提供应急处理程序的自定义实现:
```rust
#[cfg(all(feature = "custom-panic", target_arch = "bpf"))]
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
solana_program::msg!("program custom panic enabled");
solana_program::msg!("{}", info);
}
```
在上面的代码段中,显示了默认的实现,但是开发人员可以用更适合他们需求的东西代替它。
默认情况下支持完整的紧急消息的副作用之一是程序会产生将Rust的更多`libstd`实现引入程序共享对象的代价。 典型的程序将已经引入了相当数量的`libstd`,并且可能不会注意到共享对象大小的增加。 但是那些通过避免使用`libstd`显式地试图变得很小的程序可能会产生很大的影响(~25kb)。 为了消除这种影响,程序可以为自己的自定义应急处理程序提供空的实现。
```rust
#[cfg(all(feature = "custom-panic", target_arch = "bpf"))]
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
// Do nothing to save space
}
```
## 计算预算 {#compute-budget}
使用系统调用[`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/program/src/log.rs#L102)]记录包含剩余编号的消息暂停执行之前程序可能消耗的计算单元数。
相关的更多信息,请参见[计算预算](developing/programming-model/runtime.md#compute-budget)。
## ELF转储 {#elf-dump}
可以将BPF共享对象的内部信息转储到文本文件中以更深入地了解程序的组成及其在运行时的工作方式。 转储将包含ELF信息以及所有符号和实现它们的指令的列表。 一些BPF加载程序的错误日志消息将引用发生错误的特定指令号。 可以在ELF转储中查找这些引用以标识有问题的指令及其上下文。
创建一个转储文件:
```bash
$ cd <program directory>
$ cargo build-bpf --dump
```
## 示例 {#examples}
[Solana 程序库github](https://github.com/solana-labs/solana-program-library/tree/master/examples/rust)代码库包含了Rust例子集合。

View File

@@ -0,0 +1,48 @@
---
title: "示例"
---
## Hello World
Hello World是一个演示项目展示了如何使用Solana Javascript API以及Rust和C程序来构建、部署、与Solana区块链程序进行交互。
该项目包括:
- 链上的Hello World程序
- 向某个帐户发送 hello 并获取发送次数。
### 构建并运行
首先获取示例代码的最新版本:
```bash
$ git clone https://github.com/solana-labs/example-helloworld.git
$ cd example-helloworld
```
接下来按照git仓库[README](https://github.com/solana-labs/example-helloworld/blob/master/README.md)中的步骤进行操作。
## 中断
[Break](https://break.solana.com/)是一个React应用程序它使用户对Solana网络的速度和高性能有熟悉的感觉。 你能_干倒_Solana区块链吗 在15秒的游戏过程中每次单击按钮或击键都会向集群发送新的事务。 尽可能快地敲击键盘,并观看交易实时实时完成,而网络正在正常运行!
可以在我们的Devnet、Testnet和Mainnet Beta网络上来玩这个游戏。 在Devnet和Testnet可以免费测试用户可从网络水龙头获得空投测试代币。 在Mainnet Beta上用户每次玩耍需要支付0.08 SOL的费用。 可以通过本地密钥库钱包或通过扫描Trust Wallet的QR码以传输代币为测试帐户转入代币。
[点击这里玩这个游戏](https://break.solana.com/)
### 构建并运行
首先获取示例代码的最新版本:
```bash
$ git clone https://github.com/solana-labs/break.git
$ cd break
```
接下来按照git仓库的[README](https://github.com/solana-labs/break/blob/master/README.md)中的步骤进行操作。
## 特定语言
- [Rust](developing-rust.md#examples)
- [C](developing-c.md#examples)

View File

@@ -0,0 +1,61 @@
---
title: "常见问题解答"
---
在编写或与Solana程序进行交互时经常会遇到一些常见的问题或困难。 以下是有助于回答这些问题的资源。
如果还没有解决您的问题那么Solana[#developers](https://discord.gg/RxeGBH)Discord频道是一个不错的资源。
## `CallDepth`错误
此错误意味着跨程序调用超出了允许的调用深度。
请参见[跨程序调用调用深度](developing/programming-model/calling-between-programs.md#call-depth)
## `CallDepthExceeded`错误
此错误表示已超出BPF堆栈深度。
请参阅[通话深度](overview.md#call-depth)
## 计算约束
请参见[计算约束](developing/programming-model/runtime.md#compute-budget)
## 浮动Rust类型
请参见[浮动支持](overview.md#float-support)
## Heap大小
请参见[heap](overview.md#heap)
## 无效账户数据
该程序错误的发生可能有很多原因。 通常,这是由于在指令中的错误位置或与正在执行的指令不兼容的帐户向程序传递了程序不期望的帐户所致。
当执行跨程序指令而忘记提供您正在调用的程序的帐户时,程序的实现也可能导致此错误。
## 无效指示数据
尝试反序列化指令时,可能会发生此程序错误,请检查传入的结构是否与指令完全匹配。 字段之间可能会有一些填充。 如果程序实现了Rust的`Pack`特性,则尝试打包和解压缩指令类型`T`以确定程序期望的确切编码:
https://github.com/solana-labs/solana/blob/v1.4/sdk/program/src/program_pack.rs
## MissingRequiredSignature
有些说明要求帐户必须是签名者;如果预计将对帐户进行签名但未签名,则返回此错误。
当执行需要签名程序地址的跨程序调用时,程序的实现也可能会导致此错误,但是传递的签名者种子将传递给[`invoke_signed`](developing/programming-model/calling-between-programs.md)与用于创建程序地址[`create_program_address`](developing/programming-model/calling-between-programs.md#program-derived-addresses)的签名者种子不匹配。
## `rand` Rust依赖导致编译失败
请参见[Rust项目依赖项](developing-rust.md#project-dependencies)
## Rust限制
请参见[Rust限制](developing-rust.md#restrictions)
## 堆栈大小
请参见[stack](overview.md#stack)

View File

@@ -0,0 +1,127 @@
---
title: "概述"
---
开发人员可以编写自己的程序并将其部署到Solana区块链。
[Helloworld示例](examples.md#helloworld)是了解如何编写、构建、部署、与链上程序交互的入门材料。
## Berkley数据包过滤器(BPF)
Solana链上程序通过[LLVM编译器基础结构](https://llvm.org/)编译为[可执行和可链接格式(ELF)](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format),其中包含了[Berkley数据包过滤器(BPF)](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter)字节码的变体。
由于Solana使用LLVM编译器基础结构因此可以使用可以针对LLVM的BPF后端的任何编程语言编写程序。 Solana当前支持用Rust和C / C ++编写程序。
BPF提供了有效的[指令集](https://github.com/iovisor/bpf-docs/blob/master/eBPF.md),可以在解释的虚拟机中执行,也可以作为高效的即时编译原生执行指令。
## 内存映射
Solana BPF程序使用的虚拟地址内存映射是固定的其布局如下
- 程序代码从0x100000000开始
- 堆栈数据从0x200000000开始
- 堆数据从0x300000000开始
- 程序输入参数从0x400000000开始
上面的虚拟地址是起始地址,但是程序可以访问存储器映射的子集。 如果程序尝试读取或写入未授予其访问权限的虚拟地址则会panic并且将返回`AccessViolation`错误,其中包含尝试违反的地址和大小。
## 堆栈 {#stack}
BPF使用堆栈帧而不是可变堆栈指针。 每个堆栈帧的大小为4KB。
如果程序违反了该堆栈帧大小,则编译器将报告溢出情况,作为警告。
例如:`Error: Function
_ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E
Stack offset of -30728 exceeded max offset of -4096 by 26632 bytes, please
minimize large stack variables`
该消息标识哪个符号超出了其堆栈框架但是如果它是Rust或C ++符号,则名称可能会被修饰。 要对Rust符号进行解码请使用[rustfilt](https://github.com/luser/rustfilt)。 上面的警告来自Rust程序因此已取消组合的符号名称为
```bash
$ rustfilt _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E
curve25519_dalek::edwards::EdwardsBasepointTable::create
```
要对C ++符号进行解码请使用binutils中的`c++filt`
报告警告而不提示错误的原因是,即使程序不使用该功能,某些从属工具也可能包含违反堆栈框架限制的功能。 如果程序在运行时违反了堆栈大小,则会报告`AccessViolation`错误。
BPF堆栈帧占用一个从0x200000000开始的虚拟地址范围。
## 调用深度
程序被限制为必须快速运行并且为了方便起见程序的调用堆栈被限制为最大深度为64帧。
## 堆Heap
程序可以直接在C中或通过Rust `alloc` API来访问运行时堆。 为了促进快速分配使用了一个简单的32KB凹凸堆。 堆不支持`free``realloc`,因此请慎重使用它。
在内部程序可以访问从虚拟地址0x300000000开始的32KB内存区域并且可以根据程序的特定需求实现自定义堆。
- [Rust程序堆使用情况](developing-rust.md#heap)
- [C程序堆使用情况](developing-c.md#heap)
## 浮点数支持
程序支持Rust的float操作的有限子集尽管由于涉及的开销而强烈不建议使用。 如果程序尝试使用不受支持的浮点运算,则运行时将报告未解决的符号错误。
## 静态可写入数据
程序共享对象不支持可写共享数据。 使用相同的共享只读代码和数据在多个并行执行之间共享程序。 这意味着开发人员不应在程序中包含任何静态可写变量或全局变量。 将来,可以添加写时复制机制以支持可写数据。
## 签名分配
BPF 指令集不支持 [签名分配](https://www.kernel.org/doc/html/latest/bpf/bpf_design_QA.html#q-why-there-is-no-bpf-sdiv-for-signed-divide-operation)。 添加签名分配的指令是一个考虑因素。
## 加载程序loader
程序由运行时加载程序部署并执行,目前有两个受支持的加载程序[BPF加载程序](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader.rs#L17)]和[不建议使用BPF加载程序](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader_deprecated.rs#L14)
加载程序可能支持不同的应用程序二进制接口,因此开发人员必须为其编写程序并将其部署到同一加载程序中。 如果为一个装载程序编写的程序被部署到另一个装载程序,则由于程序输入参数的反序列化不匹配,结果通常是`AccessViolation`错误。
出于所有实际目的应始终将程序编写为以最新的BPF加载程序为目标并且最新的加载程序是命令行界面和javascript API的默认设置。
有关为特定加载程序实现程序的语言特定信息,请参见:
- [Rust程序入口点](developing-rust.md#program-entrypoint)
- [C程序入口点](developing-c.md#program-entrypoint)
### 部署
BPF程序部署是将BPF共享对象上载到程序帐户的数据中并标记该帐户可执行文件的过程。 客户端将BPF共享对象分成较小的部分并将其作为[`Write`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/loader_instruction.rs#L13)的指令数据发送向加载程序的指令,加载程序在此将数据写入程序的帐户数据。 一旦收到所有片段,客户端就会向加载程序发送[`Finalize`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/loader_instruction.rs#L30)指令然后加载程序将验证BPF数据是否有效并将程序帐户标记为_executable_。 一旦程序帐户被标记为可执行,随后的交易就可以发出该程序要处理的指令。
当指令针对可执行的BPF程序时加载程序将配置程序的执行环境序列化程序的输入参数调用程序的入口点并报告遇到的任何错误。
有关更多信息,请参见[deploying](deploying.md)
### 输入参数序列化
BPF加载程序将程序输入参数序列化为字节数组然后将其传递到程序的入口点由程序负责在链上反序列化它。 不赞成使用的加载器和当前加载器之间的变化之一是,输入参数以某种方式序列化,导致各种参数落在对齐字节数组内的对齐偏移量上。 这允许反序列化实现直接引用字节数组并提供指向程序的对齐指针。
有关序列化的特定于语言的信息,请参见:
- [Rust程序参数反序列化](developing-rust.md#parameter-deserialization)
- [C程序参数反序列化](developing-c.md#parameter-deserialization)
最新的加载器按如下方式序列化程序输入参数(所有编码均为小尾数法)
- 8字节无符号帐户数
- 对于每个帐户
- 1个字节指示这是否是重复帐户如果不是重复帐户则值为0xff否则值为与其重复的帐户的索引。
- 填充7个字节
- 如果不重复
- 1个字节的填充
- 1个字节的布尔值如果account是签名者则为true
- 1字节布尔值如果帐户可写则为true
- 1字节布尔值如果帐户可执行则为true
- 4个字节的填充
- 32个字节的帐户公钥
- 该帐户的所有者公共密钥的32个字节
- 该帐户拥有的Lamport的8字节无符号数
- -8个字节的无符号帐户数据字节数
- x字节的帐户数据
- 10k字节的填充用于重新分配
- 足够的填充以将偏移量对齐到8个字节。
- 8个字节的租用纪元
- 8个字节的无符号指令数据
- -x字节的指令数据
- -程序ID的32个字节

View File

@@ -0,0 +1,91 @@
---
title: "账户"
---
## 在交易之间存储状态
如果程序需要在交易之间存储状态则可以使用_accounts_进行存储。 帐户类似于Linux等操作系统中的文件。 就像文件一样,帐户可以保存任意数据,并且该数据会在程序的生存期内持续存在。 帐户也像文件一样,包含元数据,该元数据告诉运行时允许谁访问数据以及如何访问数据。
与文件不同,该帐户包含文件生存期内的元数据。 该存在时间用“代币”表示即称为_lamports_的许多局部原生代币。 帐户保存在验证节点的内存中,并支付[“rent”](#rent)留在那里。 每个验证节点都会定期扫描所有帐户并收取租金。 任何掉落到零零花的账户都将被清除。 如果帐户包含足够数量的Lamport也可以标记为[rent-exempt](#rent-exemption)。
与Linux用户使用路径查找文件的方式相同Solana客户端使用_address_查找帐户。 该地址是一个256位公共密钥。
## 签名者
交易可以包括与交易所引用的账户的公共密钥相对应的数字[签名](terminology.md#signature)。 当存在相应的数字签名时它表示该帐户的私钥持有人已签名并因此“授权”了该交易因此该帐户称为_signer_。 帐户是否为签名者将作为帐户元数据的一部分传达给程序。 然后,程序可以使用该信息来制定权限决策。
## 只读
事务可以[指示](transactions.md#message-header-format)它引用的某些帐户被视为_只读帐户_以便能够在事务之间进行并行帐户处理。 运行时允许多个程序同时读取只读帐户。 如果程序尝试修改只读帐户,则运行时将拒绝该事务。
## 可执行
如果某个帐户在其元数据中被标记为“可执行”,则将其视为可以通过将帐户的公钥包含在指令的[程序ID](transactions.md#program-id)中来执行的程序。 在成功的程序部署过程中,拥有该帐户的加载程序将帐户标记为可执行文件。 例如在BPF程序部署期间一旦加载程序确定帐户数据中的BPF字节码有效则加载程序会将程序帐户永久标记为可执行文件。 一旦可执行,运行时将强制该帐户的数据(程序) 是不可变的。
## 创建
为了创建一个帐户客户端生成一个_keypair_并使用`SystemProgram::CreateAccount`指令注册其公共密钥,并预先分配了固定的存储大小(以字节为单位)。 当前帐户数据的最大大小为10MB。
帐户地址可以是任意的256位值并且高级用户可以使用一些机制来创建派生地址(`SystemProgram::CreateAccountWithSeed`[`Pubkey::CreateProgramAddress`](calling-between-programs.md#program-derived-addresses))。
从未通过系统程序创建的帐户也可以传递到程序。 当指令引用以前尚未创建的帐户时程序将通过系统程序拥有的帐户该帐户具有0个Lamport和0个数据。 但是,该帐户将反映它是否是该交易的签名者,因此可以用作授权。 在这种情况下,授权机构向程序传达与帐户的公共密钥相关联的私有密钥的持有者对交易进行了签名。 该程序可能知道该帐户的公钥,也可能将其记录在另一个帐户中,并表示对该程序控制或执行的资产或操作具有某种所有权或授权。
## 程序的所有权和分配
创建的帐户由称为System程序的内置程序初始化为_owned_并适当地称为_system account_。 帐户包含“所有者”元数据。 所有者是一个程序ID。 如果运行时的ID与所有者匹配则运行时将授予该程序对该帐户的写访问权限。 对于System程序运行时允许客户端转移Lamport并且重要的是_转移_帐户所有权这意味着将所有者更改为其他程序ID。 如果某个帐户不属于某个程序,则仅允许该程序读取其数据并将该帐户记入贷方。
## 承租
使帐户在Solana上保持活动状态会产生称为_rent_的存储成本因为集群必须积极维护数据以处理其上的任何将来的事务。 这与比特币和以太坊不同,在比特币和以太坊中,存储帐户不会产生任何费用。
租金是在当前时期通过事务在第一次访问(包括初始帐户创建) 时通过运行时从帐户余额中扣除的,如果没有交易,则在每个时期一次。 该费用目前是固定费率,以字节乘以时期为单位。 该费用将来可能会更改。
为了简化租金计算,租金始终是在一个完整的时期内收取的。 租金不是按比例分配的,这意味着部分时期既不收费也不退款。 这意味着,在创建帐户时,收取的首笔租金不是针对当前的部分时期,而是针对下一个完整的时期而预先收取的租金。 随后的租金收取是未来的进一步时期。 另一方面,如果一个已出租的帐户的余额降到另一个租金费用的中间时期以下,则该帐户将在当前时期继续存在,并在即将到来的时期开始时立即被清除。
如果帐户保持最低余额,则可以免交租金。 此免租金描述如下。
### 租金计算
注意:租金率将来可能会改变。
在撰写本文时在testnet和mainnet-beta群集上固定租金为每字节纪元19.055441478439427兰特。 一个[epoch](terminology.md#epoch)的目标是2天(对于devnet租金为每字节纪元 0.3608183131797095 lamports长度为54m36s长)。
计算得出该值的目标是每兆字节天0.01 SOL(与每兆字节年3.56SOL完全匹配)
```text
租金19.055441478439427=10_000_000(0.01SOL)*365(一年中大约一天)/(1024*1024)(1MiB)/(365.25/2)(一年中的纪元)
```
租金计算以`f64`精度完成最终结果在Lamports中被截断为`u64`
租金计算包括帐户大小的帐户元数据(地址、所有者、lamports等)。 因此用于租金计算的最小帐户为128字节。
例如创建的帐户初始转移了10,000 lamports并且没有其他数据。 租金会在创建时立即从中扣除从而产生7,561 lamports的余款
```text
租金2,439=19.055441478439427(租金)*128字节(最小帐户大小)*1(纪元)
帐户余额7,561=10,000(转让的兰特)-2,439(此帐户的时期租金)
```
即使没有活动帐户余额也将在下一个时期减少到5,122 lamports
```text
帐户余额5,122=7,561(当前余额) -2,439(该帐户的租金,用于某个时期)
```
因此如果转移的兰特小于或等于2439则最小尺寸帐户将在创建后立即删除。
### 免租金
另外通过存入至少2年的租金可以使一个帐户完全免收租金。 每次帐户余额减少时都会进行检查,一旦余额低于最低金额,便会立即从租金中扣除。
运行时要求程序可执行帐户免租金,以免被清除。
注意:请使用[`getMinimumBalanceForRentExemption`RPC端点](developing/clients/jsonrpc-api.md#getminimumbalanceforrentexemption)计算特定帐户大小的最小余额。 以下计算仅是说明性的。
例如一个程序可执行文件的大小为15,000字节则需要105,290,880 lamports(=〜0.105SOL) 的余额才能免租:
```text
105,290,880=19.055441478439427(手续费率)*(128+15_000)(包括元数据的帐户大小)*((365.25/2)*2)(以2年为周期)
```

View File

@@ -0,0 +1,198 @@
---
title: 程序之间的调用
---
## 跨程序调用 {#cross-program-invocations}
Solana运行时允许程序通过称为跨程序调用的机制相互调用。 程序之间的调用是通过一个程序调用另一个程序的指令来实现的。 调用程序将暂停,直到被调用的程序完成对指令的处理为止。
例如,客户可以创建一个交易来修改两个帐户,每个帐户都由单独的链上程序拥有:
```rust,ignore
let message = Message::new(vec![
token_instruction::pay(&alice_pubkey),
acme_instruction::launch_missiles(&bob_pubkey),
]);
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
```
客户可以代之以允许`acme`程序代表客户方便地调用`token`指令:
```rust,ignore
let message = Message::new(vec![
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
]);
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
```
给定两个链上程序`token`和`acme`,每个程序分别执行指令`pay()`和`launch_missiles()`,可以通过调用`token`模块中定义的函数来实现acme跨程序调用
```rust,ignore
mod acme {
use token_instruction;
fn launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
...
}
fn pay_and_launch_missiles(accounts: &[AccountInfo]) -> Result<()> {
let alice_pubkey = accounts[1].key;
let instruction = token_instruction::pay(&alice_pubkey);
invoke(&instruction, accounts)?;
launch_missiles(accounts)?;
}
```
Solana的运行时内置了`invoke()`,它负责通过指令的`program_id`字段将给定指令路由到`token`程序。
请注意,`invoke` 要求调用者传递被调用指令所需的所有帐户。 这意味着可执行帐户(与指令的程序ID匹配的帐户) 和传递给指令处理器的帐户都可以。
在调用`pay()`之前,运行时必须确保`acme`没有修改`token`拥有的任何帐户。 它通过在`acme`调用`invoke`时将运行时策略应用于帐户的当前状态,而不是在`acme`指令开始时将帐户的初始状态应用到帐户的当前状态。 在`pay()`完成之后,运行时必须再次通过应用运行时的策略来确保`token`不会修改`acme`拥有的任何帐户,但是这次使用`token`程序ID。 最后,在`pay_and_launch_missiles()`完成之后运行时必须再次使用runtime 策略,这通常是正常的时间,但要使用所有更新的`pre_ *`变量。 如果执行直到`pay()`为止的`pay_and_launch_missiles()`没有任何无效的帐户更改,`pay()`没有任何无效的更改,并且从`pay()`一直执行到`pay_and_launch_missiles()`返回则没有无效的更改然后runtime可以过渡性地假设`pay_and_launch_missiles()`总体上没有进行无效的帐户更改,因此可以提交所有这些帐户修改。
### 需要特权的指令
运行时使用授予调用者程序的特权来确定可以扩展给被调用者的特权。 在这种情况下,特权是指签名者和可写帐户。 例如,如果调用者正在处理的指令包含签名者或可写帐户,则调用者可以调用也包含该签名者和/或可写帐户的指令。
此特权扩展依赖于程序是不可变的这一事实。 对于`acme`程序,运行时可以安全地将事务签名视为`token`指令签名。 当运行时看到`token`指令引用`alice_pubkey`时,它将在`acme`指令中查找密钥,以查看该密钥是否与已签名的帐户相对应。 在这种情况下,它会这样做并因此授权`token`程序修改Alice的帐户。
### 程序签名帐户
程序可以使用[程序派生地址](#program-derived-addresses)发出包含未在原始交易中签名的已签名帐户的指令。
为了用程序派生的地址签署一个帐户,程序可以`invoke_signed()`。
```rust,ignore
invoke_signed(
&instruction,
accounts,
&[&["First addresses seed"],
&["Second addresses first seed", "Second addresses second seed"]],
)?;
```
### 调用深度
跨程序调用允许程序直接调用其他程序但当前深度限制为4。
### 可重入
目前,可重入仅限于以固定深度为上限的直接自递归。 此限制可防止程序可能在不知道稍后会被调用回状态的情况下从中间状态调用另一个程序的情况。 直接递归可以使程序在被调用时完全控制其状态。
## 程序派生地址
程序派生的地址允许在[程序之间调用](#cross-program-invocations)时使用以编程方式生成的签名。
使用程序派生的地址,可以向某个程序授予某个帐户的权限,然后再将该权限转移给另一个帐户。 这是可能的,因为该程序可以在授予权限的事务中充当签名者。
例如如果两个用户想要对Solana中的游戏结果押注则他们每个人都必须将其下注的资产转移到将遵守协议的某些中介机构上。 当前在Solana中尚无办法将此中介程序作为程序来实现因为该中介程序无法将资产转让给获胜者。
对于许多DeFi应用程序来说此功能是必需的因为它们要求将资产转移到托管代理直到发生确定新所有者的事件为止。
- 去中心化交易所,可在匹配的买价和卖价之间转移资产。
- 将资产转移给获胜者的拍卖。
- 收集奖品并将其重新分配给获奖者的游戏或预测市场。
程序派生地址:
1. 允许程序控制特定的地址(称为程序地址),以使任何外部用户都无法生成带有这些地址签名的有效交易。
2. 允许程序以编程方式签名通过[跨程序调用](#cross-program-invocations)调用的指令中存在的程序地址。
在这两个条件下,用户可以安全地将链上资产的权限转移或分配给程序地址,然后程序可以自行决定在其他地方分配该权限。
### 程序地址的私钥
程序地址不在ed25519曲线上因此没有与之关联的有效私钥因此无法生成签名。 虽然它没有自己的私钥,但是程序可以使用它来发布包含程序地址作为签名者的指令。
### 基于哈希的生成程序地址
程序地址是使用256位抗映像前哈希函数从种子和程序ID的集合中确定性地得出的。 程序地址一定不能位于ed25519曲线上以确保没有关联的私钥。 如果发现地址位于曲线上,则在生成过程中将返回错误。 对于给定的种子和程序ID集合这种情况大约发生50/50的变化。 如果发生这种情况,可以使用另一组种子或种子凹凸(附加的8位种子) 来查找曲线外的有效程序地址。
程序的确定性程序地址遵循与使用`system_instruction::create_address_with_seed`实现的用`SystemInstruction::CreateAccountWithSeed`创建的帐户类似的派生路径。
作为参考,该实现如下:
```rust,ignore
pub fn create_address_with_seed(
base: &Pubkey,
seed: &str,
program_id: &Pubkey,
) -> Result<Pubkey, SystemError> {
if seed.len() > MAX_ADDRESS_SEED_LEN {
return Err(SystemError::MaxSeedLengthExceeded);
}
Ok(Pubkey::new(
hashv(&[base.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(),
))
}
```
程序可以使用种子确定性地派生任意数量的地址。 这些种子可以象征性地标识地址的使用方式。
来自`Pubkey`::
```rust,ignore
///生成派生程序地址
/// *种子,用于派生密钥的符号关键字
/// * program_id为该地址派生的程序
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError>
```
### 使用程序地址
客户可以使用`create_program_address`函数生成目标地址。
```rust,ignore
//确定性地导出托管密钥
let escrow_pubkey = create_program_address(&[&["escrow"]], &escrow_program_id);
//使用该密钥构造传输消息
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey, 1),
]);
//处理将一个1令牌传输到托管的消息
client.send_and_confirm_message(&[&alice_keypair], &message);
```
程序可以使用相同的函数来生成相同的地址。 程序在下面的功能中从程序地址发出`token_instruction::transfer`,就好像它具有用于签署交易的私钥一样。
```rust,ignore
fn transfer_one_token_from_escrow(
program_id: &Pubkey,
keyed_accounts: &[KeyedAccount]
) -> Result<()> {
//用户提供目的地
let alice_pubkey = keyed_accounts[1].unsigned_key();
//确定性派生托管公钥。
let escrow_pubkey = create_program_address(&[&["escrow"]], program_id);
//创建转移指令
let instruction = token_instruction::transfer(&escrow_pubkey, &alice_pubkey, 1);
//运行时确定性地从当前
//执行程序ID和提供的关键字。
//如果派生地址与指令中标记为已签名的键匹配
//然后该密钥被接受为已签名。
invoke_signed(&instruction, &[&["escrow"]])?
}
```
### 需要签名者的说明
用`create_program_address`生成的地址与任何其他公钥都没有区别。 运行时验证地址是否属于程序的唯一方法是使程序提供用于生成地址的种子。
运行时将在内部调用`create_program_address`,并将结果与指令中提供的地址进行比较。
## 示例
请参阅 [使用Rust开发](developing/on-chain-programs/developing-rust.md#examples) 和 [使用C开发](developing/on-chain-programs/developing-c.md#examples)以获取有关如何使用跨程序调用的示例。

View File

@@ -0,0 +1,7 @@
---
title: "概述"
---
一个[app](terminology.md#app)通过向Solana集群发送带有一个或多个[指令](transactions.md#instructions)的[事务](transactions.md)来与之交互。 Solana [runtime](runtime.md) 会将这些指令传递给应用程序开发人员事先部署的[程序](terminology.md#program)。 例如,一条指令可能告诉程序将[lamports](terminology.md#lamports)从一个[账户](accounts.md)转移到另一个或创建一个交互协议来管理Lamport的转移方式。 指令针对每个交易顺序地和原子级地执行。 如果任何指令无效,则交易中的所有帐户更改都将被丢弃。
要立即开始开发,您可以构建,部署和运行[示例](developing/on-chain-programs/examples.md)之一。

View File

@@ -0,0 +1,86 @@
---
title: "运行时runtime"
---
## 程序功能
运行时仅允许所有者程序借记帐户或修改其数据。 然后,程序为客户是否可以修改其拥有的帐户定义其他规则。 在系统程序的情况下,它允许用户通过识别交易签名来转移灯饰。 如果看到客户端使用密钥对的_private key_签署了交易则表明客户端已授权代币传输。
换句话说,给定程序拥有的整个帐户集可以视为键值存储,其中键是帐户地址,值是特定于程序的任意二进制数据。 程序作者可以决定如何管理尽可能多的帐户的整个程序状态。
运行时执行每个事务的指令后,它将使用帐户元数据来验证是否违反了访问策略。 如果程序违反了该策略,则运行时会丢弃交易中所有指令所做的所有帐户更改,并将交易标记为失败。
### 政策
程序处理完指令后,运行时将验证程序仅执行了允许的操作,并且结果符合运行时策略。
该策略如下:
- 只有帐户所有者可以更改所有者。
- 仅在帐户可写时。
- 并且仅在该帐户不可执行时
- 并且仅当数据为零初始化或为空时。
- 未分配给该程序的帐户的余额不能减少。
- 只读帐户和可执行帐户的余额可能不会更改。
- 只有系统程序拥有该帐户,只有系统程序才能更改数据大小。
- 只有所有者可以更改帐户数据。
- 如果该帐户可写。
- 并且该帐户不可执行。
- 可执行文件是单向的(false->true),只有帐户所有者可以设置。
- 没有与此帐户相关联的rent_epoch的修改。
## 计算预算 {#compute-budget}
为了防止程序滥用计算资源,必须为事务中的每个指令分配一个计算预算。 预算由计算单元组成,在程序执行各种操作和程序可能不会超出的范围时会消耗这些计算单元。 当程序消耗了全部预算或超出界限时,运行时将暂停程序并返回错误。
以下操作会产生计算成本:
- 执行BPF指令
- 呼叫系统电话
- 记录
- 创建程序地址
- 跨程序调用
- ...
对于跨程序调用,所调用的程序将继承其父级的预算。 如果被调用的程序消耗了预算或超出了限制,则整个调用链和父级都将停止。
可以在Solana SDK中找到当前的[计算预算](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L65)。
例如,如果当前预算是:
```rust
max_units: 200,000,
log_units: 100,
log_u64_units: 100,
create_program address units: 1500,
invoke_units: 1000,
max_invoke_depth: 4,
max_call_depth: 64,
stack_frame_size: 4096,
log_pubkey_units: 100,
```
然后程序
- 如果不执行其他操作则可以执行200,000条BPF指令
- 可以记录2,000条日志消息
- 堆栈使用量不能超过4k
- 不能超过BPF通话深度64
- 不能超过4个级别的跨程序调用。
由于计算预算在程序执行时会逐渐消耗,因此总预算消耗将是其执行的各种操作成本的组合。
在运行时,程序可以记录剩余多少计算预算。 有关更多信息,请参见[debugging](developing/on-chain-programs/debugging.md#monitoring-compute-budget-consumption)。
预算值取决于功能启用,请查看计算预算的[new](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L97)函数列出预算的编制方式。 需要了解[功能](runtime.md#features)的工作方式以及正在使用的群集上启用的功能才能确定当前预算的值。
## 新的功能
随着Solana的发展可能会引入新功能或补丁这些新功能或补丁会更改集群的行为以及程序的运行方式。 行为的变化必须在群集的各个节点之间进行协调,如果节点不协调,则这些变化可能会导致共识破裂。 Solana支持一种称为运行时功能的机制以方便平滑地采用更改。
运行时功能是历时协调的事件,将在集群中发生一个或多个行为更改。 对Solana的新更改将更改行为使用功能门进行包装并且默认情况下处于禁用状态。 然后使用Solana工具激活功能将其标记为未决一旦标记为未决该功能将在下一个时期激活。
要确定激活了哪些功能,请使用[Solana命令行工具](cli/install-solana-cli-tools.md)
```bash
solana功能状态
```
如果首先遇到问题请确保您使用的Solana工具版本与`solana cluster-version`返回的版本相匹配。 如果它们不匹配,请[安装正确的工具套件](cli/install-solana-cli-tools.md)。

View File

@@ -0,0 +1,113 @@
---
title: "交易"
---
程序执行从将[transaction](terminology.md#transaction)提交到集群开始。 Solana运行时将执行一个程序以按顺序和原子方式处理事务中包含的每个[指令](terminology.md#instruction)。
## 交易剖析
本节介绍事务的二进制格式
### 交易格式
事务包含签名的[compact-array](#compact-array-format),然后是[message](#message-format)。 签名数组中的每个项目都是给定消息的[数字签名](#signature-format)。 Solana运行时验证签名数是否与[message heade](#message-header-format)的前8位中的数字匹配。 它还验证每个签名是否由与邮件帐户地址数组中相同索引处的公钥相对应的私钥签名。
#### 签名格式
每个数字签名均为ed25519二进制格式占用64个字节。
### 邮件格式
一条消息包含[header](#message-header-format),然后是[account address](#account-addresses-format)的紧凑数组,然后是最近的[blockhash](#blockhash-format),紧接着是[指令](#instruction-format)的紧凑数组。
#### 邮件标题格式
消息头包含三个无符号的8位值。 第一个值是包含交易中所需签名的数量。 第二个值是那些对应的只读帐户地址的数量。 邮件标题中的第三个值是不需要签名的只读帐户地址的数量。
#### 帐户地址格式
要求签名的地址出现在帐户地址数组的开头,其地址首先请求写访问权限,然后请求只读帐户。 不需要签名的地址跟在需要签名的地址之后,再次是先读写帐户,然后是只读帐户。
#### Blockhash区块链哈希值格式
区块哈希包含一个32字节的SHA-256哈希。 它用于指示客户最后一次观察分类帐的时间。 当区块哈希值太旧时,验证程序将拒绝交易。
### 指令格式
一条指令包含一个程序ID索引后跟一个帐户地址索引的紧凑数组然后是一个不透明的8位数据的紧凑数组。 程序ID索引用于标识可以解释不透明数据的链上程序。 程序ID索引是消息的帐户地址数组中帐户地址的无符号8位索引。 帐户地址索引是同一数组中的无符号8位索引。
### 紧凑数组格式
紧凑数组被序列化为数组长度,随后是每个数组项。 数组长度是一种特殊的多字节编码称为compact-u16。
#### Compact-u16格式
一个compact-u16是16位的多字节编码。 第一个字节在其低7位中包含该值的低7位。 如果该值大于0x7f则设置高位并将该值的后7位放入第二个字节的低7位。 如果该值大于0x3fff则设置高位并将该值的其余2位放入第三个字节的低2位。
### 帐户地址格式
帐户地址是32字节的任意数据。 当地址需要数字签名时运行时会将其解释为ed25519密钥对的公钥。
## 指示
每个[instruction](terminology.md#instruction)都指定一个程序,应传递给该程序的交易帐户的子集以及一个传递给该程序的数据字节数组。 该程序解释数据数组并在指令指定的帐户上运行。 该程序可以成功返回,或者带有错误代码。 错误返回会导致整个事务立即失败。
程序通常提供帮助程序功能来构造它们支持的指令。 例如系统程序提供了以下Rust助手来构建[`SystemInstruction::CreateAccount`](https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs#L63)指令:
```rust
pub fn create_account(
from_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
space: u64,
owner: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, true),
];
Instruction::new(
system_program::id(),
&SystemInstruction::CreateAccount {
lamports,
space,
owner: *owner,
},
account_metas,
)
}
```
可以在这里找到:
https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs#L220
### 程序ID
指令的[程序ID](terminology.md#program-id)指定将处理该指令的程序。 程序帐户的所有者指定应使用哪个加载程序来加载和执行程序,并且数据包含有关运行时应如何执行程序的信息。
对于[已部署的BPF程序](developing/on-chain-programs/overview.md)所有者是BPF加载程序帐户数据包含BPF字节码。 一旦成功部署,程序帐户便会被加载程序永久标记为可执行文件。 运行时将拒绝指定不可执行程序的事务。
与已部署的程序不同,[runtime facilities](developing/runtime-facilities/programs.md)的处理方式有所不同因为它们直接内置在Solana运行时中。
### 帐户
指令引用的帐户代表链上状态,并且既作为程序的输入又作为输出。 有关帐户的更多信息,请参见[帐户](accounts.md)章节。
### 指令数据
每个指令都带有一个通用字节数组,该字节数组与帐户一起传递给程序。 指令数据的内容是特定于程序的,通常用于传达程序应执行的操作以及这些操作在帐户所包含的内容之外可能需要的任何其他信息。
程序可以自由指定如何将信息编码到指令数据字节数组中。 数据编码方式的选择应考虑到解码的开销,因为该步骤是由链上程序执行的。 据观察,一些常见的编码(例如Rust的bincode) 效率很低。
[Solana程序库的代币程序](https://github.com/solana-labs/solana-program-library/tree/master/token)提供了一个示例,说明如何有效地对指令数据进行编码,但是请注意,这种方法仅支持固定大小的类型。 代币利用[Pack](https://github.com/solana-labs/solana/blob/master/sdk/program/src/program_pack.rs)特征来对代币指令和代币的指令数据进行编码/解码帐户状态。
## 签名
每笔交易都明确列出了交易指令所引用的所有帐户公钥。 这些公钥的子集每个都带有交易签名。 这些签名向链上程序发出信号,表明帐户持有人已授权交易。 通常,程序使用授权来允许借记帐户或修改其数据。 有关如何将授权传达给程序的更多信息,请参见[帐户](accounts.md#signers)。
## 最近的区块链哈希值
事务包括最近的[blockhash](terminology.md#blockhash),以防止重复并赋予事务生命周期。 任何与上一个交易完全相同的交易都会被拒绝,因此添加一个更新的区块哈希可以使多个交易重复完全相同的操作。 事务还具有由Blockhash定义的生存期因为Blockhash太旧的任何事务都将被拒绝。

View File

@@ -0,0 +1,95 @@
---
title: "构建程序"
---
Solana包含少量内置程序这些程序是运行验证程序节点所必需的。 与第三方程序不同,内置程序是验证程序实现的一部分,可以作为群集升级的一部分进行升级。 可能会进行升级以添加功能,修复错误或提高性能。 个别指令的界面更改很少(如果有的话)发生。 相反,当需要更改时,将添加新指令,并且将先前的指令标记为已弃用。 应用程序可以在自己的时间表上进行升级,而无需担心升级过程中的中断。
对于每个内置程序将提供每个支持的指令的程序ID和说明。 事务可以混合和匹配来自不同程序的指令,也可以包括来自已部署程序的指令。
## 系统程序
创建帐户并在它们之间转移Lamport
- 程序ID`11111111111111111111111111111111`
- 说明:[SystemInstruction](https://docs.rs/solana-sdk/VERSION_FOR_DOCS_RS/solana_sdk/system_instruction/enum.SystemInstruction.html)
## 配置程序
将配置数据添加到链和允许对其进行修改的公钥列表中
- 程序ID`Config1111111111111111111111111111111111111111`
- 说明:[config_instruction](https://docs.rs/solana-config-program/VERSION_FOR_DOCS_RS/solana_config_program/config_instruction/index.html)
与其他程序不同Config程序未定义任何单独的指令。 它只有一条隐式指令,即“存储”指令。 它的指令数据是一组密钥,用于控制对帐户的访问以及存储在其中的数据。
## 权益计划
创建权益账户并将其委托给验证者
- 程序ID`Stake11111111111111111111111111111111111111`
- 说明: [StakeInstruction](https://docs.rs/solana-stake-program/VERSION_FOR_DOCS_RS/solana_stake_program/stake_instruction/enum.StakeInstruction.html)
## 投票程序
创建投票账户并对区块进行投票
- 程序ID`Vote111111111111111111111111111111111111111`
- 说明:[VoteInstruction](https://docs.rs/solana-vote-program/VERSION_FOR_DOCS_RS/solana_vote_program/vote_instruction/enum.VoteInstruction.html)
## BPF加载程序
将程序添加到链中并执行它们。
- 程序ID`BPFLoader11111111111111111111111111111111111`
- 说明:[LoaderInstruction](https://docs.rs/solana-sdk/VERSION_FOR_DOCS_RS/solana_sdk/loader_instruction/enum.LoaderInstruction.html)
BPF加载程序将其自身标记为它创建的用于存储程序的可执行帐户的“所有者”。 当用户通过程序ID调用指令时Solana运行时将同时加载您的可执行帐户及其所有者BPF Loader。 然后运行时将您的程序传递给BPF加载程序以处理指令。
## Secp256k1程序
验证secp256k1公钥恢复操作(ecrecover)。
- 程序ID`KeccakSecp256k11111111111111111111111111111111`
- 说明:[new_secp256k1_instruction](https://github.com/solana-labs/solana/blob/c1f3f9d27b5f9534f9a37704bae1d690d4335b6b/programs/secp256k1/src/lib.rs#L18)
Secp256k1程序处理一条指令该指令将在指令数据中序列化的以下结构的计数作为第一个字节
```
struct Secp256k1SignatureOffsets {
secp_signature_key_offset: u16, // offset to [signature,recovery_id,etherum_address] of 64+1+20 bytes
secp_signature_instruction_index: u8, // instruction index to find data
secp_pubkey_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
secp_signature_instruction_index: u8, // instruction index to find data
secp_message_data_offset: u16, // offset to start of message data
secp_message_data_size: u16, // size of message data
secp_message_instruction_index: u8, // index of instruction data to get message data
}
```
伪代码的操作:
```
process_instruction() {
for i in 0..count {
// i'th index values referenced:
instructions = &transaction.message().instructions
signature = instructions[secp_signature_instruction_index].data[secp_signature_offset..secp_signature_offset + 64]
recovery_id = instructions[secp_signature_instruction_index].data[secp_signature_offset + 64]
ref_eth_pubkey = instructions[secp_pubkey_instruction_index].data[secp_pubkey_offset..secp_pubkey_offset + 32]
message_hash = keccak256(instructions[secp_message_instruction_index].data[secp_message_data_offset..secp_message_data_offset + secp_message_data_size])
pubkey = ecrecover(signature, recovery_id, message_hash)
eth_pubkey = keccak256(pubkey[1..])[12..]
if eth_pubkey != ref_eth_pubkey {
return Error
}
}
return Success
}
```
这允许用户在事务中指定用于签名和消息数据的任何指令数据。 通过指定一种特殊的指令sysvar也可以从事务本身接收数据。
交易成本将计算要验证的签名数乘以签名成本验证乘数。
### 优化注意事项
该操作将必须在(至少部分) 反序列化之后进行但是所有输入都来自交易数据本身这使得它相对于交易处理和PoH验证并行执行相对容易。

View File

@@ -0,0 +1,80 @@
---
title: Sysvar群集数据
---
Solana通过[`sysvar`](terminology.md#sysvar)帐户向程序公开了各种群集状态数据。 这些帐户填充在[`solana-program`开发工具](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/sysvar/index.html)中发布的已知地址以及帐户布局中,并在下面概述。
要将sysvar数据包括在程序操作中请在事务处理的帐户列表中传递sysvar帐户地址。 可以像其他任何帐户一样在您的指令处理器中读取该帐户。 始终以*只读方式*访问sysvars帐户。
## 时钟
Clock sysvar包含有关群集时间的数据包括当前时间段时期和估计的Wall-clock Unix时间戳。 它在每个插槽中更新。
- 地址:`SysvarC1ock11111111111111111111111111111111`
- 布局:[时钟](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/clock/struct.Clock.html)
- 栏位:
- `slot`:当前的插槽
- `epoch_start_timestamp`此epoch中第一个插槽的Unix时间戳。 在纪元的第一个时隙中,此时间戳与`unix_timestamp`(如下所示) 相同。
- `epoch`:当前纪元
- `leader_schedule_epoch`:已经为其生成了领导者时间表的最新纪元
- `unix_timestamp`此插槽的Unix时间戳。
每个插槽都有一个基于历史证明的估计持续时间。 但实际上,时隙的流逝可能比此估计更快或更慢。 结果将基于投票验证程序的oracle输入生成插槽的Unix时间戳。 此时间戳的计算方式为投票提供的时间戳估计的赌注加权中位数,以自纪元开始以来经过的预期时间为界。
更明确地说:对于每个插槽,每个验证节点提供的最新投票时间戳用于生成当前时隙的时间戳估计(自投票时间戳以来经过的插槽假定为Bank:: ns_per_slot)。 每个时间戳估计都与委派给该投票帐户的股份相关联,以按股份创建时间戳分布。 除非将自`epoch_start_timestamp`以来的经过时间与预期经过时间相差超过25否则将中值时间戳记用作`unix_timestamp`
## Epoch时间表
这时间段表sysvar包含在创世中设置的时间段常量并允许计算给定时间段中的时隙数给定时隙的时间段等。(注意:时间段时间表与[`leader时间表不同`](terminology.md#leader-schedule))
- 地址:`SysvarEpochSchedu1e111111111111111111111111`
- 布局:[EpochSchedule](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/epoch_schedule/struct.EpochSchedule.html)
## 费用
Fees sysvar包含当前广告位的费用计算器。 它会根据费用调节器在每个时段进行更新。
- 地址:`SysvarFees111111111111111111111111111111111`
- 布局:[费用](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/sysvar/fees/struct.Fees.html)
## 指示
指令sysvar在处理消息时在消息中包含序列化的指令。 这允许程序指令引用同一事务中的其他指令。 阅读有关[指令自省](implemented-proposals/instruction_introspection.md)的更多信息。
- 地址:`` Sysvar1nstructions1111111111111111111111111` ``
- 布局:[指令](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/sysvar/instructions/type.Instructions.html)
## 最近的区块散列值
最近的区块哈希系统变量包含活动的最近区块哈希及其关联的费用计算器。 它在每个插槽中更新。
- 地址:`SysvarRecentB1ockHashes11111111111111111111`
- 布局:[RecentBlockhashes](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/sysvar/recent_blockhashes/struct.RecentBlockhashes.html)
## 承租
Rent sysvar包含租金。 目前,该比率是静态的,并且是根据发生率设定的。 通过手动激活功能可以修改租金燃烧百分比。
- 地址:`SysvarRent111111111111111111111111111111111`
- 赞成:[出租](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/rent/struct.Rent.html)
## 插槽哈希
SlotHashes sysvar包含插槽父库的最新哈希。 它在每个插槽中更新。
- 地址:`SysvarS1otHashes111111111111111111111111111111`
- 布局:[SlotHashes](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/slot_hashes/struct.SlotHashes.html)
## 插槽历史
SlotHistory sysvar包含在最后一个时期出现的插槽的位向量。 它在每个插槽中更新。
- 地址:`SysvarS1otHistory11111111111111111111111111111`
- 布局:[SlotHistory](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/slot_history/struct.SlotHistory.html)
## 权益历史
StakeHistory sysvar包含每个时期群集范围内的权益激活和停用的历史记录。 在每个时间段开始时都会对其进行更新。
- 地址:`` SysvarStakeHistory11111111111111111111111111` ``
- 布局:[StakeHistory](https://docs.rs/solana-program/VERSION_FOR_DOCS_RS/solana_program/stake_history/struct.StakeHistory.html)

View File

@@ -0,0 +1,15 @@
---
title: 发展历史
---
2017年11月Anatoly Yakovenko发布了一份白皮书描述了历史证明一种在彼此不信任的计算机之间保持时间同步的技术。 根据Anatoly在Qualcomm、Mesosphere和Dropbox上设计分布式系统的先前经验他知道可靠的时钟可使网络同步变得非常简单。 如果同步很简单,那么生成的网络将很快受到限制,仅受网络带宽限制。
Anatoly深刻地见识了没有时钟的区块链系统例如比特币和以太坊。账本在全球范围内难以扩展到每秒15笔交易以上而Visa等中心化支付系统需要达到65,000账本tps的峰值。 很明显,如果没有一个时钟,永远都不会成就他们梦寐以求的全球支付系统或全球超级计算机。 当Anatoly解决了让彼此不信任的计算机按时达成协议的问题时他拥有的将40年分布式系统研究是带入区块链世界的关键武器。 账本最终的集群将提高速度到10倍100倍或1000倍甚至10,000倍
Anatoly的操作开始于私有代码库并以C编程语言实现。 Greg Fitzgerald以前曾在半导体巨头高通公司Qualcomm Incorporated与Anatoly合作他鼓励他用Rust编程语言重新实现该项目。 Greg致力于LLVM编译器基础结构该基础结构是Clang C/C++编译器和Rust编译器的基础。 Greg声称该语言的安全保证将提高软件生产效率并且缺少垃圾收集器将使该程序能够像用C编写的程序一样完美执行。Anatoly试了一下仅两周后他就将整个代码库迁移到了Rust。 成功了! 这个计划将全球所有交易整合到一个可扩展的单个区块链中Anatoly称其为Loom项目。
在2018年2月13日Greg开始对Anatoly的白皮书的第一个开源执行进行原型设计。 该项目已在织机组织中以Silk的名称发布到GitHub。 2月28日Greg发布了他的第一个版本演示了将在短短半秒钟内完成1万笔已签名交易的验证和处理。 不久之后另一位前高通的研究人员Stephen Akridge展示了通过将签名验证卸载到图形处理器上可以大大提高吞吐量。 Anatoly招募了Greg、Stephen和其他三个人共同创立了一家公司当时名为Loom。
大约在同一时间,基于以太坊的项目织机网络,如雨后春笋般涌现,许多人对于他们是否属于同一项目感到困惑。 织机团队决定将其品牌重塑。 他们选择了Solana这个名字来表示对圣地亚哥北部一个名为Solana Beach的海滩小镇的怀念。AnatolyGreg和Stephen在高通公司工作的时候在那里生活和冲浪了三年。 3月28日该团队创建了Solana Labs GitHub组织并将Greg的原型Silk重命名为Solana。
在2018年6月该团队扩大了该技术的运行范围使其可以基于云网络运行并于7月19日发布了一个50节点经过许可的公共测试网该网络始终支持每秒250,000次交易。 在12月发布的名为v0.10 Pillbox的更高版本中该团队发布了一个许可的测试网该测试网在千兆位网络上运行150个节点并演示了浸泡测试该测试每秒处理_平均_20万笔事务突发次数超过50万笔。 该项目还得到扩展以支持用C编程语言编写的链上程序并在称为BPF的安全执行环境中并行运行。

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm图标 / 沟通 / 地址簿#1</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Communication-/-Adress-book#1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M17,2 L19,2 C20.6568542,2 22,3.34314575 22,5 L22,19 C22,20.6568542 20.6568542,22 19,22 L17,22 L17,2 Z" id="Rectangle-161-Copy" fill="#335EEA" opacity="0.3"/>
<path d="M4,2 L16,2 C17.6568542,2 19,3.34314575 19,5 L19,19 C19,20.6568542 17.6568542,22 16,22 L4,22 C3.44771525,22 3,21.5522847 3,21 L3,3 C3,2.44771525 3.44771525,2 4,2 Z M11.1176481,13.709585 C10.6725287,14.1547043 9.99251947,14.2650547 9.42948307,13.9835365 C8.86644666,13.7020183 8.18643739,13.8123686 7.74131803,14.2574879 L6.2303083,15.7684977 C6.17542087,15.8233851 6.13406645,15.8902979 6.10952004,15.9639372 C6.02219616,16.2259088 6.16377615,16.5090688 6.42574781,16.5963927 L7.77956724,17.0476658 C9.07965249,17.4810276 10.5130001,17.1426601 11.4820264,16.1736338 L15.4812434,12.1744168 C16.3714821,11.2841781 16.5921828,9.92415954 16.0291464,8.79808673 L15.3965752,7.53294436 C15.3725414,7.48487691 15.3409156,7.44099843 15.302915,7.40299777 C15.1076528,7.20773562 14.7910703,7.20773562 14.5958082,7.40299777 L13.0032662,8.99553978 C12.5581468,9.44065914 12.4477965,10.1206684 12.7293147,10.6837048 C13.0108329,11.2467412 12.9004826,11.9267505 12.4553632,12.3718698 L11.1176481,13.709585 Z" id="Combined-Shape" fill="#335EEA"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm图标 / 沟通 / 删除用户</title>
<desc>通过 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Communication-/-Delete-user" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="Shape" points="0 0 24 0 24 24 0 24"/>
<path d="M9,11 C6.790861,11 5,9.209139 5,7 C5,4.790861 6.790861,3 9,3 C11.209139,3 13,4.790861 13,7 C13,9.209139 11.209139,11 9,11 Z M21,8 L17,8 C16.4477153,8 16,7.55228475 16,7 C16,6.44771525 16.4477153,6 17,6 L21,6 C21.5522847,6 22,6.44771525 22,7 C22,7.55228475 21.5522847,8 21,8 Z" id="Combined-Shape" fill="#335EEA" opacity="0.3"/>
<path d="M0.00065168429,20.1992055 C0.388258525,15.4265159 4.26191235,13 8.98334134,13 C13.7712164,13 17.7048837,15.2931929 17.9979143,20.2 C18.0095879,20.3954741 17.9979143,21 17.2466999,21 C13.541124,21 8.03472472,21 0.727502227,21 C0.476712155,21 -0.0204617505,20.45918 0.00065168429,20.1992055 Z" id="Mask-Copy" fill="#335EEA"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm图标 / 沟通 / 延迟邮件</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Communication-/-Snoozed-mail" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M12.9835977,18 C12.7263047,14.0909841 9.47412135,11 5.5,11 C4.98630124,11 4.48466491,11.0516454 4,11.1500272 L4,7 C4,5.8954305 4.8954305,5 6,5 L20,5 C21.1045695,5 22,5.8954305 22,7 L22,16 C22,17.1045695 21.1045695,18 20,18 L12.9835977,18 Z M19.1444251,6.83964668 L13,10.1481833 L6.85557487,6.83964668 C6.4908718,6.6432681 6.03602525,6.77972206 5.83964668,7.14442513 C5.6432681,7.5091282 5.77972206,7.96397475 6.14442513,8.16035332 L12.6444251,11.6603533 C12.8664074,11.7798822 13.1335926,11.7798822 13.3555749,11.6603533 L19.8555749,8.16035332 C20.2202779,7.96397475 20.3567319,7.5091282 20.1603533,7.14442513 C19.9639747,6.77972206 19.5091282,6.6432681 19.1444251,6.83964668 Z" id="Combined-Shape" fill="#335EEA"/>
<path d="M8.4472136,18.1055728 C8.94119209,18.3525621 9.14141644,18.9532351 8.89442719,19.4472136 C8.64743794,19.9411921 8.0467649,20.1414164 7.5527864,19.8944272 L5,18.618034 L5,14.5 C5,13.9477153 5.44771525,13.5 6,13.5 C6.55228475,13.5 7,13.9477153 7,14.5 L7,17.381966 L8.4472136,18.1055728 Z" id="Path-85" fill="#335EEA" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 烹饪 / 面包工具</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Cooking-/-Baking-glove" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M7.03587629,17.2664036 L2.54094808,14.2345363 C1.62521847,13.6168689 1.38359132,12.373805 2.00125875,11.4580753 C2.61892617,10.5423457 3.86199008,10.3007186 4.7777197,10.918386 L7,12.417333 L7,8 C7,4.6862915 9.6862915,2 13,2 C16.3137085,2 19,4.6862915 19,8 L19,17 C19,17.5522847 18.5522847,18 18,18 L8,18 C7.53996718,18 7.15248755,17.6893628 7.03587629,17.2664036 Z" id="Combined-Shape" fill="#335EEA"/>
<rect id="Rectangle-2" fill="#335EEA" opacity="0.3" x="6" y="20" width="14" height="2" rx="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 烹饪 / 平底锅</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Cooking-/-Saucepan" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M2,9 L12,9 C12.5522847,9 13,9.44771525 13,10 L13,13 C13,15.209139 11.209139,17 9,17 L5,17 C2.790861,17 1,15.209139 1,13 L1,10 C1,9.44771525 1.44771525,9 2,9 Z" id="Rectangle-203" fill="#335EEA"/>
<path d="M14.9984604,9.44452998 L21.5023095,9.08320503 C22.2847837,9.03973424 22.9543445,9.63881491 22.9978153,10.4212892 C22.9992715,10.4475009 23,10.4737479 23,10.5 L23,10.5 C23,11.2836808 22.3647011,11.9189797 21.5810203,11.9189797 C21.5547682,11.9189797 21.5285212,11.9182512 21.5023095,11.916795 L14.9984604,11.55547 C14.4382868,11.5243493 14,11.0610373 14,10.5 L14,10.5 C14,9.93896268 14.4382868,9.47565073 14.9984604,9.44452998 Z" id="Rectangle-206" fill="#335EEA" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 设计 / 多边形</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Design-/-Polygon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M8.08113883,20 L15.9188612,20 C16.5645068,20 17.137715,19.5868549 17.3418861,18.9743416 L19.6721428,11.9835717 C19.8694432,11.3916705 19.6797482,10.7394436 19.1957765,10.3456849 L12.9561839,5.26916104 C12.4053757,4.82102426 11.6158052,4.82050247 11.0644052,5.26791085 L4.80622561,10.345825 C4.32117072,10.7394007 4.13079092,11.3923728 4.32832067,11.984962 L6.65811388,18.9743416 C6.86228495,19.5868549 7.43549322,20 8.08113883,20 Z" id="Path-4" fill="#335EEA"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1012 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 设备 / 安卓</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Devices-/-Android" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M7.5,4 L7.5,19 L16.5,19 L16.5,4 L7.5,4 Z M7.71428571,2 L16.2857143,2 C17.2324881,2 18,2.8954305 18,4 L18,20 C18,21.1045695 17.2324881,22 16.2857143,22 L7.71428571,22 C6.76751186,22 6,21.1045695 6,20 L6,4 C6,2.8954305 6.76751186,2 7.71428571,2 Z" id="Combined-Shape" fill="#335EEA"/>
<polygon id="Combined-Shape" fill="#335EEA" opacity="0.3" points="7.5 4 7.5 19 16.5 19 16.5 4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 文件 / 云上传</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Files-/-Cloud-upload" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="Shape" points="0 0 24 0 24 24 0 24"/>
<path d="M5.74714567,13.0425758 C4.09410362,11.9740356 3,10.1147886 3,8 C3,4.6862915 5.6862915,2 9,2 C11.7957591,2 14.1449096,3.91215918 14.8109738,6.5 L17.25,6.5 C19.3210678,6.5 21,8.17893219 21,10.25 C21,12.3210678 19.3210678,14 17.25,14 L8.25,14 C7.28817895,14 6.41093178,13.6378962 5.74714567,13.0425758 Z" id="Combined-Shape" fill="#335EEA" opacity="0.3"/>
<path d="M11.1288761,15.7336977 L11.1288761,17.6901712 L9.12120481,17.6901712 C8.84506244,17.6901712 8.62120481,17.9140288 8.62120481,18.1901712 L8.62120481,19.2134699 C8.62120481,19.4896123 8.84506244,19.7134699 9.12120481,19.7134699 L11.1288761,19.7134699 L11.1288761,21.6699434 C11.1288761,21.9460858 11.3527337,22.1699434 11.6288761,22.1699434 C11.7471877,22.1699434 11.8616664,22.1279896 11.951961,22.0515402 L15.4576222,19.0834174 C15.6683723,18.9049825 15.6945689,18.5894857 15.5161341,18.3787356 C15.4982803,18.3576485 15.4787093,18.3380775 15.4576222,18.3202237 L11.951961,15.3521009 C11.7412109,15.173666 11.4257142,15.1998627 11.2472793,15.4106128 C11.1708299,15.5009075 11.1288761,15.6153861 11.1288761,15.7336977 Z" id="Shape" fill="#335EEA" transform="translate(11.959697, 18.661508) rotate(-90.000000) translate(-11.959697, -18.661508) "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 文件 / 编辑</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Files-/-Compilation" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M3.5,21 L20.5,21 C21.3284271,21 22,20.3284271 22,19.5 L22,8.5 C22,7.67157288 21.3284271,7 20.5,7 L10,7 L7.43933983,4.43933983 C7.15803526,4.15803526 6.77650439,4 6.37867966,4 L3.5,4 C2.67157288,4 2,4.67157288 2,5.5 L2,19.5 C2,20.3284271 2.67157288,21 3.5,21 Z" id="Combined-Shape" fill="#335EEA" opacity="0.3"/>
<rect id="Rectangle-41" fill="#335EEA" opacity="0.3" transform="translate(8.984240, 14.127098) rotate(-45.000000) translate(-8.984240, -14.127098) " x="7.41281179" y="12.5556689" width="3.14285714" height="3.14285714" rx="0.75"/>
<rect id="Rectangle-41-Copy" fill="#335EEA" opacity="0.3" transform="translate(15.269955, 14.127098) rotate(-45.000000) translate(-15.269955, -14.127098) " x="13.6985261" y="12.5556689" width="3.14285714" height="3.14285714" rx="0.75"/>
<rect id="Rectangle-41-Copy-3" fill="#335EEA" transform="translate(12.127098, 17.269955) rotate(-45.000000) translate(-12.127098, -17.269955) " x="10.5556689" y="15.6985261" width="3.14285714" height="3.14285714" rx="0.75"/>
<rect id="Rectangle-41-Copy-4" fill="#335EEA" transform="translate(12.127098, 10.984240) rotate(-45.000000) translate(-12.127098, -10.984240) " x="10.5556689" y="9.41281179" width="3.14285714" height="3.14285714" rx="0.75"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 文件 / 文件夹</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Files-/-Folder" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M3.5,21 L20.5,21 C21.3284271,21 22,20.3284271 22,19.5 L22,8.5 C22,7.67157288 21.3284271,7 20.5,7 L10,7 L7.43933983,4.43933983 C7.15803526,4.15803526 6.77650439,4 6.37867966,4 L3.5,4 C2.67157288,4 2,4.67157288 2,5.5 L2,19.5 C2,20.3284271 2.67157288,21 3.5,21 Z" id="Combined-Shape" fill="#335EEA" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 860 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 食物 / 芝士</title>
<desc>通过 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Food-/-Cheese" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M22,13.9146471 L22,19 C22,20.1045695 21.1045695,21 20,21 L14,21 C14,19.8954305 13.1045695,19 12,19 C10.8954305,19 10,19.8954305 10,21 L4,21 C2.8954305,21 2,20.1045695 2,19 L2,7 L22,7 L22,11.0853529 C21.8436105,11.0300771 21.6753177,11 21.5,11 C20.6715729,11 20,11.6715729 20,12.5 C20,13.3284271 20.6715729,14 21.5,14 C21.6753177,14 21.8436105,13.9699229 22,13.9146471 Z M9,17 C11.209139,17 13,15.209139 13,13 C13,10.790861 11.209139,9 9,9 C6.790861,9 5,10.790861 5,13 C5,15.209139 6.790861,17 9,17 Z M18,18 C18.5522847,18 19,17.5522847 19,17 C19,16.4477153 18.5522847,16 18,16 C17.4477153,16 17,16.4477153 17,17 C17,17.5522847 17.4477153,18 18,18 Z M5,21 C5.55228475,21 6,20.5522847 6,20 C6,19.4477153 5.55228475,19 5,19 C4.44771525,19 4,19.4477153 4,20 C4,20.5522847 4.44771525,21 5,21 Z" id="Combined-Shape" fill="#335EEA"/>
<path d="M19.5954729,5.83476152 L4.60883918,4.07162814 C4.23525261,4.02767678 3.86860536,4.19709197 3.65994764,4.51007855 L2,7 C15.3333333,7 22,7 22,7 C22,7 22,7 22,7 L22,7 C21.352294,6.35229396 20.5051936,5.94178748 19.5954729,5.83476152 Z" id="Path-98" fill="#335EEA" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 通用 / 素材</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-General-/-Clip" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M14,16 L12,16 L12,12.5 C12,11.6715729 11.3284271,11 10.5,11 C9.67157288,11 9,11.6715729 9,12.5 L9,17.5 C9,19.4329966 10.5670034,21 12.5,21 C14.4329966,21 16,19.4329966 16,17.5 L16,7.5 C16,5.56700338 14.4329966,4 12.5,4 L12,4 C10.3431458,4 9,5.34314575 9,7 L7,7 C7,4.23857625 9.23857625,2 12,2 L12.5,2 C15.5375661,2 18,4.46243388 18,7.5 L18,17.5 C18,20.5375661 15.5375661,23 12.5,23 C9.46243388,23 7,20.5375661 7,17.5 L7,12.5 C7,10.5670034 8.56700338,9 10.5,9 C12.4329966,9 14,10.5670034 14,12.5 L14,16 Z" id="Path-16" fill="#335EEA" transform="translate(12.500000, 12.500000) rotate(-315.000000) translate(-12.500000, -12.500000) "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 通用 / 半心</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-General-/-Half-heart" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="Shape" points="0 0 24 0 24 24 0 24"/>
<path d="M16.5,4.5 C14.8905,4.5 13.00825,6.32463215 12,7.5 C10.99175,6.32463215 9.1095,4.5 7.5,4.5 C4.651,4.5 3,6.72217984 3,9.55040872 C3,12.6834696 6,16 12,19.5 C18,16 21,12.75 21,9.75 C21,6.92177112 19.349,4.5 16.5,4.5 Z" id="Shape" fill="#335EEA" opacity="0.3"/>
<path d="M12,19.5 C6,16 3,12.6834696 3,9.55040872 C3,6.72217984 4.651,4.5 7.5,4.5 C9.1095,4.5 10.99175,6.32463215 12,7.5 L12,19.5 Z" id="Combined-Shape" fill="#335EEA"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 980 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 通用 / 隐藏</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-General-/-Hidden" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M19.2078777,9.84836149 C20.3303823,11.0178941 21,12 21,12 C21,12 16.9090909,18 12,18 C11.6893441,18 11.3879033,17.9864845 11.0955026,17.9607365 L19.2078777,9.84836149 Z" id="Combined-Shape" fill="#335EEA"/>
<path d="M14.5051465,6.49485351 L12,9 C10.3431458,9 9,10.3431458 9,12 L5.52661464,15.4733854 C3.75006453,13.8334911 3,12 3,12 C3,12 5.45454545,6 12,6 C12.8665422,6 13.7075911,6.18695134 14.5051465,6.49485351 Z" id="Combined-Shape" fill="#335EEA"/>
<rect id="Rectangle" fill="#335EEA" opacity="0.3" transform="translate(12.524621, 12.424621) rotate(-45.000000) translate(-12.524621, -12.424621) " x="3.02462111" y="11.4246212" width="19" height="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Stockholm-图标 / 主页 / 洗脸台#2</title>
<desc>用 Sketch 创建。</desc>
<g id="Stockholm-icons-/-Home-/-Сommode#2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bound" x="0" y="0" width="24" height="24"/>
<path d="M5.5,2 L18.5,2 C19.3284271,2 20,2.67157288 20,3.5 L20,6.5 C20,7.32842712 19.3284271,8 18.5,8 L5.5,8 C4.67157288,8 4,7.32842712 4,6.5 L4,3.5 C4,2.67157288 4.67157288,2 5.5,2 Z M11,4 C10.4477153,4 10,4.44771525 10,5 C10,5.55228475 10.4477153,6 11,6 L13,6 C13.5522847,6 14,5.55228475 14,5 C14,4.44771525 13.5522847,4 13,4 L11,4 Z" id="Combined-Shape" fill="#335EEA" opacity="0.3"/>
<path d="M5.5,9 L18.5,9 C19.3284271,9 20,9.67157288 20,10.5 L20,13.5 C20,14.3284271 19.3284271,15 18.5,15 L5.5,15 C4.67157288,15 4,14.3284271 4,13.5 L4,10.5 C4,9.67157288 4.67157288,9 5.5,9 Z M11,11 C10.4477153,11 10,11.4477153 10,12 C10,12.5522847 10.4477153,13 11,13 L13,13 C13.5522847,13 14,12.5522847 14,12 C14,11.4477153 13.5522847,11 13,11 L11,11 Z M5.5,16 L18.5,16 C19.3284271,16 20,16.6715729 20,17.5 L20,20.5 C20,21.3284271 19.3284271,22 18.5,22 L5.5,22 C4.67157288,22 4,21.3284271 4,20.5 L4,17.5 C4,16.6715729 4.67157288,16 5.5,16 Z M11,18 C10.4477153,18 10,18.4477153 10,19 C10,19.5522847 10.4477153,20 11,20 L13,20 C13.5522847,20 14,19.5522847 14,19 C14,18.4477153 13.5522847,18 13,18 L11,18 Z" id="Combined-Shape" fill="#335EEA"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

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