From f2f46e17b745d8fef7f7105d9df92968bb4d8755 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:01:35 -0800 Subject: [PATCH 01/17] feat: SIMD 387 implementation: Add BLS Pubkey management to vote program --- Cargo.lock | 123 +- Cargo.toml | 2 +- ci/xtask/Cargo.lock | 4 +- clap-utils/src/input_parsers.rs | 19 +- cli/Cargo.toml | 8 +- cli/src/cli.rs | 71 +- cli/src/vote.rs | 1371 ++++++++++++++++++----- cli/tests/stake.rs | 9 +- cli/tests/vote.rs | 12 +- dev-bins/Cargo.lock | 5 +- feature-set/src/lib.rs | 10 + genesis/src/main.rs | 43 +- ledger/src/leader_schedule_utils.rs | 54 +- ledger/src/staking_utils.rs | 39 +- local-cluster/src/local_cluster.rs | 12 +- multinode-demo/validator.sh | 2 +- program-test/tests/setup.rs | 12 +- programs/sbf/Cargo.lock | 5 +- programs/vote/Cargo.toml | 1 + programs/vote/src/vote_processor.rs | 929 ++++++++++++++- programs/vote/src/vote_state/handler.rs | 217 ++-- programs/vote/src/vote_state/mod.rs | 297 ++++- rpc/Cargo.toml | 1 + rpc/src/rpc_pubsub.rs | 13 +- runtime/src/bank.rs | 39 + runtime/src/bank/tests.rs | 163 ++- svm-feature-set/src/lib.rs | 2 + votor-messages/src/consensus_message.rs | 6 +- 28 files changed, 2886 insertions(+), 583 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 997bbce106458d..7e5ca3ad1fb124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,7 +802,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -937,7 +937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -963,7 +963,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1038,7 +1038,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1103,7 +1103,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -1115,7 +1115,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1198,7 +1198,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1377,7 +1377,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1474,7 +1474,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1618,7 +1618,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1717,7 +1717,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1878,7 +1878,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2013,7 +2013,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2414,7 +2414,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2438,7 +2438,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2449,7 +2449,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2542,7 +2542,7 @@ checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2553,7 +2553,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2588,7 +2588,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.111", + "syn 2.0.110", "unicode-xid", ] @@ -2710,7 +2710,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2832,7 +2832,7 @@ dependencies = [ "enum-ordinalize 4.3.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2884,7 +2884,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2904,7 +2904,7 @@ checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2937,7 +2937,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -3265,7 +3265,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4000,7 +4000,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4243,7 +4243,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4805,7 +4805,7 @@ dependencies = [ "cfg-if 1.0.4", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4826,7 +4826,7 @@ checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4953,7 +4953,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -5026,7 +5026,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -5106,7 +5106,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -5750,7 +5750,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6685,7 +6685,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6751,7 +6751,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6801,7 +6801,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -7485,9 +7485,9 @@ dependencies = [ [[package]] name = "solana-bls-signatures" -version = "1.0.0" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c75573697bbb148afa8209aa3ce228ca0754584c9a8a91e818db0f706ae4fb" +checksum = "4acb2f8e2ce54f8798c8a5eb5d06f4cb4f596a5879be5ad81e95a60a1bde9359" dependencies = [ "base64 0.22.1", "blst", @@ -7760,6 +7760,7 @@ dependencies = [ "agave-feature-set", "agave-logger", "agave-syscalls", + "agave-votor-messages", "assert_matches", "bincode", "bs58", @@ -7781,6 +7782,7 @@ dependencies = [ "solana-account", "solana-account-decoder", "solana-address-lookup-table-interface", + "solana-bls-signatures", "solana-borsh", "solana-clap-utils", "solana-cli-config", @@ -8713,7 +8715,7 @@ checksum = "38e4bb0f5232668866a7f6f5cbc3222bbd9eebbff6afe392e3394498e8c9b1fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10547,7 +10549,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -11958,6 +11960,7 @@ dependencies = [ "serde", "solana-account", "solana-bincode", + "solana-bls-signatures", "solana-clock", "solana-epoch-schedule", "solana-fee-calculator", @@ -12191,7 +12194,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12203,7 +12206,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.111", + "syn 2.0.110", "thiserror 1.0.69", ] @@ -12468,9 +12471,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -12500,7 +12503,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12661,7 +12664,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12673,7 +12676,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "test-case-core", ] @@ -12718,7 +12721,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12729,7 +12732,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12908,7 +12911,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13214,7 +13217,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13610,7 +13613,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -13740,7 +13743,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13817,7 +13820,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13828,7 +13831,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14311,7 +14314,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -14332,7 +14335,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14352,7 +14355,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -14373,7 +14376,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14395,7 +14398,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e831e1854c0880..3778725f34c70d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -417,7 +417,7 @@ solana-big-mod-exp = "3.0.0" solana-bincode = "3.1.0" solana-blake3-hasher = "3.1.0" solana-bloom = { path = "bloom", version = "=4.0.0-alpha.0", features = ["agave-unstable-api"] } -solana-bls-signatures = { version = "1.0.0", features = ["serde"] } +solana-bls-signatures = { version = "2.0.0-alpha.1", features = ["serde"] } #TODO: change to 2.0.0 before this gets into 4.0 solana-bn254 = "3.1.2" solana-borsh = "3.0.0" solana-bpf-loader-program = { path = "programs/bpf_loader", version = "=4.0.0-alpha.0", features = ["agave-unstable-api"] } diff --git a/ci/xtask/Cargo.lock b/ci/xtask/Cargo.lock index bcef8ceca51be3..05337d8b2b8afe 100644 --- a/ci/xtask/Cargo.lock +++ b/ci/xtask/Cargo.lock @@ -378,9 +378,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index 2800dad0c3a4cb..9dfde72512e0db 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -5,7 +5,9 @@ use { }, chrono::DateTime, clap::ArgMatches, - solana_bls_signatures::Pubkey as BLSPubkey, + solana_bls_signatures::{ + ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, Pubkey as BLSPubkey, + }, solana_clock::UnixTimestamp, solana_cluster_type::ClusterType, solana_commitment_config::CommitmentConfig, @@ -117,6 +119,21 @@ pub fn bls_pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option, + name: &str, +) -> Option> { + matches.values_of(name).map(|values| { + values + .map(|value| { + BLSProofOfPossessionCompressed::from_str(value).unwrap_or_else(|_| { + panic!("Failed to parse BLS public key from value: {value}") + }) + }) + .collect() + }) +} + // Return pubkey/signature pairs for a string of the form pubkey=signature pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option> { matches.values_of(name).map(|values| { diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8b899e9154aa3c..f8b778e8b93ec6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,10 +19,7 @@ path = "src/main.rs" [features] default = ["remote-wallet-hidraw"] agave-unstable-api = [] -dev-context-only-utils = [ - "solana-faucet/dev-context-only-utils", - "solana-client/dev-context-only-utils", -] +dev-context-only-utils = ["solana-faucet/dev-context-only-utils"] remote-wallet-hidraw = ["solana-remote-wallet/linux-static-hidraw"] remote-wallet-libusb = ["solana-remote-wallet/linux-static-libusb"] @@ -30,6 +27,7 @@ remote-wallet-libusb = ["solana-remote-wallet/linux-static-libusb"] agave-feature-set = { workspace = true } agave-logger = { workspace = true } agave-syscalls = { workspace = true } +agave-votor-messages = { workspace = true } bincode = { workspace = true } bs58 = { workspace = true } clap = { workspace = true } @@ -50,6 +48,7 @@ serde_json = { workspace = true } solana-account = "=3.2.0" solana-account-decoder = { workspace = true } solana-address-lookup-table-interface = { workspace = true } +solana-bls-signatures = { workspace = true } solana-borsh = "=3.0.0" solana-clap-utils = { workspace = true } solana-cli-config = { workspace = true } @@ -118,5 +117,6 @@ solana-rpc = { workspace = true } solana-sha256-hasher = { workspace = true } solana-test-validator = { workspace = true } solana-tps-client = { workspace = true, features = ["dev-context-only-utils"] } +solana-vote-program = { workspace = true } tempfile = { workspace = true } test-case = { workspace = true } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 9c82ce59479041..27a9eeaa9bdba5 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -35,7 +35,9 @@ use { }, solana_transaction::versioned::VersionedTransaction, solana_transaction_error::TransactionError, - solana_vote_program::vote_state::VoteAuthorize, + solana_vote_program::vote_state::{ + VoteAuthorize, BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE, + }, std::{ collections::HashMap, error, io::stdout, rc::Rc, str::FromStr, sync::Arc, time::Duration, }, @@ -407,6 +409,24 @@ pub enum CliCommand { fee_payer: SignerIndex, compute_unit_price: Option, }, + CreateVoteAccountV2 { + vote_account: SignerIndex, + seed: Option, + identity_account: SignerIndex, + authorized_voter: Option, + bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + bls_proof_of_possession: [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + authorized_withdrawer: Pubkey, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: BlockhashQuery, + nonce_account: Option, + nonce_authority: SignerIndex, + memo: Option, + fee_payer: SignerIndex, + compute_unit_price: Option, + inflation_rewards_commission_bps: u16, + }, // Wallet Commands Address, Airdrop { @@ -758,6 +778,9 @@ pub fn parse_command( ("create-vote-account", Some(matches)) => { parse_create_vote_account(matches, default_signer, wallet_manager) } + ("create-vote-account-with-bls", Some(matches)) => { + parse_create_vote_account_with_bls(matches, default_signer, wallet_manager) + } ("vote-update-validator", Some(matches)) => { parse_vote_update_validator(matches, default_signer, wallet_manager) } @@ -771,6 +794,9 @@ pub fn parse_command( VoteAuthorize::Voter, !CHECKED, ), + ("vote-authorize-voter-with-bls", Some(matches)) => { + parse_vote_authorize_with_bls(matches, default_signer, wallet_manager, !CHECKED) + } ("vote-authorize-withdrawer", Some(matches)) => parse_vote_authorize( matches, default_signer, @@ -785,6 +811,9 @@ pub fn parse_command( VoteAuthorize::Voter, CHECKED, ), + ("vote-authorize-voter-checked-with-bls", Some(matches)) => { + parse_vote_authorize_with_bls(matches, default_signer, wallet_manager, CHECKED) + } ("vote-authorize-withdrawer-checked", Some(matches)) => parse_vote_authorize( matches, default_signer, @@ -1596,6 +1625,46 @@ pub async fn process_command(config: &CliConfig<'_>) -> ProcessResult { ) .await } + CliCommand::CreateVoteAccountV2 { + vote_account, + seed, + identity_account, + authorized_voter, + authorized_withdrawer, + bls_pubkey, + bls_proof_of_possession, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, + memo, + fee_payer, + compute_unit_price, + inflation_rewards_commission_bps, + } => { + process_create_vote_account_v2( + &rpc_client, + config, + *vote_account, + seed, + *identity_account, + authorized_voter, + *authorized_withdrawer, + *bls_pubkey, + *bls_proof_of_possession, + *sign_only, + *dump_transaction_message, + blockhash_query, + nonce_account.as_ref(), + *nonce_authority, + memo.as_ref(), + *fee_payer, + *compute_unit_price, + *inflation_rewards_commission_bps, + ) + .await + } CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, use_lamports_unit, diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 12b7bb9159cda8..c7f2ac7e7e8bba 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -13,8 +13,10 @@ use { spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, stake::check_current_authority, }, + agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED, clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}, solana_account::Account, + solana_bls_signatures::keypair::Keypair as BLSKeypair, solana_clap_utils::{ compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG}, fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, @@ -31,6 +33,8 @@ use { }, solana_commitment_config::CommitmentConfig, solana_feature_gate_interface::from_account, + solana_instruction::Instruction, + solana_keypair::Signer, solana_message::Message, solana_pubkey::Pubkey, solana_remote_wallet::remote_wallet::RemoteWalletManager, @@ -42,7 +46,12 @@ use { solana_vote_program::{ vote_error::VoteError, vote_instruction::{self, withdraw, CreateVoteAccountConfig}, - vote_state::{VoteAuthorize, VoteInit, VoteStateV4, VOTE_CREDITS_MAXIMUM_PER_SLOT}, + vote_state::{ + create_bls_proof_of_possession, verify_bls_proof_of_possession, VoteAuthorize, + VoteInit, VoteInitV2, VoteStateV4, VoterWithBLSArgs, + BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE, + VOTE_CREDITS_MAXIMUM_PER_SLOT, + }, }, std::rc::Rc, }; @@ -123,6 +132,79 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) + .subcommand( + SubCommand::with_name("create-vote-account-with-bls") + .about("Create a vote account with BLS pubkey") + .arg( + Arg::with_name("vote_account") + .index(1) + .value_name("ACCOUNT_KEYPAIR") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("Vote account keypair to create"), + ) + .arg( + Arg::with_name("identity_account") + .index(2) + .value_name("IDENTITY_KEYPAIR") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("Keypair of validator that will vote with this account"), + ) + .arg(pubkey!( + Arg::with_name("authorized_withdrawer") + .index(3) + .value_name("WITHDRAWER_PUBKEY") + .takes_value(true) + .required(true) + .long("authorized-withdrawer"), + "Authorized withdrawer." + )) + .arg( + Arg::with_name("inflation_rewards_commission_bps") + .long("inflation_rewards_commission_bps") + .value_name("INFLATION_REWARDS_COMMISSION_BPS") + .takes_value(true) + .default_value("0") + .help("The commission taken on inflation rewards"), + ) + .arg( + Arg::with_name("authorized_voter") + .index(4) + .value_name("AUTHORIZED_VOTER_KEYPAIR") + .takes_value(true) + .required(false) + .validator(is_valid_signer) + .help("Authorized voter [default: validator identity pubkey]."), + ) + .arg( + Arg::with_name("allow_unsafe_authorized_withdrawer") + .long("allow-unsafe-authorized-withdrawer") + .takes_value(false) + .help( + "Allow an authorized withdrawer pubkey to be identical to the \ + validator identity account pubkey or vote account pubkey, which is \ + normally an unsafe configuration and should be avoided.", + ), + ) + .arg( + Arg::with_name("seed") + .long("seed") + .value_name("STRING") + .takes_value(true) + .help( + "Seed for address generation; if specified, the resulting account \ + will be at a derived address of the VOTE ACCOUNT pubkey", + ), + ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) + .arg(memo_arg()) + .arg(compute_unit_price_arg()), + ) .subcommand( SubCommand::with_name("vote-authorize-voter") .about("Authorize a new vote signing keypair for the given vote account") @@ -154,6 +236,41 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) + .subcommand( + SubCommand::with_name("vote-authorize-voter-with-bls") + .about( + "Authorize a new vote signing keypair for the given vote account, update BLS \ + pubkey", + ) + .arg(pubkey!( + Arg::with_name("vote_account_pubkey") + .index(1) + .value_name("VOTE_ACCOUNT_ADDRESS") + .required(true), + "Vote account in which to set the authorized voter." + )) + .arg( + Arg::with_name("authorized") + .index(2) + .value_name("AUTHORIZED_KEYPAIR") + .required(true) + .validator(is_valid_signer) + .help("Current authorized vote signer."), + ) + .arg( + Arg::with_name("new_authorized") + .index(3) + .value_name("NEW_AUTHORIZED_KEYPAIR") + .required(true) + .validator(is_valid_signer) + .help("New authorized vote signer."), + ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) + .arg(memo_arg()) + .arg(compute_unit_price_arg()), + ) .subcommand( SubCommand::with_name("vote-authorize-withdrawer") .about("Authorize a new withdraw signing keypair for the given vote account") @@ -220,6 +337,41 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) + .subcommand( + SubCommand::with_name("vote-authorize-voter-checked-with-bls") + .about( + "Authorize a new vote signing keypair for the given vote account, checking \ + the new authority as a signer, also update BLS pubkey", + ) + .arg(pubkey!( + Arg::with_name("vote_account_pubkey") + .index(1) + .value_name("VOTE_ACCOUNT_ADDRESS") + .required(true), + "Vote account in which to set the authorized voter." + )) + .arg( + Arg::with_name("authorized") + .index(2) + .value_name("AUTHORIZED_KEYPAIR") + .required(true) + .validator(is_valid_signer) + .help("Current authorized vote signer."), + ) + .arg( + Arg::with_name("new_authorized") + .index(3) + .value_name("NEW_AUTHORIZED_KEYPAIR") + .required(true) + .validator(is_valid_signer) + .help("New authorized vote signer."), + ) + .offline_args() + .nonce_args(false) + .arg(fee_payer_arg()) + .arg(memo_arg()) + .arg(compute_unit_price_arg()), + ) .subcommand( SubCommand::with_name("vote-authorize-withdrawer-checked") .about( @@ -449,6 +601,17 @@ impl VoteSubCommands for App<'_, '_> { } } +fn generate_bls_pubkey_and_proof_of_possession( + vote_account_pubkey: &Pubkey, + keypair: &dyn Signer, +) -> ( + [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], +) { + let bls_keypair = BLSKeypair::derive_from_signer(keypair, BLS_KEYPAIR_DERIVE_SEED).unwrap(); + create_bls_proof_of_possession(vote_account_pubkey, &bls_keypair) +} + pub fn parse_create_vote_account( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -518,6 +681,88 @@ pub fn parse_create_vote_account( }) } +pub fn parse_create_vote_account_with_bls( + matches: &ArgMatches<'_>, + default_signer: &DefaultSigner, + wallet_manager: &mut Option>, +) -> Result { + let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?; + let seed = matches.value_of("seed").map(|s| s.to_string()); + let (identity_account, identity_pubkey) = + signer_of(matches, "identity_account", wallet_manager)?; + let (authorized_voter_keypair, authorized_voter) = + signer_of(matches, "authorized_voter", wallet_manager)?; + let authorized_withdrawer = + pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap(); + let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer"); + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?; + let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name); + let inflation_rewards_commission_bps = + value_of(matches, "inflation_rewards_commission_bps").unwrap_or(0); + + let authorized_voter_keypair: &dyn Signer = authorized_voter_keypair + .as_deref() + .or(identity_account.as_deref()) + .unwrap(); + let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = + generate_bls_pubkey_and_proof_of_possession( + &vote_account_pubkey.unwrap(), + authorized_voter_keypair, + ); + if !allow_unsafe { + if authorized_withdrawer == vote_account_pubkey.unwrap() { + return Err(CliError::BadParameter( + "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \ + configuration" + .to_owned(), + )); + } + if authorized_withdrawer == identity_pubkey.unwrap() { + return Err(CliError::BadParameter( + "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \ + configuration" + .to_owned(), + )); + } + } + + let mut bulk_signers = vec![fee_payer, vote_account, identity_account]; + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + + Ok(CliCommandInfo { + command: CliCommand::CreateVoteAccountV2 { + vote_account: signer_info.index_of(vote_account_pubkey).unwrap(), + seed, + identity_account: signer_info.index_of(identity_pubkey).unwrap(), + authorized_voter, + bls_pubkey: bls_pubkey_compressed_bytes, + bls_proof_of_possession: bls_proof_of_possession_compressed_bytes, + authorized_withdrawer, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), + compute_unit_price, + inflation_rewards_commission_bps, + }, + signers: signer_info.signers, + }) +} + pub fn parse_vote_authorize( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -579,6 +824,76 @@ pub fn parse_vote_authorize( }) } +pub fn parse_vote_authorize_with_bls( + matches: &ArgMatches<'_>, + default_signer: &DefaultSigner, + wallet_manager: &mut Option>, + checked: bool, +) -> Result { + let vote_account_pubkey = + pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); + let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?; + let (new_authorized, new_authorized_pubkey) = + signer_of(matches, "new_authorized", wallet_manager)?; + + let sign_only = matches.is_present(SIGN_ONLY_ARG.name); + let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); + let blockhash_query = BlockhashQuery::new_from_matches(matches); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + let memo = matches.value_of(MEMO_ARG.name).map(String::from); + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; + let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name); + + let mut bulk_signers = vec![fee_payer, authorized]; + + let new_authorized_voter_keypair: &dyn Signer = new_authorized.as_deref().unwrap(); + let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = + generate_bls_pubkey_and_proof_of_possession( + &vote_account_pubkey, + new_authorized_voter_keypair, + ); + + let vote_authorize = VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey: bls_pubkey_compressed_bytes, + bls_proof_of_possession: bls_proof_of_possession_compressed_bytes, + }); + + let new_authorized_pubkey = new_authorized_pubkey.unwrap(); + if checked { + bulk_signers.push(new_authorized); + } + if nonce_account.is_some() { + bulk_signers.push(nonce_authority); + } + let signer_info = + default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + + Ok(CliCommandInfo { + command: CliCommand::VoteAuthorize { + vote_account_pubkey, + new_authorized_pubkey, + vote_authorize, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + memo, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), + authorized: signer_info.index_of(authorized_pubkey).unwrap(), + new_authorized: if checked { + signer_info.index_of(Some(new_authorized_pubkey)) + } else { + None + }, + compute_unit_price, + }, + signers: signer_info.signers, + }) +} + pub fn parse_vote_update_validator( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -807,6 +1122,144 @@ pub async fn process_create_vote_account( fee_payer: SignerIndex, compute_unit_price: Option, ) -> ProcessResult { + let build_instructions = + |(lamports, identity_pubkey, vote_account_pubkey, vote_account_address)| { + let vote_init = VoteInit { + node_pubkey: identity_pubkey, + authorized_voter: authorized_voter.unwrap_or(identity_pubkey), + authorized_withdrawer, + commission, + }; + let space = VoteStateV4::size_of() as u64; + let mut create_vote_account_config = CreateVoteAccountConfig { + space, + ..CreateVoteAccountConfig::default() + }; + let to = if let Some(seed) = seed { + create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); + &vote_account_address + } else { + &vote_account_pubkey + }; + + vote_instruction::create_account_with_config( + &config.signers[0].pubkey(), + to, + &vote_init, + lamports, + create_vote_account_config, + ) + }; + process_create_vote_account_internal( + rpc_client, + config, + vote_account, + seed, + identity_account, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, + memo, + fee_payer, + compute_unit_price, + build_instructions, + ) + .await +} + +#[allow(clippy::too_many_arguments)] +pub async fn process_create_vote_account_v2( + rpc_client: &RpcClient, + config: &CliConfig<'_>, + vote_account: SignerIndex, + seed: &Option, + identity_account: SignerIndex, + authorized_voter: &Option, + authorized_withdrawer: Pubkey, + authorized_voter_bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + authorized_voter_bls_proof_of_possession: [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option<&Pubkey>, + nonce_authority: SignerIndex, + memo: Option<&String>, + fee_payer: SignerIndex, + compute_unit_price: Option, + inflation_rewards_commission_bps: u16, +) -> ProcessResult { + let build_instructions = + |(lamports, identity_pubkey, vote_account_pubkey, vote_account_address)| { + let vote_init_v2 = VoteInitV2 { + node_pubkey: identity_pubkey, + authorized_voter: authorized_voter.unwrap_or(identity_pubkey), + authorized_withdrawer, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + inflation_rewards_commission_bps, + ..VoteInitV2::default() + }; + let space = VoteStateV4::size_of() as u64; + let mut create_vote_account_config = CreateVoteAccountConfig { + space, + ..CreateVoteAccountConfig::default() + }; + let to = if let Some(seed) = seed { + create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); + &vote_account_address + } else { + &vote_account_pubkey + }; + + vote_instruction::create_account_with_config_v2( + &config.signers[0].pubkey(), + to, + &vote_init_v2, + lamports, + create_vote_account_config, + ) + }; + process_create_vote_account_internal( + rpc_client, + config, + vote_account, + seed, + identity_account, + sign_only, + dump_transaction_message, + blockhash_query, + nonce_account, + nonce_authority, + memo, + fee_payer, + compute_unit_price, + build_instructions, + ) + .await +} + +#[allow(clippy::too_many_arguments)] +async fn process_create_vote_account_internal( + rpc_client: &RpcClient, + config: &CliConfig<'_>, + vote_account: SignerIndex, + seed: &Option, + identity_account: SignerIndex, + sign_only: bool, + dump_transaction_message: bool, + blockhash_query: &BlockhashQuery, + nonce_account: Option<&Pubkey>, + nonce_authority: SignerIndex, + memo: Option<&String>, + fee_payer: SignerIndex, + compute_unit_price: Option, + build_instructions: F, +) -> ProcessResult +where + F: Fn((u64, Pubkey, Pubkey, Pubkey)) -> Vec, +{ let vote_account = config.signers[vote_account]; let vote_account_pubkey = vote_account.pubkey(); let vote_account_address = if let Some(seed) = seed { @@ -834,43 +1287,24 @@ pub async fn process_create_vote_account( let fee_payer = config.signers[fee_payer]; let nonce_authority = config.signers[nonce_authority]; - let space = VoteStateV4::size_of() as u64; let compute_unit_limit = match blockhash_query { BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default, BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated, }; - let build_message = |lamports| { - let vote_init = VoteInit { - node_pubkey: identity_pubkey, - authorized_voter: authorized_voter.unwrap_or(identity_pubkey), - authorized_withdrawer, - commission, - }; - let mut create_vote_account_config = CreateVoteAccountConfig { - space, - ..CreateVoteAccountConfig::default() - }; - let to = if let Some(seed) = seed { - create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); - &vote_account_address - } else { - &vote_account_pubkey - }; - let ixs = vote_instruction::create_account_with_config( - &config.signers[0].pubkey(), - to, - &vote_init, + let build_message = |lamports| { + let ixs = build_instructions(( lamports, - create_vote_account_config, - ) + identity_pubkey, + vote_account_pubkey, + vote_account_address, + )) .with_memo(memo) .with_compute_unit_config(&ComputeUnitConfig { compute_unit_price, compute_unit_limit, }); - if let Some(nonce_account) = &nonce_account { Message::new_with_nonce( ixs, @@ -1009,21 +1443,46 @@ pub async fn process_vote_authorize( } } } - VoteAuthorize::Withdrawer => { - check_unique_pubkeys( - (&authorized.pubkey(), "authorized_account".to_string()), - (new_authorized_pubkey, "new_authorized_pubkey".to_string()), - )?; - if let Some(vote_state) = vote_state { - check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())? - } - } - VoteAuthorize::VoterWithBLS(_) => { - return Err(CliError::BadParameter( - "VoterWithBLS authorization not yet supported".to_string(), - ) - .into()); - } + VoteAuthorize::Withdrawer => { + check_unique_pubkeys( + (&authorized.pubkey(), "authorized_account".to_string()), + (new_authorized_pubkey, "new_authorized_pubkey".to_string()), + )?; + if let Some(vote_state) = vote_state { + check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())? + } + } + VoteAuthorize::VoterWithBLS(args) => { + if let Some(vote_state) = vote_state { + let current_epoch = rpc_client.get_epoch_info().await?.epoch; + let current_authorized_voter = vote_state + .authorized_voters + .get_authorized_voter(current_epoch) + .ok_or_else(|| { + CliError::RpcRequestError( + "Invalid vote account state; no authorized voters found".to_string(), + ) + })?; + check_current_authority( + &[current_authorized_voter, vote_state.authorized_withdrawer], + &authorized.pubkey(), + )?; + if let Some(signer) = new_authorized_signer { + if signer.is_interactive() { + return Err(CliError::BadParameter(format!( + "invalid new authorized vote signer {new_authorized_pubkey:?}. \ + Interactive vote signers not supported" + )) + .into()); + } + } + verify_bls_proof_of_possession( + vote_account_pubkey, + &args.bls_pubkey, + &args.bls_proof_of_possession, + )?; + } + } } let vote_ix = if new_authorized_signer.is_some() { @@ -1629,6 +2088,7 @@ mod tests { solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source, solana_signer::Signer, tempfile::NamedTempFile, + test_case::test_case, }; fn make_tmp_file() -> (String, NamedTempFile) { @@ -1636,8 +2096,9 @@ mod tests { (String::from(tmp_file.path().to_str().unwrap()), tmp_file) } - #[test] - fn test_parse_command() { + #[test_case(true; "With BLS")] + #[test_case(false; "Without BLS")] + fn test_parse_vote_authorize(with_bls: bool) { let test_commands = get_clap_app("test", "desc", "version"); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); @@ -1653,25 +2114,47 @@ mod tests { write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); let default_signer = DefaultSigner::new("", &default_keypair_file); + let (keypair2_file, mut tmp_file) = make_tmp_file(); + write_keypair(&keypair2, tmp_file.as_file_mut()).unwrap(); + let blockhash = Hash::default(); let blockhash_string = format!("{blockhash}"); let nonce_account = Pubkey::new_unique(); - // Test VoteAuthorize SubCommand - let test_authorize_voter = test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &default_keypair_file, - &pubkey2_string, - ]); + let vote_authorize = if with_bls { + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&pubkey, &keypair2); + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; + let test_authorize_voter = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter-with-bls", + &pubkey_string, + &default_keypair_file, + &keypair2_file, + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &default_keypair_file, + &pubkey2_string, + ]) + }; assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -1691,20 +2174,31 @@ mod tests { let (authorized_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap(); - let test_authorize_voter = test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair_file, - &pubkey2_string, - ]); + let test_authorize_voter = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter-with-bls", + &pubkey_string, + &authorized_keypair_file, + &keypair2_file, + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair_file, + &pubkey2_string, + ]) + }; + assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -1723,23 +2217,36 @@ mod tests { } ); - let test_authorize_voter = test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair_file, - &pubkey2_string, - "--blockhash", - &blockhash_string, - "--sign-only", - ]); + let test_authorize_voter = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter-with-bls", + &pubkey_string, + &authorized_keypair_file, + &keypair2_file, + "--blockhash", + &blockhash_string, + "--sign-only", + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair_file, + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--sign-only", + ]) + }; assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::Static(blockhash), @@ -1760,32 +2267,54 @@ mod tests { let authorized_sig = authorized_keypair.sign_message(&[0u8]); let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig); - let test_authorize_voter = test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair.pubkey().to_string(), - &pubkey2_string, - "--blockhash", - &blockhash_string, - "--signer", - &authorized_signer, - "--signer", - &signer2, - "--fee-payer", - &pubkey2_string, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - ]); + let test_authorize_voter = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter-with-bls", + &pubkey_string, + &authorized_keypair.pubkey().to_string(), + &keypair2_file, + "--blockhash", + &blockhash_string, + "--signer", + &authorized_signer, + "--signer", + &signer2, + "--fee-payer", + &pubkey2_string, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair.pubkey().to_string(), + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--signer", + &authorized_signer, + "--signer", + &signer2, + "--fee-payer", + &pubkey2_string, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]) + }; assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Validated( @@ -1809,15 +2338,47 @@ mod tests { ], } ); + } + + #[test_case(true; "With BLS")] + #[test_case(false; "Without BLS")] + fn test_parse_vote_authorize_checked(with_bls: bool) { + let test_commands = get_clap_app("test", "desc", "version"); + let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); + let pubkey_string = pubkey.to_string(); + + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let default_signer = DefaultSigner::new("", &default_keypair_file); - // Test checked VoteAuthorize SubCommand let (voter_keypair_file, mut tmp_file) = make_tmp_file(); let voter_keypair = Keypair::new(); write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap(); + let authorized_keypair = Keypair::new(); + let (authorized_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap(); + + let vote_authorize = if with_bls { + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&pubkey, &voter_keypair); + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; + let command = if with_bls { + "vote-authorize-voter-checked-with-bls" + } else { + "vote-authorize-voter-checked" + }; let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - "vote-authorize-voter-checked", + &command, &pubkey_string, &default_keypair_file, &voter_keypair_file, @@ -1828,7 +2389,7 @@ mod tests { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -1849,7 +2410,7 @@ mod tests { let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - "vote-authorize-voter-checked", + &command, &pubkey_string, &authorized_keypair_file, &voter_keypair_file, @@ -1860,7 +2421,7 @@ mod tests { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), - vote_authorize: VoteAuthorize::Voter, + vote_authorize, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -1882,14 +2443,33 @@ mod tests { let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - "vote-authorize-voter-checked", + &command, &pubkey_string, &authorized_keypair_file, - &pubkey2_string, + &voter_keypair.pubkey().to_string(), ]); assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err()); + } + + #[test_case(true; "With BLS")] + #[test_case(false; "Without BLS")] + fn test_parse_vote_create_vote_account(with_bls: bool) { + let test_commands = get_clap_app("test", "desc", "version"); + + let keypair2 = Keypair::new(); + let pubkey2 = keypair2.pubkey(); + let pubkey2_string = pubkey2.to_string(); + let sig2 = keypair2.sign_message(&[0u8]); + let signer2 = format!("{}={}", keypair2.pubkey(), sig2); + + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let default_signer = DefaultSigner::new("", &default_keypair_file); + + let blockhash = Hash::default(); + let blockhash_string = format!("{blockhash}"); - // Test CreateVoteAccount SubCommand let (identity_keypair_file, mut tmp_file) = make_tmp_file(); let identity_keypair = Keypair::new(); let authorized_withdrawer = Keypair::new().pubkey(); @@ -1898,34 +2478,73 @@ mod tests { let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let test_create_vote_account = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--commission", - "10", - ]); + let nonce_account = Pubkey::new_unique(); + + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &identity_keypair); + + let test_create_vote_account = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--inflation_rewards_commission_bps", + "10", + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--commission", + "10", + ]) + }; + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + bls_pubkey, + bls_proof_of_possession, + authorized_withdrawer, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + inflation_rewards_commission_bps: 10, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -1934,32 +2553,64 @@ mod tests { } ); - let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - ]); + let test_create_vote_account2 = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + ]) + }; + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + bls_pubkey, + bls_proof_of_possession, + inflation_rewards_commission_bps: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -1968,39 +2619,78 @@ mod tests { } ); - let test_create_vote_account = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--commission", - "10", - "--blockhash", - &blockhash_string, - "--sign-only", - "--fee-payer", - &default_keypair.pubkey().to_string(), - ]); + let test_create_vote_account = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--inflation_rewards_commission_bps", + "10", + "--blockhash", + &blockhash_string, + "--sign-only", + "--fee-payer", + &default_keypair.pubkey().to_string(), + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--sign-only", + "--fee-payer", + &default_keypair.pubkey().to_string(), + ]) + }; + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + bls_pubkey, + bls_proof_of_possession, + inflation_rewards_commission_bps: 10, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Static(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Static(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: true, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Static(blockhash), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2011,49 +2701,98 @@ mod tests { let identity_sig = identity_keypair.sign_message(&[0u8]); let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig); - let test_create_vote_account = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair.pubkey().to_string(), - &authorized_withdrawer.to_string(), - "--commission", - "10", - "--blockhash", - &blockhash_string, - "--signer", - &identity_signer, - "--signer", - &signer2, - "--fee-payer", - &default_keypair_file, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - ]); + let test_create_vote_account = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--inflation_rewards_commission_bps", + "10", + "--blockhash", + &blockhash_string, + "--signer", + &identity_signer, + "--signer", + &signer2, + "--fee-payer", + &default_keypair_file, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair.pubkey().to_string(), + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--signer", + &identity_signer, + "--signer", + &signer2, + "--fee-payer", + &default_keypair_file, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]) + }; + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + bls_pubkey, + bls_proof_of_possession, + inflation_rewards_commission_bps: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Validated( + Source::NonceAccount(nonce_account), + blockhash, + ), + nonce_account: Some(nonce_account), + nonce_authority: 3, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Validated( + Source::NonceAccount(nonce_account), + blockhash, + ), + nonce_account: Some(nonce_account), + nonce_authority: 3, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Validated( - Source::NonceAccount(nonce_account), - blockhash - ), - nonce_account: Some(nonce_account), - nonce_authority: 3, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2064,39 +2803,77 @@ mod tests { ); // test init with an authed voter - let authed = solana_pubkey::new_rand(); + let authed_keypair = Keypair::new(); + let authed = authed_keypair.pubkey(); + let (authed_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&authed_keypair, tmp_file.as_file_mut()).unwrap(); let (keypair_file, mut tmp_file) = make_tmp_file(); let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - - let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--authorized-voter", - &authed.to_string(), - ]); + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &authed_keypair); + + let test_create_vote_account3 = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + &authed_keypair_file, + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--authorized-voter", + &authed.to_string(), + ]) + }; + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: Some(authed), + authorized_withdrawer, + bls_pubkey, + bls_proof_of_possession, + inflation_rewards_commission_bps: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: Some(authed), + authorized_withdrawer, + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: Some(authed), - authorized_withdrawer, - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(keypair), @@ -2109,33 +2886,68 @@ mod tests { let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); // succeed even though withdrawer unsafe (because forcefully allowed) - let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &identity_keypair_file, - "--allow-unsafe-authorized-withdrawer", - ]); + let test_create_vote_account4 = if with_bls { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account-with-bls", + &keypair_file, + &identity_keypair_file, + &identity_keypair_file, + "--allow-unsafe-authorized-withdrawer", + ]) + } else { + test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &identity_keypair_file, + "--allow-unsafe-authorized-withdrawer", + ]) + }; + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &identity_keypair); + let expected_command = if with_bls { + CliCommand::CreateVoteAccountV2 { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer: identity_keypair.pubkey(), + inflation_rewards_commission_bps: 0, + bls_pubkey, + bls_proof_of_possession, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + } else { + CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer: identity_keypair.pubkey(), + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + } + }; assert_eq!( parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer: identity_keypair.pubkey(), - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - }, + command: expected_command, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2143,6 +2955,29 @@ mod tests { ], } ); + } + + #[test] + fn test_parse_vote_other_commands() { + let test_commands = get_clap_app("test", "desc", "version"); + let keypair = Keypair::new(); + let pubkey = keypair.pubkey(); + let pubkey_string = pubkey.to_string(); + + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let default_signer = DefaultSigner::new("", &default_keypair_file); + + let blockhash = Hash::default(); + let blockhash_string = format!("{blockhash}"); + + let (identity_keypair_file, mut tmp_file) = make_tmp_file(); + let identity_keypair = Keypair::new(); + write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap(); + let (keypair_file, mut tmp_file) = make_tmp_file(); + let keypair = Keypair::new(); + write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); let test_update_validator = test_commands.clone().get_matches_from(vec![ "test", diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index b2bddcb59c4fc9..e67a3e6c9d4490 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -31,6 +31,7 @@ use { state::{Lockup, StakeAuthorize, StakeStateV2}, }, solana_test_validator::{TestValidator, TestValidatorGenesis}, + solana_vote_program::vote_state::create_bls_pubkey_and_proof_of_possession, test_case::test_case, }; @@ -77,14 +78,17 @@ async fn test_stake_delegation_force() { // Create vote account let vote_keypair = Keypair::new(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_keypair.pubkey()); config.signers = vec![&default_signer, &vote_keypair]; - config.command = CliCommand::CreateVoteAccount { + config.command = CliCommand::CreateVoteAccountV2 { vote_account: 1, seed: None, identity_account: 0, authorized_voter: None, authorized_withdrawer, - commission: 0, + bls_pubkey, + bls_proof_of_possession, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -93,6 +97,7 @@ async fn test_stake_delegation_force() { memo: None, fee_payer: 0, compute_unit_price: None, + inflation_rewards_commission_bps: 0, }; process_command(&config).await.unwrap(); diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 983ca318799d0c..96ebaf99a5c3f5 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -16,7 +16,9 @@ use { solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery, solana_signer::{null_signer::NullSigner, Signer}, solana_test_validator::TestValidator, - solana_vote_program::vote_state::{VoteAuthorize, VoteStateV4}, + solana_vote_program::vote_state::{ + create_bls_pubkey_and_proof_of_possession, VoteAuthorize, VoteStateV4, + }, test_case::test_case, }; @@ -47,15 +49,18 @@ async fn test_vote_authorize_and_withdraw(compute_unit_price: Option) { // Create vote account let vote_account_keypair = Keypair::new(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_account_keypair.pubkey()); let vote_account_pubkey = vote_account_keypair.pubkey(); config.signers = vec![&default_signer, &vote_account_keypair]; - config.command = CliCommand::CreateVoteAccount { + config.command = CliCommand::CreateVoteAccountV2 { vote_account: 1, seed: None, identity_account: 0, authorized_voter: None, + bls_pubkey, + bls_proof_of_possession, authorized_withdrawer: config.signers[0].pubkey(), - commission: 0, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -64,6 +69,7 @@ async fn test_vote_authorize_and_withdraw(compute_unit_price: Option) { memo: None, fee_payer: 0, compute_unit_price, + inflation_rewards_commission_bps: 0, }; process_command(&config).await.unwrap(); let vote_account = rpc_client diff --git a/dev-bins/Cargo.lock b/dev-bins/Cargo.lock index 914c19e018c1ec..a9b3ad30d3dbdb 100644 --- a/dev-bins/Cargo.lock +++ b/dev-bins/Cargo.lock @@ -6443,9 +6443,9 @@ dependencies = [ [[package]] name = "solana-bls-signatures" -version = "1.0.0" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c75573697bbb148afa8209aa3ce228ca0754584c9a8a91e818db0f706ae4fb" +checksum = "4acb2f8e2ce54f8798c8a5eb5d06f4cb4f596a5879be5ad81e95a60a1bde9359" dependencies = [ "base64 0.22.1", "blst", @@ -9828,6 +9828,7 @@ dependencies = [ "serde", "solana-account", "solana-bincode", + "solana-bls-signatures", "solana-clock", "solana-epoch-schedule", "solana-hash 3.1.0", diff --git a/feature-set/src/lib.rs b/feature-set/src/lib.rs index eeec75f5902950..e884b3e466dfd5 100644 --- a/feature-set/src/lib.rs +++ b/feature-set/src/lib.rs @@ -175,6 +175,8 @@ impl FeatureSet { fix_alt_bn128_pairing_length_check: self .is_active(&fix_alt_bn128_pairing_length_check::id()), alt_bn128_little_endian: self.is_active(&alt_bn128_little_endian::id()), + bls_pubkey_management_in_vote_account: self + .is_active(&bls_pubkey_management_in_vote_account::id()), } } } @@ -1209,6 +1211,10 @@ pub mod alt_bn128_little_endian { solana_pubkey::declare_id!("bnS3pWfLrxHRJvMyLm6EaYQkP7A2Fe9DxoKv4aGA8YM"); } +pub mod bls_pubkey_management_in_vote_account { + solana_pubkey::declare_id!("EGJLweNUVskAPEwpjvNB7JT6uUi6h4mFhowNYXVSrimG"); +} + pub static FEATURE_NAMES: LazyLock> = LazyLock::new(|| { [ (secp256k1_program_enabled::id(), "secp256k1 program"), @@ -2168,6 +2174,10 @@ pub static FEATURE_NAMES: LazyLock> = LazyLock::n alt_bn128_little_endian::id(), "SIMD-0284: Add little-endian compatibility for alt_bn128", ), + ( + bls_pubkey_management_in_vote_account::id(), + "SIMD-0387: BLS Pubkey Management in Vote Account", + ), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 556b6ad92e81a9..8f19e3658fbe25 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -950,7 +950,6 @@ mod tests { solana_genesis_config::GenesisConfig, solana_stake_interface as stake, std::{collections::HashMap, fs::remove_file, io::Write, path::Path}, - test_case::test_case, }; #[test] @@ -1300,10 +1299,8 @@ mod tests { assert_eq!(genesis_config.accounts.len(), 3); } - #[test_case(true; "add bls pubkey")] - #[test_case(false; "no bls pubkey")] - // It's wrong to have (true, false) combination, Alpenglow requires BLS keys - fn test_append_validator_accounts_to_genesis(add_bls_pubkey: bool) { + #[test] + fn test_append_validator_accounts_to_genesis() { // Test invalid file returns error assert!(load_validator_accounts( "unknownfile", @@ -1315,19 +1312,12 @@ mod tests { let mut genesis_config = GenesisConfig::default(); - let generate_bls_pubkey = || { - if add_bls_pubkey { - Some(BLSKeypair::new().public.to_string()) - } else { - None - } - }; let validator_accounts = vec![ StakedValidatorAccountInfo { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: generate_bls_pubkey(), + bls_pubkey: Some(BLSKeypair::new().public.to_string()), balance_lamports: 100000000000, stake_lamports: 10000000000, }, @@ -1335,7 +1325,7 @@ mod tests { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: generate_bls_pubkey(), + bls_pubkey: Some(BLSKeypair::new().public.to_string()), balance_lamports: 200000000000, stake_lamports: 20000000000, }, @@ -1343,7 +1333,7 @@ mod tests { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: generate_bls_pubkey(), + bls_pubkey: Some(BLSKeypair::new().public.to_string()), balance_lamports: 300000000000, stake_lamports: 30000000000, }, @@ -1352,11 +1342,7 @@ mod tests { let serialized = serde_yaml::to_string(&validator_accounts).unwrap(); // write accounts to file - let filename = if add_bls_pubkey { - "test_append_validator_accounts_to_genesis_with_bls.yml" - } else { - "test_append_validator_accounts_to_genesis_without_bls.yml" - }; + let filename = "test_append_validator_accounts_to_genesis.yml"; let path = Path::new(filename); let mut file = File::create(path).unwrap(); file.write_all(b"validator_accounts:\n").unwrap(); @@ -1393,17 +1379,12 @@ mod tests { assert_eq!(vote_state.authorized_withdrawer, identity_pk); let authorized_voters = &vote_state.authorized_voters; assert_eq!(authorized_voters.first().unwrap().1, &identity_pk); - if add_bls_pubkey { - assert_eq!( - bls_pubkey_to_compressed_bytes( - &BLSPubkey::from_str(b64_account.bls_pubkey.as_ref().unwrap()).unwrap() - ), - vote_state.bls_pubkey_compressed.unwrap() - ); - } else { - assert!(b64_account.bls_pubkey.is_none()); - assert!(vote_state.bls_pubkey_compressed.is_none()); - } + assert_eq!( + bls_pubkey_to_compressed_bytes( + &BLSPubkey::from_str(b64_account.bls_pubkey.as_ref().unwrap()).unwrap() + ), + vote_state.bls_pubkey_compressed.unwrap() + ); // check stake account let stake_pk = b64_account.stake_account.parse().unwrap(); diff --git a/ledger/src/leader_schedule_utils.rs b/ledger/src/leader_schedule_utils.rs index 747784c865e461..825f8d58911db4 100644 --- a/ledger/src/leader_schedule_utils.rs +++ b/ledger/src/leader_schedule_utils.rs @@ -1,5 +1,7 @@ use { - crate::leader_schedule::{LeaderSchedule, VoteKeyedLeaderSchedule}, + crate::leader_schedule::{ + IdentityKeyedLeaderSchedule, LeaderSchedule, VoteKeyedLeaderSchedule, + }, solana_clock::{Epoch, Slot, NUM_CONSECUTIVE_LEADER_SLOTS}, solana_pubkey::Pubkey, solana_runtime::bank::Bank, @@ -8,14 +10,26 @@ use { /// Return the leader schedule for the given epoch. pub fn leader_schedule(epoch: Epoch, bank: &Bank) -> Option { - bank.epoch_vote_accounts(epoch).map(|vote_accounts_map| { - Box::new(VoteKeyedLeaderSchedule::new( - vote_accounts_map, - epoch, - bank.get_slots_in_epoch(epoch), - NUM_CONSECUTIVE_LEADER_SLOTS, - )) as LeaderSchedule - }) + let use_new_leader_schedule = bank.should_use_vote_keyed_leader_schedule(epoch)?; + if use_new_leader_schedule { + bank.epoch_vote_accounts(epoch).map(|vote_accounts_map| { + Box::new(VoteKeyedLeaderSchedule::new( + vote_accounts_map, + epoch, + bank.get_slots_in_epoch(epoch), + NUM_CONSECUTIVE_LEADER_SLOTS, + )) as LeaderSchedule + }) + } else { + bank.epoch_staked_nodes(epoch).map(|stakes| { + Box::new(IdentityKeyedLeaderSchedule::new( + &stakes, + epoch, + bank.get_slots_in_epoch(epoch), + NUM_CONSECUTIVE_LEADER_SLOTS, + )) as LeaderSchedule + }) + } } /// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot @@ -83,20 +97,34 @@ mod tests { super::*, solana_runtime::genesis_utils::{ bootstrap_validator_stake_lamports, create_genesis_config_with_leader, + deactivate_features, }, + test_case::test_case, }; - #[test] - fn test_leader_schedule_via_bank() { + #[test_case(true; "vote keyed leader schedule")] + #[test_case(false; "identity keyed leader schedule")] + fn test_leader_schedule_via_bank(use_vote_keyed_leader_schedule: bool) { let pubkey = solana_pubkey::new_rand(); - let genesis_config = + let mut genesis_config = create_genesis_config_with_leader(0, &pubkey, bootstrap_validator_stake_lamports()) .genesis_config; + if !use_vote_keyed_leader_schedule { + deactivate_features( + &mut genesis_config, + &vec![agave_feature_set::enable_vote_address_leader_schedule::id()], + ); + } + let bank = Bank::new_for_tests(&genesis_config); let leader_schedule = leader_schedule(0, &bank).unwrap(); - assert!(leader_schedule.get_vote_key_at_slot_index(0).is_some()); + assert_eq!( + leader_schedule.get_vote_key_at_slot_index(0).is_some(), + use_vote_keyed_leader_schedule + ); + assert_eq!(leader_schedule[0], pubkey); assert_eq!(leader_schedule[1], pubkey); assert_eq!(leader_schedule[2], pubkey); diff --git a/ledger/src/staking_utils.rs b/ledger/src/staking_utils.rs index 9928601eb9e33f..2e2925720ba5c0 100644 --- a/ledger/src/staking_utils.rs +++ b/ledger/src/staking_utils.rs @@ -18,7 +18,10 @@ pub(crate) mod tests { solana_vote::vote_account::{VoteAccount, VoteAccounts}, solana_vote_program::{ vote_instruction, - vote_state::{VoteInit, VoteStateV4, VoteStateVersions}, + vote_state::{ + create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4, + VoteStateVersions, + }, }, std::sync::Arc, }; @@ -41,17 +44,22 @@ pub(crate) mod tests { bank.process_transaction(&tx).unwrap(); } + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + process_instructions( bank, &[from_account, vote_account, validator_identity_account], - &vote_instruction::create_account_with_config( + &vote_instruction::create_account_with_config_v2( &from_account.pubkey(), &vote_pubkey, - &VoteInit { + &VoteInitV2 { node_pubkey: validator_identity_account.pubkey(), authorized_voter: vote_pubkey, authorized_withdrawer: vote_pubkey, - commission: 0, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, amount, vote_instruction::CreateVoteAccountConfig { @@ -97,15 +105,19 @@ pub(crate) mod tests { let node1 = solana_pubkey::new_rand(); let vote_pubkey1 = solana_pubkey::new_rand(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey1); + // Node 1 has stake of 3 for i in 0..3 { stakes.push(( i, - VoteStateV4::new_with_defaults( - &vote_pubkey1, - &VoteInit { + VoteStateV4::new( + &VoteInitV2 { node_pubkey: node1, - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, &Clock::default(), ), @@ -115,14 +127,17 @@ pub(crate) mod tests { // Node 1 has stake of 5 let node2 = solana_pubkey::new_rand(); let vote_pubkey2 = solana_pubkey::new_rand(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey2); stakes.push(( 5, - VoteStateV4::new_with_defaults( - &vote_pubkey2, - &VoteInit { + VoteStateV4::new( + &VoteInitV2 { node_pubkey: node2, - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, &Clock::default(), ), diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 2c4bf183117314..670f5ed868583b 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -54,7 +54,7 @@ use { solana_transaction_error::TransportError, solana_vote_program::{ vote_instruction, - vote_state::{self, VoteInit, VoteStateV4}, + vote_state::{self, create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4}, }, std::{ collections::HashMap, @@ -1012,14 +1012,18 @@ impl LocalCluster { == 0 { // 1) Create vote account - let instructions = vote_instruction::create_account_with_config( + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey); + let instructions = vote_instruction::create_account_with_config_v2( &from_account.pubkey(), &vote_account_pubkey, - &VoteInit { + &VoteInitV2 { node_pubkey, authorized_voter: vote_account_pubkey, authorized_withdrawer: vote_account_pubkey, - commission: 0, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, amount, vote_instruction::CreateVoteAccountConfig { diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index 67ec8e281d7ad7..a8ab1de7ec1f82 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -319,7 +319,7 @@ setup_validator_accounts() { fi echo "Creating validator vote account" - wallet create-vote-account "$vote_account" "$identity" "$authorized_withdrawer" || return $? + wallet create-vote-account-with-bls "$vote_account" "$identity" "$authorized_withdrawer"|| return $? fi echo "Validator vote account configured" diff --git a/program-test/tests/setup.rs b/program-test/tests/setup.rs index 3b6373e8ee9682..bb0ba4838ef184 100644 --- a/program-test/tests/setup.rs +++ b/program-test/tests/setup.rs @@ -12,7 +12,7 @@ use { solana_transaction::Transaction, solana_vote_program::{ vote_instruction, - vote_state::{self, VoteInit, VoteStateV4}, + vote_state::{self, create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4}, }, }; @@ -56,14 +56,18 @@ pub async fn setup_vote(context: &mut ProgramTestContext) -> Pubkey { )); let vote_lamports = Rent::default().minimum_balance(VoteStateV4::size_of()); let vote_keypair = Keypair::new(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_keypair.pubkey()); let user_keypair = Keypair::new(); - instructions.append(&mut vote_instruction::create_account_with_config( + instructions.append(&mut vote_instruction::create_account_with_config_v2( &context.payer.pubkey(), &vote_keypair.pubkey(), - &VoteInit { + &VoteInitV2 { node_pubkey: validator_keypair.pubkey(), authorized_voter: user_keypair.pubkey(), - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, vote_lamports, vote_instruction::CreateVoteAccountConfig { diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 0e70eeaad9f8d2..ac072b797ad8a6 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -6277,9 +6277,9 @@ dependencies = [ [[package]] name = "solana-bls-signatures" -version = "1.0.0" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c75573697bbb148afa8209aa3ce228ca0754584c9a8a91e818db0f706ae4fb" +checksum = "4acb2f8e2ce54f8798c8a5eb5d06f4cb4f596a5879be5ad81e95a60a1bde9359" dependencies = [ "base64 0.22.1", "blst", @@ -10360,6 +10360,7 @@ dependencies = [ "serde", "solana-account", "solana-bincode", + "solana-bls-signatures", "solana-clock", "solana-epoch-schedule", "solana-hash 3.1.0", diff --git a/programs/vote/Cargo.toml b/programs/vote/Cargo.toml index 50cf53b28c7f3c..5b188809d132c6 100644 --- a/programs/vote/Cargo.toml +++ b/programs/vote/Cargo.toml @@ -35,6 +35,7 @@ num-traits = { workspace = true } serde = { workspace = true } solana-account = { workspace = true } solana-bincode = { workspace = true } +solana-bls-signatures = { workspace = true } solana-clock = { workspace = true } solana-epoch-schedule = { workspace = true } solana-frozen-abi = { workspace = true, optional = true, features = [ diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index cadadaae235edb..9be50632f181e5 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -26,6 +26,7 @@ fn process_authorize_with_seed_instruction( authorization_type: VoteAuthorize, current_authority_derived_key_owner: &Pubkey, current_authority_derived_key_seed: &str, + is_bls_pubkey_feature_enabled: bool, ) -> Result<(), InstructionError> { let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?; let mut expected_authority_keys: HashSet = HashSet::default(); @@ -49,9 +50,17 @@ fn process_authorize_with_seed_instruction( authorization_type, &expected_authority_keys, &clock, + is_bls_pubkey_feature_enabled, ) } +fn is_bls_pubkey_feature_enabled(invoke_context: &InvokeContext) -> bool { + invoke_context + .get_feature_set() + .bls_pubkey_management_in_vote_account + && invoke_context.get_feature_set().vote_state_v4 +} + // Citing `runtime/src/block_cost_limit.rs`, vote has statically defined 2100 // units; can consume based on instructions in the future like `bpf_loader` does. pub const DEFAULT_COMPUTE_UNITS: u64 = 2_100; @@ -76,8 +85,13 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| }; let signers = instruction_context.get_signers()?; + let is_bls_pubkey_feature_enabled = is_bls_pubkey_feature_enabled(invoke_context); match limited_deserialize(data, solana_packet::PACKET_DATA_SIZE as u64)? { VoteInstruction::InitializeAccount(vote_init) => { + // If the BLS pubkey feature is active, reject the instruction + if is_bls_pubkey_feature_enabled { + return Err(InstructionError::InvalidInstructionData); + } let rent = get_sysvar_with_account_check::rent(invoke_context, &instruction_context, 1)?; if !rent.is_exempt(me.get_lamports(), me.get_data().len()) { @@ -97,6 +111,7 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| vote_authorize, &signers, &clock, + is_bls_pubkey_feature_enabled, ) } VoteInstruction::AuthorizeWithSeed(args) => { @@ -110,6 +125,7 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| args.authorization_type, &args.current_authority_derived_key_owner, args.current_authority_derived_key_seed.as_str(), + is_bls_pubkey_feature_enabled, ) } VoteInstruction::AuthorizeCheckedWithSeed(args) => { @@ -127,6 +143,7 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| args.authorization_type, &args.current_authority_derived_key_owner, args.current_authority_derived_key_seed.as_str(), + is_bls_pubkey_feature_enabled, ) } VoteInstruction::UpdateValidatorIdentity => { @@ -253,11 +270,28 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| vote_authorize, &signers, &clock, + is_bls_pubkey_feature_enabled, + ) + } + VoteInstruction::InitializeAccountV2(vote_init_v2) => { + if !is_bls_pubkey_feature_enabled { + return Err(InstructionError::InvalidInstructionData); + } + let rent = invoke_context.get_sysvar_cache().get_rent()?; + if !rent.is_exempt(me.get_lamports(), me.get_data().len()) { + return Err(InstructionError::InsufficientFunds); + } + let clock = invoke_context.get_sysvar_cache().get_clock()?; + vote_state::initialize_account_v2( + &mut me, + target_version, + &vote_init_v2, + &signers, + &clock, ) } // New instructions not yet implemented. - VoteInstruction::InitializeAccountV2(_) - | VoteInstruction::UpdateCommissionCollector(_) + VoteInstruction::UpdateCommissionCollector(_) | VoteInstruction::UpdateCommissionBps { .. } | VoteInstruction::DepositDelegatorRewards { .. } => { Err(InstructionError::InvalidInstructionData) @@ -279,11 +313,11 @@ mod tests { vote_switch, withdraw, CreateVoteAccountConfig, VoteInstruction, }, vote_state::{ - self, - handler::{self, VoteStateHandle, VoteStateHandler}, + self, create_bls_pubkey_and_proof_of_possession, + handler::{VoteStateHandle, VoteStateHandler}, Lockout, TowerSync, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs, - VoteAuthorizeWithSeedArgs, VoteInit, VoteStateUpdate, VoteStateV3, VoteStateV4, - VoteStateVersions, + VoteAuthorizeWithSeedArgs, VoteInit, VoteInitV2, VoteStateUpdate, VoteStateV3, + VoteStateV4, VoteStateVersions, }, }, bincode::serialize, @@ -300,9 +334,15 @@ mod tests { solana_sdk_ids::sysvar, solana_slot_hashes::SlotHashes, solana_svm_feature_set::SVMFeatureSet, - solana_vote_interface::instruction::{tower_sync, tower_sync_switch}, + solana_vote_interface::{ + instruction::{tower_sync, tower_sync_switch}, + state::{ + VoterWithBLSArgs, BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, + BLS_PUBLIC_KEY_COMPRESSED_SIZE, + }, + }, std::{collections::HashSet, str::FromStr}, - test_case::test_case, + test_case::{test_case, test_matrix}, }; // They're the same, but just for posterity. @@ -343,6 +383,7 @@ mod tests { fn process_instruction( vote_state_v4_enabled: bool, + bls_pubkey_management_in_vote_account_enabled: bool, instruction_data: &[u8], transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, @@ -360,6 +401,8 @@ mod tests { |_invoke_context| {}, &SVMFeatureSet { vote_state_v4: vote_state_v4_enabled, + bls_pubkey_management_in_vote_account: + bls_pubkey_management_in_vote_account_enabled, ..SVMFeatureSet::all_enabled() }, ) @@ -367,6 +410,7 @@ mod tests { fn process_instruction_as_one_arg( vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, instruction: &Instruction, expected_result: Result<(), InstructionError>, ) -> Vec { @@ -410,6 +454,7 @@ mod tests { .collect(); process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &instruction.data, transaction_accounts, instruction.accounts.clone(), @@ -575,7 +620,7 @@ mod tests { let lamports = Rent::default().minimum_balance(space); let mut vote_state = if vote_state_v4_enabled { - let v4 = handler::create_new_vote_state_v4(&vote_pubkey, &vote_init, &clock); + let v4 = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock); VoteStateHandler::new_v4(v4) } else { let v3 = VoteStateV3::new(&vote_init, &clock); @@ -635,6 +680,7 @@ mod tests { fn test_vote_process_instruction_decode_bail(vote_state_v4_enabled: bool) { process_instruction( vote_state_v4_enabled, + false, &[], Vec::new(), Vec::new(), @@ -642,9 +688,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_initialize_vote_account(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_initialize_vote_account( + vote_state_v4_enabled: bool, + bls_pubkey_management_in_vote_account_enabled: bool, + ) { let vote_pubkey = solana_pubkey::new_rand(); let vote_account = AccountSharedData::new(100, vote_state_size_of(vote_state_v4_enabled), &id()); @@ -680,9 +728,51 @@ mod tests { }, ]; - // init should pass + // processing incompatible instruction should fail + if vote_state_v4_enabled && bls_pubkey_management_in_vote_account_enabled { + // If both features are enabled, the old instruction should be rejected + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + return; + } else { + // If either feature is disabled, the new instruction should be rejected + let bad_instruction_data = + serialize(&VoteInstruction::InitializeAccountV2(VoteInitV2 { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + ..Default::default() + })) + .unwrap(); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &bad_instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + } + let accounts = process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, vec![ (vote_pubkey, vote_account.clone()), @@ -697,6 +787,7 @@ mod tests { // reinit should fail process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, vec![ (vote_pubkey, accounts[0].clone()), @@ -711,6 +802,7 @@ mod tests { // init should fail, account is too big process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, vec![ ( @@ -733,7 +825,215 @@ mod tests { instruction_accounts[3].is_signer = false; process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + } + + #[test_matrix([false, true], [false, true])] + fn test_initialize_vote_account_v2( + vote_state_v4_enabled: bool, + bls_pubkey_management_in_vote_account_enabled: bool, + ) { + let vote_pubkey = solana_pubkey::new_rand(); + let vote_account = + AccountSharedData::new(100, vote_state_size_of(vote_state_v4_enabled), &id()); + let node_pubkey = solana_pubkey::new_rand(); + let node_account = AccountSharedData::default(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let instruction_data = serialize(&VoteInstruction::InitializeAccountV2(VoteInitV2 { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..Default::default() + })) + .unwrap(); + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::rent::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: node_pubkey, + is_signer: true, + is_writable: false, + }, + ]; + + // processing incompatible instruction should fail + if vote_state_v4_enabled && bls_pubkey_management_in_vote_account_enabled { + // If both features are enabled, the old instruction should be rejected + let bad_instruction_data = serialize(&VoteInstruction::InitializeAccount(VoteInit { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + commission: 0, + })) + .unwrap(); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &bad_instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + } else { + // If either feature is disabled, the new instruction should be rejected + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + return; + } + + let accounts = process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Ok(()), + ); + + // reinit should fail + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, + vec![ + (vote_pubkey, accounts[0].clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, accounts[3].clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::AccountAlreadyInitialized), + ); + + // init should fail, account is too big + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + ( + vote_pubkey, + AccountSharedData::new( + 100, + 2 * vote_state_size_of(vote_state_v4_enabled), + &id(), + ), + ), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidAccountData), + ); + + // init should fail, node_pubkey didn't sign the transaction + instruction_accounts[3].is_signer = false; + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + } + + #[test] + fn test_initialize_vote_account_v2_bad_proof_of_possession() { + let vote_pubkey = solana_pubkey::new_rand(); + let vote_account = AccountSharedData::new(100, VoteStateV4::size_of(), &id()); + let node_pubkey = solana_pubkey::new_rand(); + let node_account = AccountSharedData::default(); + let instruction_with_bad_pop = + serialize(&VoteInstruction::InitializeAccountV2(VoteInitV2 { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + authorized_voter_bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + authorized_voter_bls_proof_of_possession: [2u8; + BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + ..Default::default() + })) + .unwrap(); + let instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::rent::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: node_pubkey, + is_signer: true, + is_writable: false, + }, + ]; + process_instruction( + true, + true, + &instruction_with_bad_pop, vec![ (vote_pubkey, vote_account), (sysvar::rent::id(), create_default_rent_account()), @@ -741,7 +1041,7 @@ mod tests { (node_pubkey, node_account), ], instruction_accounts, - Err(InstructionError::MissingRequiredSignature), + Err(InstructionError::InvalidArgument), ); } @@ -779,6 +1079,7 @@ mod tests { instruction_accounts[1].is_signer = false; let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -796,6 +1097,7 @@ mod tests { instruction_accounts[2].is_signer = false; let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -812,6 +1114,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -863,6 +1166,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::UpdateCommission(200)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -878,6 +1182,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -894,6 +1199,7 @@ mod tests { instruction_accounts[1].is_signer = false; let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -951,6 +1257,7 @@ mod tests { instruction_accounts[0].is_signer = false; process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -961,6 +1268,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -992,6 +1300,7 @@ mod tests { ); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1005,6 +1314,7 @@ mod tests { ); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1018,6 +1328,7 @@ mod tests { ); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1031,6 +1342,7 @@ mod tests { transaction_accounts[0] = (vote_pubkey, vote_account); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1043,9 +1355,11 @@ mod tests { } } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_authorize_voter(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_authorize_voter( + vote_state_v4_enabled: bool, + bls_pubkey_management_in_vote_account_enabled: bool, + ) { let (vote_pubkey, vote_account) = create_test_account(vote_state_v4_enabled); let authorized_voter_pubkey = solana_pubkey::new_rand(); let clock = Clock { @@ -1059,9 +1373,205 @@ mod tests { VoteAuthorize::Voter, )) .unwrap(); + let mut transaction_accounts = vec![ - (vote_pubkey, vote_account), - (sysvar::clock::id(), clock_account), + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), + (authorized_voter_pubkey, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // processing incompatible instruction should fail + if vote_state_v4_enabled && bls_pubkey_management_in_vote_account_enabled { + // If both features are enabled, the old instruction should be rejected + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), + (authorized_voter_pubkey, AccountSharedData::default()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + return; + } else { + // If either feature is disabled, the new instruction should be rejected + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let bad_instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), + )) + .unwrap(); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &bad_instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), + (authorized_voter_pubkey, AccountSharedData::default()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + } + + // should fail, unsigned + instruction_accounts[0].is_signer = false; + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[0].is_signer = true; + + // should pass + let accounts = process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should fail, already set an authorized voter earlier for leader_schedule_epoch == 2 + transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(VoteError::TooSoonToReauthorize.into()), + ); + + // should pass, verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) + instruction_accounts[0].is_signer = false; + instruction_accounts.push(AccountMeta { + pubkey: authorized_voter_pubkey, + is_signer: true, + is_writable: false, + }); + let clock = Clock { + // The authorized voter was set when leader_schedule_epoch == 2, so will + // take effect when epoch == 3 + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + transaction_accounts[1] = (sysvar::clock::id(), clock_account); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + instruction_accounts[0].is_signer = true; + instruction_accounts.pop(); + + // should fail, not signed by authorized voter + let (vote, instruction_datas) = create_serialized_votes(); + let slot_hashes = SlotHashes::new(&[(*vote.slots.last().unwrap(), vote.hash)]); + let slot_hashes_account = account::create_account_shared_data_for_test(&slot_hashes); + transaction_accounts.push((sysvar::slot_hashes::id(), slot_hashes_account)); + instruction_accounts.insert( + 1, + AccountMeta { + pubkey: sysvar::slot_hashes::id(), + is_signer: false, + is_writable: false, + }, + ); + let mut authorized_instruction_accounts = instruction_accounts.clone(); + authorized_instruction_accounts.push(AccountMeta { + pubkey: authorized_voter_pubkey, + is_signer: true, + is_writable: false, + }); + + for (instruction_data, is_tower_sync) in instruction_datas { + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(if is_tower_sync { + InstructionError::MissingRequiredSignature + } else { + InstructionError::InvalidInstructionData + }), + ); + + // should pass, signed by authorized voter + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &instruction_data, + transaction_accounts.clone(), + authorized_instruction_accounts.clone(), + if is_tower_sync { + Ok(()) + } else { + Err(InstructionError::InvalidInstructionData) + }, + ); + } + } + + #[test_matrix([false, true], [false, true])] + fn test_authorize_voter_with_bls( + vote_state_v4_enabled: bool, + bls_pubkey_management_in_vote_account_enabled: bool, + ) { + agave_logger::setup(); + let (vote_pubkey, vote_account) = create_test_account(vote_state_v4_enabled); + let authorized_voter_pubkey = solana_pubkey::new_rand(); + let clock = Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), + )) + .unwrap(); + + let mut transaction_accounts = vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), (authorized_voter_pubkey, AccountSharedData::default()), ]; let mut instruction_accounts = vec![ @@ -1077,10 +1587,104 @@ mod tests { }, ]; + // processing incompatible instruction should fail + if vote_state_v4_enabled && bls_pubkey_management_in_vote_account_enabled { + // If both features are enabled, the old instruction should be accepted when + // the account does not have a BLS key. + let (new_vote_pubkey, vote_account_no_bls_key) = create_test_account(false); + let new_authorized_voter_pubkey = solana_pubkey::new_rand(); + let old_instruction_data = serialize(&VoteInstruction::Authorize( + new_authorized_voter_pubkey, + VoteAuthorize::Voter, + )) + .unwrap(); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &old_instruction_data, + vec![ + (new_vote_pubkey, vote_account_no_bls_key), + (sysvar::clock::id(), clock_account.clone()), + (new_authorized_voter_pubkey, AccountSharedData::default()), + ], + vec![ + AccountMeta { + pubkey: new_vote_pubkey, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ], + Ok(()), + ); + // However, once the BLS key is set, the old instruction should be rejected + let (new_vote_pubkey, vote_account_with_bls_key) = create_test_account(true); + let new_authorized_voter_pubkey = solana_pubkey::new_rand(); + let old_instruction_data = serialize(&VoteInstruction::Authorize( + new_authorized_voter_pubkey, + VoteAuthorize::Voter, + )) + .unwrap(); + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &old_instruction_data, + vec![ + (new_vote_pubkey, vote_account_with_bls_key), + (sysvar::clock::id(), clock_account.clone()), + (new_authorized_voter_pubkey, AccountSharedData::default()), + ], + vec![ + AccountMeta { + pubkey: new_vote_pubkey, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ], + Err(InstructionError::InvalidInstructionData), + ); + } else { + // If either feature is disabled, the new instruction should be rejected + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let bad_instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), + )) + .unwrap(); + + process_instruction( + vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, + &bad_instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), + (authorized_voter_pubkey, AccountSharedData::default()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidInstructionData), + ); + return; + } + // should fail, unsigned instruction_accounts[0].is_signer = false; process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1091,6 +1695,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1101,6 +1706,7 @@ mod tests { transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1125,6 +1731,7 @@ mod tests { transaction_accounts[1] = (sysvar::clock::id(), clock_account); process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1156,6 +1763,7 @@ mod tests { for (instruction_data, is_tower_sync) in instruction_datas { process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1169,6 +1777,7 @@ mod tests { // should pass, signed by authorized voter process_instruction( vote_state_v4_enabled, + bls_pubkey_management_in_vote_account_enabled, &instruction_data, transaction_accounts.clone(), authorized_instruction_accounts.clone(), @@ -1181,6 +1790,70 @@ mod tests { } } + #[test] + fn test_authorize_voter_with_bls_bad_proof_of_possession() { + let (vote_pubkey, vote_account) = create_test_account(true); + let authorized_voter_pubkey = solana_pubkey::new_rand(); + let clock = Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + let transaction_accounts = vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), clock_account.clone()), + (authorized_voter_pubkey, AccountSharedData::default()), + ]; + let instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // Test that bad proof of possession fails authorization + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + bls_proof_of_possession: [2u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + }), + )) + .unwrap(); + process_instruction( + true, + true, + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::InvalidArgument), + ); + + // Test that if the account already has a BLS pubkey, VoteAuthorize::Voter + // is rejected + // Initialize a new vote account with BLS pubkey + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::Voter, + )) + .unwrap(); + process_instruction( + true, + true, + &instruction_data, + transaction_accounts, + instruction_accounts, + Err(InstructionError::InvalidInstructionData), + ); + } + #[test_case(false ; "VoteStateV3")] #[test_case(true ; "VoteStateV4")] fn test_authorize_withdrawer(vote_state_v4_enabled: bool) { @@ -1213,6 +1886,7 @@ mod tests { instruction_accounts[0].is_signer = false; process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1223,6 +1897,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1239,6 +1914,7 @@ mod tests { transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1255,6 +1931,7 @@ mod tests { .unwrap(); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1290,6 +1967,7 @@ mod tests { // should pass, withdraw using authorized_withdrawer to authorized_withdrawer's account let accounts = process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Authorize( authorized_withdrawer_pubkey, VoteAuthorize::Withdrawer, @@ -1308,6 +1986,7 @@ mod tests { transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); let accounts = process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1323,6 +2002,7 @@ mod tests { transaction_accounts[0] = (vote_pubkey, vote_account); process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1333,6 +2013,7 @@ mod tests { // should pass process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1342,6 +2023,7 @@ mod tests { // should fail, insufficient funds process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1352,6 +2034,7 @@ mod tests { let withdraw_lamports = 42; let accounts = process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(withdraw_lamports)).unwrap(), transaction_accounts, instruction_accounts, @@ -1406,6 +2089,7 @@ mod tests { instruction_accounts[0].pubkey = vote_pubkey_1; process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1416,6 +2100,7 @@ mod tests { instruction_accounts[0].pubkey = vote_pubkey_2; process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1426,6 +2111,7 @@ mod tests { instruction_accounts[0].pubkey = vote_pubkey_1; process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), @@ -1436,6 +2122,7 @@ mod tests { instruction_accounts[0].pubkey = vote_pubkey_2; process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), transaction_accounts, instruction_accounts, @@ -1445,6 +2132,7 @@ mod tests { fn perform_authorize_with_seed_test( vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, authorization_type: VoteAuthorize, vote_pubkey: Pubkey, vote_account: AccountSharedData, @@ -1486,6 +2174,7 @@ mod tests { instruction_accounts[2].is_signer = false; process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeWithSeed( VoteAuthorizeWithSeedArgs { authorization_type, @@ -1504,6 +2193,7 @@ mod tests { // Can't change authority if seed doesn't match. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeWithSeed( VoteAuthorizeWithSeedArgs { authorization_type, @@ -1521,6 +2211,7 @@ mod tests { // Can't change authority if owner doesn't match. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeWithSeed( VoteAuthorizeWithSeedArgs { authorization_type, @@ -1538,6 +2229,7 @@ mod tests { // Can change authority when base key signs for related derived key. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeWithSeed( VoteAuthorizeWithSeedArgs { authorization_type, @@ -1555,6 +2247,7 @@ mod tests { fn perform_authorize_checked_with_seed_test( vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, authorization_type: VoteAuthorize, vote_pubkey: Pubkey, vote_account: AccountSharedData, @@ -1602,6 +2295,7 @@ mod tests { instruction_accounts[2].is_signer = false; process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type, @@ -1620,6 +2314,7 @@ mod tests { instruction_accounts[3].is_signer = false; process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type, @@ -1637,6 +2332,7 @@ mod tests { // Can't change authority if seed doesn't match. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type, @@ -1653,6 +2349,7 @@ mod tests { // Can't change authority if owner doesn't match. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type, @@ -1669,6 +2366,7 @@ mod tests { // Can change authority when base key signs for related derived key and new authority signs. process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type, @@ -1683,9 +2381,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_voter_base_key_can_authorize_new_voter(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_voter_base_key_can_authorize_new_voter( + vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, + ) { let VoteAccountTestFixtureWithAuthorities { vote_pubkey, voter_base_key, @@ -1695,9 +2395,20 @@ mod tests { .. } = create_test_account_with_authorized_from_seed(vote_state_v4_enabled); let new_voter_pubkey = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let authorize_type = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; perform_authorize_with_seed_test( vote_state_v4_enabled, - VoteAuthorize::Voter, + bls_pubkey_feature_enabled, + authorize_type, vote_pubkey, vote_account, voter_base_key, @@ -1707,9 +2418,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_withdrawer_base_key_can_authorize_new_voter(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_withdrawer_base_key_can_authorize_new_voter( + vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, + ) { let VoteAccountTestFixtureWithAuthorities { vote_pubkey, withdrawer_base_key, @@ -1719,9 +2432,20 @@ mod tests { .. } = create_test_account_with_authorized_from_seed(vote_state_v4_enabled); let new_voter_pubkey = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let authorize_type = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; perform_authorize_with_seed_test( vote_state_v4_enabled, - VoteAuthorize::Voter, + bls_pubkey_feature_enabled, + authorize_type, vote_pubkey, vote_account, withdrawer_base_key, @@ -1774,6 +2498,7 @@ mod tests { // Despite having Voter authority, you may not change the Withdrawer authority. process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::AuthorizeWithSeed( VoteAuthorizeWithSeedArgs { authorization_type: VoteAuthorize::Withdrawer, @@ -1789,9 +2514,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_withdrawer_base_key_can_authorize_new_withdrawer(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_withdrawer_base_key_can_authorize_new_withdrawer( + vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, + ) { let VoteAccountTestFixtureWithAuthorities { vote_pubkey, withdrawer_base_key, @@ -1803,6 +2530,7 @@ mod tests { let new_withdrawer_pubkey = Pubkey::new_unique(); perform_authorize_with_seed_test( vote_state_v4_enabled, + bls_pubkey_feature_enabled, VoteAuthorize::Withdrawer, vote_pubkey, vote_account, @@ -1813,9 +2541,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_voter_base_key_can_authorize_new_voter_checked(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_voter_base_key_can_authorize_new_voter_checked( + vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, + ) { let VoteAccountTestFixtureWithAuthorities { vote_pubkey, voter_base_key, @@ -1825,9 +2555,20 @@ mod tests { .. } = create_test_account_with_authorized_from_seed(vote_state_v4_enabled); let new_voter_pubkey = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let authorize_type = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; perform_authorize_checked_with_seed_test( vote_state_v4_enabled, - VoteAuthorize::Voter, + bls_pubkey_feature_enabled, + authorize_type, vote_pubkey, vote_account, voter_base_key, @@ -1837,9 +2578,11 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_withdrawer_base_key_can_authorize_new_voter_checked(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_withdrawer_base_key_can_authorize_new_voter_checked( + vote_state_v4_enabled: bool, + bls_pubkey_feature_enabled: bool, + ) { let VoteAccountTestFixtureWithAuthorities { vote_pubkey, withdrawer_base_key, @@ -1849,9 +2592,20 @@ mod tests { .. } = create_test_account_with_authorized_from_seed(vote_state_v4_enabled); let new_voter_pubkey = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let authorize_type = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; perform_authorize_checked_with_seed_test( vote_state_v4_enabled, - VoteAuthorize::Voter, + bls_pubkey_feature_enabled, + authorize_type, vote_pubkey, vote_account, withdrawer_base_key, @@ -1910,6 +2664,7 @@ mod tests { // Despite having Voter authority, you may not change the Withdrawer authority. process_instruction( vote_state_v4_enabled, + false, &serialize(&VoteInstruction::AuthorizeCheckedWithSeed( VoteAuthorizeCheckedWithSeedArgs { authorization_type: VoteAuthorize::Withdrawer, @@ -1938,6 +2693,7 @@ mod tests { let new_withdrawer_pubkey = Pubkey::new_unique(); perform_authorize_checked_with_seed_test( vote_state_v4_enabled, + false, VoteAuthorize::Withdrawer, vote_pubkey, vote_account, @@ -1953,6 +2709,7 @@ mod tests { fn test_spoofed_vote(vote_state_v4_enabled: bool) { process_instruction_as_one_arg( vote_state_v4_enabled, + false, &vote( &invalid_vote_state_pubkey(), &Pubkey::new_unique(), @@ -1962,6 +2719,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state( &invalid_vote_state_pubkey(), &Pubkey::default(), @@ -1971,6 +2729,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &compact_update_vote_state( &invalid_vote_state_pubkey(), &Pubkey::default(), @@ -1980,6 +2739,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &tower_sync( &invalid_vote_state_pubkey(), &Pubkey::default(), @@ -2025,6 +2785,7 @@ mod tests { // should fail, since VoteState1_14_11 isn't supported anymore process_instruction( vote_state_v4_enabled, + false, &instructions[1].data, transaction_accounts, instructions[1].accounts.clone(), @@ -2067,6 +2828,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instructions[1].data, transaction_accounts, instructions[1].accounts.clone(), @@ -2089,11 +2851,13 @@ mod tests { // process_instruction_as_one_arg passes a default (empty) account process_instruction_as_one_arg( vote_state_v4_enabled, + false, &instructions[1], Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &vote( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2103,6 +2867,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &vote_switch( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2113,6 +2878,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &authorize( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2123,6 +2889,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state( &Pubkey::default(), &Pubkey::default(), @@ -2133,6 +2900,7 @@ mod tests { process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state_switch( &Pubkey::default(), &Pubkey::default(), @@ -2143,6 +2911,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &compact_update_vote_state( &Pubkey::default(), &Pubkey::default(), @@ -2152,6 +2921,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &compact_update_vote_state_switch( &Pubkey::default(), &Pubkey::default(), @@ -2162,11 +2932,13 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &tower_sync(&Pubkey::default(), &Pubkey::default(), TowerSync::default()), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &tower_sync_switch( &Pubkey::default(), &Pubkey::default(), @@ -2178,6 +2950,7 @@ mod tests { process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_validator_identity( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2187,12 +2960,14 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_commission(&Pubkey::new_unique(), &Pubkey::new_unique(), 0), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &withdraw( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2203,23 +2978,37 @@ mod tests { ); } - #[test_case(false ; "VoteStateV3")] - #[test_case(true ; "VoteStateV4")] - fn test_vote_authorize_checked(vote_state_v4_enabled: bool) { + #[test_matrix([false, true], [false, true])] + fn test_vote_authorize_checked(vote_state_v4_enabled: bool, bls_pubkey_feature_enabled: bool) { let vote_pubkey = Pubkey::new_unique(); let authorized_pubkey = Pubkey::new_unique(); let new_authorized_pubkey = Pubkey::new_unique(); // Test with vanilla authorize accounts - let mut instruction = authorize_checked( - &vote_pubkey, - &authorized_pubkey, - &new_authorized_pubkey, - VoteAuthorize::Voter, - ); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let mut instruction = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + authorize_checked( + &vote_pubkey, + &authorized_pubkey, + &new_authorized_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), + ) + } else { + authorize_checked( + &vote_pubkey, + &authorized_pubkey, + &new_authorized_pubkey, + VoteAuthorize::Voter, + ) + }; instruction.accounts = instruction.accounts[0..2].to_vec(); process_instruction_as_one_arg( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &instruction, Err(InstructionError::MissingAccount), ); @@ -2233,20 +3022,34 @@ mod tests { instruction.accounts = instruction.accounts[0..2].to_vec(); process_instruction_as_one_arg( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &instruction, Err(InstructionError::MissingAccount), ); // Test with non-signing new_authorized_pubkey - let mut instruction = authorize_checked( - &vote_pubkey, - &authorized_pubkey, - &new_authorized_pubkey, - VoteAuthorize::Voter, - ); + let mut instruction = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + authorize_checked( + &vote_pubkey, + &authorized_pubkey, + &new_authorized_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), + ) + } else { + authorize_checked( + &vote_pubkey, + &authorized_pubkey, + &new_authorized_pubkey, + VoteAuthorize::Voter, + ) + }; instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false); process_instruction_as_one_arg( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -2260,6 +3063,7 @@ mod tests { instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false); process_instruction_as_one_arg( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &instruction, Err(InstructionError::MissingRequiredSignature), ); @@ -2303,15 +3107,25 @@ mod tests { is_writable: false, }, ]; + let authorize_type = if vote_state_v4_enabled && bls_pubkey_feature_enabled { + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }) + } else { + VoteAuthorize::Voter + }; process_instruction( vote_state_v4_enabled, - &serialize(&VoteInstruction::AuthorizeChecked(VoteAuthorize::Voter)).unwrap(), + bls_pubkey_feature_enabled, + &serialize(&VoteInstruction::AuthorizeChecked(authorize_type)).unwrap(), transaction_accounts.clone(), instruction_accounts.clone(), Ok(()), ); process_instruction( vote_state_v4_enabled, + bls_pubkey_feature_enabled, &serialize(&VoteInstruction::AuthorizeChecked( VoteAuthorize::Withdrawer, )) @@ -2372,6 +3186,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2423,6 +3238,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2479,6 +3295,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2521,6 +3338,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2564,6 +3382,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2601,6 +3420,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2650,6 +3470,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, diff --git a/programs/vote/src/vote_state/handler.rs b/programs/vote/src/vote_state/handler.rs index 18048b23d5655e..7eabd9ddff3510 100644 --- a/programs/vote/src/vote_state/handler.rs +++ b/programs/vote/src/vote_state/handler.rs @@ -19,8 +19,8 @@ use { authorized_voters::AuthorizedVoters, error::VoteError, state::{ - BlockTimestamp, LandedVote, Lockout, VoteInit, VoteState1_14_11, VoteStateV3, - VoteStateV4, VoteStateVersions, BLS_PUBLIC_KEY_COMPRESSED_SIZE, + BlockTimestamp, LandedVote, Lockout, VoteInit, VoteInitV2, VoteState1_14_11, + VoteStateV3, VoteStateV4, VoteStateVersions, BLS_PUBLIC_KEY_COMPRESSED_SIZE, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, VOTE_CREDITS_GRACE_SLOTS, VOTE_CREDITS_MAXIMUM_PER_SLOT, }, @@ -41,6 +41,7 @@ pub trait VoteStateHandle { authorized_pubkey: &Pubkey, current_epoch: Epoch, target_epoch: Epoch, + bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, verify: F, ) -> Result<(), InstructionError> where @@ -93,6 +94,8 @@ pub trait VoteStateHandle { vote_account: &mut BorrowedInstructionAccount, ) -> Result<(), InstructionError>; + fn has_bls_pubkey(&self) -> bool; + fn credits_for_vote_at_index(&self, index: usize) -> u64 { let latency = self .votes() @@ -265,11 +268,19 @@ impl VoteStateHandle for VoteStateV3 { authorized_pubkey: &Pubkey, current_epoch: Epoch, target_epoch: Epoch, + bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, verify: F, ) -> Result<(), InstructionError> where F: Fn(Pubkey) -> Result<(), InstructionError>, { + if bls_pubkey.is_some() { + // We should not be able to reach here because we only call this function + // when both Vote State V4 and BLS features are enabled. + // See `is_bls_pubkey_feature_enabled` in vote_processor.rs. + return Err(InstructionError::InvalidAccountData); + } + let epoch_authorized_voter = self.get_and_update_authorized_voter(current_epoch)?; verify(epoch_authorized_voter)?; @@ -432,6 +443,10 @@ impl VoteStateHandle for VoteStateV3 { // Vote account is large enough to store the newest version of vote state vote_account.set_state(&VoteStateVersions::V3(Box::new(self))) } + + fn has_bls_pubkey(&self) -> bool { + false + } } impl VoteStateHandle for VoteStateV4 { @@ -452,6 +467,7 @@ impl VoteStateHandle for VoteStateV4 { authorized_pubkey: &Pubkey, current_epoch: Epoch, target_epoch: Epoch, + bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, verify: F, ) -> Result<(), InstructionError> where @@ -474,6 +490,10 @@ impl VoteStateHandle for VoteStateV4 { self.authorized_voters .insert(target_epoch, *authorized_pubkey); + if bls_pubkey.is_some() { + self.bls_pubkey_compressed = bls_pubkey.copied(); + } + Ok(()) } @@ -592,47 +612,9 @@ impl VoteStateHandle for VoteStateV4 { // Vote account is large enough to store the newest version of vote state vote_account.set_state(&VoteStateVersions::V4(Box::new(self))) } -} -/// Default block revenue commission rate in basis points (100%) per SIMD-0185. -const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000; - -/// Create a new VoteStateV4 from `VoteInit` with proper SIMD-0185 defaults. -/// Note this is a temporary substitute for `VoteStateV4::new`. -#[allow(clippy::arithmetic_side_effects)] -pub(crate) fn create_new_vote_state_v4( - vote_pubkey: &Pubkey, - vote_init: &VoteInit, - clock: &Clock, -) -> VoteStateV4 { - VoteStateV4 { - node_pubkey: vote_init.node_pubkey, - authorized_voters: AuthorizedVoters::new(clock.epoch, vote_init.authorized_voter), - authorized_withdrawer: vote_init.authorized_withdrawer, - inflation_rewards_commission_bps: (vote_init.commission as u16) * 100, // u16::MAX > u8::MAX * 100 - // Per SIMD-0185, set default collectors and commission - inflation_rewards_collector: *vote_pubkey, - block_revenue_collector: vote_init.node_pubkey, - block_revenue_commission_bps: DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, - ..VoteStateV4::default() - } -} - -/// (Alpenglow) Create a test-only `VoteStateV4` with the provided values. -pub(crate) fn create_new_vote_state_v4_for_tests( - node_pubkey: &Pubkey, - authorized_voter: &Pubkey, - authorized_withdrawer: &Pubkey, - bls_pubkey_compressed: Option<[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, - inflation_rewards_commission_bps: u16, -) -> VoteStateV4 { - VoteStateV4 { - node_pubkey: *node_pubkey, - authorized_voters: AuthorizedVoters::new(0, *authorized_voter), - authorized_withdrawer: *authorized_withdrawer, - bls_pubkey_compressed, - inflation_rewards_commission_bps, - ..VoteStateV4::default() + fn has_bls_pubkey(&self) -> bool { + self.bls_pubkey_compressed.is_some() } } @@ -689,18 +671,27 @@ impl VoteStateHandle for VoteStateHandler { authorized_pubkey: &Pubkey, current_epoch: Epoch, target_epoch: Epoch, + bls_pubkey: Option<&[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]>, verify: F, ) -> Result<(), InstructionError> where F: Fn(Pubkey) -> Result<(), InstructionError>, { match &mut self.target_state { - TargetVoteState::V3(v3) => { - v3.set_new_authorized_voter(authorized_pubkey, current_epoch, target_epoch, verify) - } - TargetVoteState::V4(v4) => { - v4.set_new_authorized_voter(authorized_pubkey, current_epoch, target_epoch, verify) - } + TargetVoteState::V3(v3) => v3.set_new_authorized_voter( + authorized_pubkey, + current_epoch, + target_epoch, + bls_pubkey, + verify, + ), + TargetVoteState::V4(v4) => v4.set_new_authorized_voter( + authorized_pubkey, + current_epoch, + target_epoch, + bls_pubkey, + verify, + ), } } @@ -849,6 +840,13 @@ impl VoteStateHandle for VoteStateHandler { TargetVoteState::V4(v4) => v4.set_vote_account_state(vote_account), } } + + fn has_bls_pubkey(&self) -> bool { + match &self.target_state { + TargetVoteState::V3(v3) => v3.has_bls_pubkey(), + TargetVoteState::V4(v4) => v4.has_bls_pubkey(), + } + } } impl VoteStateHandler { @@ -863,7 +861,28 @@ impl VoteStateHandler { VoteStateV3::new(vote_init, clock).set_vote_account_state(vote_account) } VoteStateTargetVersion::V4 => { - let vote_state = create_new_vote_state_v4(vote_account.get_key(), vote_init, clock); + let vote_state = + VoteStateV4::new_with_defaults(vote_account.get_key(), vote_init, clock); + vote_state.set_vote_account_state(vote_account) + } + } + } + + pub fn init_vote_account_state_v2( + vote_account: &mut BorrowedInstructionAccount, + vote_init: &VoteInitV2, + clock: &Clock, + target_version: VoteStateTargetVersion, + ) -> Result<(), InstructionError> { + match target_version { + VoteStateTargetVersion::V3 => { + // We should not be able to reach here because we only call this function + // when both Vote State V4 and BLS features are enabled. + // See `is_bls_pubkey_feature_enabled` in vote_processor.rs. + Err(InstructionError::InvalidInstructionData) + } + VoteStateTargetVersion::V4 => { + let vote_state = VoteStateV4::new(vote_init, clock); vote_state.set_vote_account_state(vote_account) } } @@ -1025,12 +1044,18 @@ mod tests { }, solana_vote_interface::{ authorized_voters::AuthorizedVoters, - state::{BlockTimestamp, VoteInit, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY}, + state::{ + BlockTimestamp, VoteInit, BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, + MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, + }, }, std::collections::VecDeque, test_case::test_case, }; + /// Default block revenue commission rate in basis points (100%) per SIMD-0185. + const DEFAULT_BLOCK_REVENUE_COMMISSION_BPS: u16 = 10_000; + fn mock_transaction_context( vote_pubkey: Pubkey, vote_account: AccountSharedData, @@ -1093,7 +1118,7 @@ mod tests { let new_voter = Pubkey::new_unique(); // Set a new authorized voter vote_state - .set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(())) + .set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())) .unwrap(); if let Some(prior_voters_last) = prior_voters_last_callback { @@ -1105,19 +1130,19 @@ mod tests { // Trying to set authorized voter for same epoch again should fail assert_eq!( - vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, |_| Ok(())), + vote_state.set_new_authorized_voter(&new_voter, 0, epoch_offset, None, |_| Ok(())), Err(VoteError::TooSoonToReauthorize.into()) ); // Setting the same authorized voter again should succeed vote_state - .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, |_| Ok(())) + .set_new_authorized_voter(&new_voter, 2, 2 + epoch_offset, None, |_| Ok(())) .unwrap(); // Set a third and fourth authorized voter let new_voter2 = Pubkey::new_unique(); vote_state - .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, |_| Ok(())) + .set_new_authorized_voter(&new_voter2, 3, 3 + epoch_offset, None, |_| Ok(())) .unwrap(); if let Some(prior_voters_last) = prior_voters_last_callback { assert_eq!( @@ -1128,7 +1153,7 @@ mod tests { let new_voter3 = Pubkey::new_unique(); vote_state - .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, |_| Ok(())) + .set_new_authorized_voter(&new_voter3, 6, 6 + epoch_offset, None, |_| Ok(())) .unwrap(); if let Some(prior_voters_last) = prior_voters_last_callback { assert_eq!( @@ -1139,7 +1164,7 @@ mod tests { // Check can set back to original voter vote_state - .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, |_| Ok(())) + .set_new_authorized_voter(&original_voter, 9, 9 + epoch_offset, None, |_| Ok(())) .unwrap(); // Run with these voters for a while, check the ranges of authorized @@ -1202,7 +1227,7 @@ mod tests { ); // Now try with v4. No `prior_voters` to check. - let mut vote_state = create_new_vote_state_v4(&vote_pubkey, &vote_init, &clock); + let mut vote_state = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock); set_new_authorized_voter_and_assert(&mut vote_state, original_voter, epoch_offset, None); } @@ -1216,7 +1241,7 @@ mod tests { // explicitly set before let new_voter = Pubkey::new_unique(); assert_eq!( - vote_state.set_new_authorized_voter(&new_voter, 1, 1, |_| Ok(())), + vote_state.set_new_authorized_voter(&new_voter, 1, 1, None, |_| Ok(())), Err(VoteError::TooSoonToReauthorize.into()) ); assert_eq!( @@ -1225,14 +1250,14 @@ mod tests { ); // Set a new authorized voter for a future epoch assert_eq!( - vote_state.set_new_authorized_voter(&new_voter, 1, 2, |_| Ok(())), + vote_state.set_new_authorized_voter(&new_voter, 1, 2, None, |_| Ok(())), Ok(()) ); // Test that it's not possible to set a new authorized // voter within the same epoch, even if none has been // explicitly set before assert_eq!( - vote_state.set_new_authorized_voter(original_voter, 3, 3, |_| Ok(())), + vote_state.set_new_authorized_voter(original_voter, 3, 3, None, |_| Ok(())), Err(VoteError::TooSoonToReauthorize.into()) ); assert_eq!( @@ -1259,7 +1284,7 @@ mod tests { assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter); // Now v4. - let mut vote_state = create_new_vote_state_v4(&vote_pubkey, &vote_init, &clock); + let mut vote_state = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock); assert_authorized_voter_is_locked_within_epoch(&mut vote_state, &original_voter); } @@ -1309,7 +1334,7 @@ mod tests { // Set an authorized voter change at slot 7 let new_authorized_voter = Pubkey::new_unique(); vote_state - .set_new_authorized_voter(&new_authorized_voter, 5, 7, |_| Ok(())) + .set_new_authorized_voter(&new_authorized_voter, 5, 7, None, |_| Ok(())) .unwrap(); // Try to get the authorized voter for epoch 6, unchanged @@ -1335,7 +1360,7 @@ mod tests { fn test_get_and_update_authorized_voter_v4() { let vote_pubkey = Pubkey::new_unique(); let original_voter = Pubkey::new_unique(); - let mut vote_state = create_new_vote_state_v4( + let mut vote_state = VoteStateV4::new_with_defaults( &vote_pubkey, &VoteInit { node_pubkey: original_voter, @@ -1402,7 +1427,7 @@ mod tests { // Set an authorized voter change at epoch 9. let new_authorized_voter = Pubkey::new_unique(); vote_state - .set_new_authorized_voter(&new_authorized_voter, 7, 9, |_| Ok(())) + .set_new_authorized_voter(&new_authorized_voter, 7, 9, None, |_| Ok(())) .unwrap(); // Try to get the authorized voter for epoch 8, unchanged @@ -1463,6 +1488,7 @@ mod tests { &Pubkey::new_unique(), i, i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET, + None, |_| Ok(()), ) .unwrap(); @@ -2194,4 +2220,67 @@ mod tests { assert_eq!(result, expected_result); } + + #[test] + fn test_bls_pubkey_handling() { + let bls_pubkey_compressed = [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]; + let vote_state_v4 = VoteStateV4::new( + &VoteInitV2 { + node_pubkey: Pubkey::new_unique(), + authorized_voter: Pubkey::new_unique(), + authorized_withdrawer: Pubkey::new_unique(), + block_revenue_commission_bps: 500, + block_revenue_collector: Pubkey::new_unique(), + inflation_rewards_commission_bps: 1000, + inflation_rewards_collector: Pubkey::new_unique(), + authorized_voter_bls_pubkey: bls_pubkey_compressed, + authorized_voter_bls_proof_of_possession: [2u8; + BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + }, + &Clock::default(), + ); + assert_eq!( + vote_state_v4.bls_pubkey_compressed, + Some(bls_pubkey_compressed) + ); + assert!(vote_state_v4.has_bls_pubkey()); + } + + #[test] + fn test_get_and_update_authorized_voter_v4_with_bls() { + let vote_pubkey = Pubkey::new_unique(); + let original_voter = Pubkey::new_unique(); + let mut vote_state = VoteStateV4::new_with_defaults( + &vote_pubkey, + &VoteInit { + node_pubkey: original_voter, + authorized_voter: original_voter, + authorized_withdrawer: original_voter, + commission: 0, + }, + &Clock::default(), + ); + + // It has some initial authorized voter but no BLS pubkey. + assert_eq!(vote_state.authorized_voters().len(), 1); + assert_eq!( + *vote_state.authorized_voters().first().unwrap().1, + original_voter + ); + assert!(!vote_state.has_bls_pubkey()); + + // Update authorized voter with BLS pubkey. + let new_voter = Pubkey::new_unique(); + let bls_pubkey_compressed = [3u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]; + vote_state + .set_new_authorized_voter(&new_voter, 0, 1, Some(&bls_pubkey_compressed), |_| Ok(())) + .unwrap(); + assert_eq!(vote_state.authorized_voters().len(), 2); + assert_eq!(*vote_state.authorized_voters().last().unwrap().1, new_voter); + assert_eq!( + vote_state.bls_pubkey_compressed, + Some(bls_pubkey_compressed) + ); + assert!(vote_state.has_bls_pubkey()); + } } diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 48d74685f9fd88..a555eeeccda128 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -11,6 +11,11 @@ use { handler::{VoteStateHandle, VoteStateHandler, VoteStateTargetVersion}, log::*, solana_account::{AccountSharedData, WritableAccount}, + solana_bls_signatures::{ + keypair::Keypair as BLSKeypair, ProofOfPossession as BLSProofOfPossession, + ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, Pubkey as BLSPubkey, + PubkeyCompressed as BLSPubkeyCompressed, VerifiableProofOfPossession, + }, solana_clock::{Clock, Epoch, Slot}, solana_epoch_schedule::EpochSchedule, solana_hash::Hash, @@ -721,6 +726,7 @@ pub fn authorize( vote_authorize: VoteAuthorize, signers: &HashSet, clock: &Clock, + is_bls_pubkey_feature_enabled: bool, ) -> Result<(), InstructionError> { let mut vote_state = get_vote_state_handler_checked( vote_account, @@ -729,6 +735,9 @@ pub fn authorize( match vote_authorize { VoteAuthorize::Voter => { + if is_bls_pubkey_feature_enabled && vote_state.has_bls_pubkey() { + return Err(InstructionError::InvalidInstructionData); + } let authorized_withdrawer_signer = verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok(); @@ -739,6 +748,7 @@ pub fn authorize( .leader_schedule_epoch .checked_add(1) .ok_or(InstructionError::InvalidAccountData)?, + None, |epoch_authorized_voter| { // current authorized withdrawer or authorized voter must say "yay" if authorized_withdrawer_signer { @@ -754,9 +764,36 @@ pub fn authorize( verify_authorized_signer(vote_state.authorized_withdrawer(), signers)?; vote_state.set_authorized_withdrawer(*authorized); } - // VoterWithBLS not yet implemented. - VoteAuthorize::VoterWithBLS(_) => { - return Err(InstructionError::InvalidInstructionData); + VoteAuthorize::VoterWithBLS(args) => { + if !is_bls_pubkey_feature_enabled { + return Err(InstructionError::InvalidInstructionData); + } + let authorized_withdrawer_signer = + verify_authorized_signer(vote_state.authorized_withdrawer(), signers).is_ok(); + + verify_bls_proof_of_possession( + vote_account.get_key(), + &args.bls_pubkey, + &args.bls_proof_of_possession, + )?; + + vote_state.set_new_authorized_voter( + authorized, + clock.epoch, + clock + .leader_schedule_epoch + .checked_add(1) + .ok_or(InstructionError::InvalidAccountData)?, + Some(&args.bls_pubkey), + |epoch_authorized_voter| { + // current authorized withdrawer or authorized voter must say "yay" + if authorized_withdrawer_signer { + Ok(()) + } else { + verify_authorized_signer(&epoch_authorized_voter, signers) + } + }, + )?; } } @@ -850,6 +887,61 @@ fn verify_authorized_signer( } } +// The message size is fixed: +// "ALPENGLOW" (9) + Vote Pubkey (32) + BLS Pubkey (48) = 89 bytes +const POP_MESSAGE_SIZE: usize = 9 + size_of::() + BLS_PUBLIC_KEY_COMPRESSED_SIZE; + +pub(crate) fn generate_pop_message( + vote_account_pubkey: &Pubkey, + bls_pubkey_bytes: &[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], +) -> [u8; POP_MESSAGE_SIZE] { + const LABEL_LEN: usize = 9; + const PUBKEY_LEN: usize = size_of::(); + const BLS_LEN: usize = BLS_PUBLIC_KEY_COMPRESSED_SIZE; + + const LABEL_START: usize = 0; + const LABEL_END: usize = LABEL_START + LABEL_LEN; + + const PUBKEY_START: usize = LABEL_END; + const PUBKEY_END: usize = PUBKEY_START + PUBKEY_LEN; + + const BLS_START: usize = PUBKEY_END; + const BLS_END: usize = BLS_START + BLS_LEN; + + // Make sure POP_MESSAGE_SIZE matches the layout at compile time + const _: () = assert!(BLS_END == POP_MESSAGE_SIZE); + + let mut message = [0u8; POP_MESSAGE_SIZE]; + + message[LABEL_START..LABEL_END].copy_from_slice(b"ALPENGLOW"); + message[PUBKEY_START..PUBKEY_END].copy_from_slice(vote_account_pubkey.as_ref()); + message[BLS_START..BLS_END].copy_from_slice(bls_pubkey_bytes); + + message +} + +// TODO(sam): use custom payload for PoP once solana-bls-signatures v2.0.0 is published. +pub fn verify_bls_proof_of_possession( + vote_account_pubkey: &Pubkey, + bls_pubkey_compressed_bytes: &[u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + bls_proof_of_possession_compressed_bytes: &[u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], +) -> Result<(), InstructionError> { + let bls_pubkey_compressed = BLSPubkeyCompressed(*bls_pubkey_compressed_bytes); + let bls_pubkey = BLSPubkey::try_from(bls_pubkey_compressed) + .map_err(|_| InstructionError::InvalidArgument)?; + let bls_proof_of_possession_compressed = + BLSProofOfPossessionCompressed(*bls_proof_of_possession_compressed_bytes); + let bls_proof_of_possession = + BLSProofOfPossession::try_from(bls_proof_of_possession_compressed) + .map_err(|_| InstructionError::InvalidArgument)?; + let message = generate_pop_message(vote_account_pubkey, bls_pubkey_compressed_bytes); + if Ok(true) == bls_proof_of_possession.verify(&bls_pubkey, Some(&message)) { + Ok(()) + } else { + Err(InstructionError::InvalidArgument) + } +} + /// Withdraw funds from the vote account pub fn withdraw( instruction_context: &InstructionContext, @@ -908,6 +1000,37 @@ pub fn withdraw( Ok(()) } +/// Initialize the vote_state for a vote account using VoteInitV2 +/// Assumes that the account is being init as part of a account creation or balance transfer and +/// that the transaction must be signed by the staker's keys +/// It also verifies the BLS proof of possession for the authorized voter BLS pubkey +pub fn initialize_account_v2( + vote_account: &mut BorrowedInstructionAccount, + target_version: VoteStateTargetVersion, + vote_init: &VoteInitV2, + signers: &HashSet, + clock: &Clock, +) -> Result<(), InstructionError> { + VoteStateHandler::check_vote_account_length(vote_account, target_version)?; + let versioned = vote_account.get_state::()?; + + if !versioned.is_uninitialized() { + return Err(InstructionError::AccountAlreadyInitialized); + } + + // node must agree to accept this vote account + verify_authorized_signer(&vote_init.node_pubkey, signers)?; + + // verify the BLS pubkey proof of possession + verify_bls_proof_of_possession( + vote_account.get_key(), + &vote_init.authorized_voter_bls_pubkey, + &vote_init.authorized_voter_bls_proof_of_possession, + )?; + + VoteStateHandler::init_vote_account_state_v2(vote_account, vote_init, clock, target_version) +} + /// Initialize the vote_state for a vote account /// Assumes that the account is being init as part of a account creation or balance transfer and /// that the transaction must be signed by the staker's keys @@ -1105,12 +1228,17 @@ pub fn create_v4_account_with_authorized( ) -> AccountSharedData { let mut vote_account = AccountSharedData::new(lamports, VoteStateV4::size_of(), &id()); - let vote_state = handler::create_new_vote_state_v4_for_tests( - node_pubkey, - authorized_voter, - authorized_withdrawer, - bls_pubkey_compressed, - inflation_rewards_commission_bps, + let vote_state = VoteStateV4::new( + &VoteInitV2 { + node_pubkey: *node_pubkey, + authorized_voter: *authorized_voter, + authorized_withdrawer: *authorized_withdrawer, + authorized_voter_bls_pubkey: bls_pubkey_compressed + .unwrap_or([0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]), + inflation_rewards_commission_bps, + ..Default::default() + }, + &Clock::default(), ); VoteStateV4::serialize( @@ -1122,6 +1250,32 @@ pub fn create_v4_account_with_authorized( vote_account } +pub fn create_bls_pubkey_and_proof_of_possession( + vote_account_pubkey: &Pubkey, +) -> ( + [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], +) { + let bls_keypair = BLSKeypair::new(); + create_bls_proof_of_possession(vote_account_pubkey, &bls_keypair) +} + +pub fn create_bls_proof_of_possession( + vote_account_pubkey: &Pubkey, + bls_keypair: &BLSKeypair, +) -> ( + [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], + [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], +) { + let bls_pubkey_compressed: BLSPubkeyCompressed = bls_keypair.public.try_into().unwrap(); + let message = generate_pop_message(vote_account_pubkey, &bls_pubkey_compressed.0); + let proof_of_possession = bls_keypair.proof_of_possession(Some(&message)); + let proof_of_possession: BLSProofOfPossession = proof_of_possession.into(); + let proof_of_possession_compressed: BLSProofOfPossessionCompressed = + proof_of_possession.try_into().unwrap(); + (bls_pubkey_compressed.0, proof_of_possession_compressed.0) +} + #[allow(clippy::arithmetic_side_effects)] #[cfg(test)] mod tests { @@ -1157,9 +1311,11 @@ mod tests { VoteStateTargetVersion::V3 => { VoteStateHandler::new_v3(VoteStateV3::new(&vote_init, &clock)) } - VoteStateTargetVersion::V4 => VoteStateHandler::new_v4( - handler::create_new_vote_state_v4(vote_pubkey, &vote_init, &clock), - ), + VoteStateTargetVersion::V4 => VoteStateHandler::new_v4(VoteStateV4::new_with_defaults( + vote_pubkey, + &vote_init, + &clock, + )), } } @@ -1172,17 +1328,35 @@ mod tests { // Simulate prior epochs completed with credits and each setting a new authorized voter vote_state.increment_credits(0, 100); assert_eq!( - vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 0, 1, |_pubkey| Ok(())), + vote_state.set_new_authorized_voter( + &solana_pubkey::new_rand(), + 0, + 1, + None, + |_pubkey| Ok(()) + ), Ok(()) ); vote_state.increment_credits(1, 200); assert_eq!( - vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 1, 2, |_pubkey| Ok(())), + vote_state.set_new_authorized_voter( + &solana_pubkey::new_rand(), + 1, + 2, + None, + |_pubkey| Ok(()) + ), Ok(()) ); vote_state.increment_credits(2, 300); assert_eq!( - vote_state.set_new_authorized_voter(&solana_pubkey::new_rand(), 2, 3, |_pubkey| Ok(())), + vote_state.set_new_authorized_voter( + &solana_pubkey::new_rand(), + 2, + 3, + None, + |_pubkey| Ok(()) + ), Ok(()) ); @@ -3822,4 +3996,97 @@ mod tests { assert_eq!(vote_state.node_pubkey, new_node_pubkey); assert_eq!(vote_state.block_revenue_collector, new_node_pubkey); } + + #[test] + fn test_get_and_update_authorized_voter_v4_with_bls() { + let vote_account_pubkey = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey); + let node_pubkey = Pubkey::new_unique(); + let authorized_voter = Pubkey::new_unique(); + let authorized_withdrawer = Pubkey::new_unique(); + let inflation_rewards_commission_bps = 10000; + let rent = Rent::default(); + let lamports = rent.minimum_balance(VoteStateV4::size_of()); + // Create a VoteStateV4 account without BLS pubkey + let vote_account = create_v4_account_with_authorized( + &node_pubkey, + &authorized_voter, + &authorized_withdrawer, + None, + inflation_rewards_commission_bps, + lamports, + ); + assert_eq!(vote_account.lamports(), lamports); + assert_eq!(vote_account.owner(), &id()); + assert_eq!(vote_account.data().len(), VoteStateV4::size_of()); + + let processor_account = AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()); + let mut transaction_context = TransactionContext::new( + vec![ + (id(), processor_account), + (vote_account_pubkey, vote_account), + ], + rent, + 0, + 0, + ); + transaction_context + .configure_next_instruction_for_tests( + 0, + vec![InstructionAccount::new(1, false, true)], + vec![], + ) + .unwrap(); + let instruction_context = transaction_context.get_next_instruction_context().unwrap(); + let mut borrowed_account = instruction_context + .try_borrow_instruction_account(0) + .unwrap(); + + let new_node_pubkey = solana_pubkey::new_rand(); + let signers: HashSet = vec![authorized_withdrawer, new_node_pubkey] + .into_iter() + .collect(); + let clock = Clock::default(); + assert!(authorize( + &mut borrowed_account, + VoteStateTargetVersion::V4, + &new_node_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession + }), + &signers, + &clock, + true, + ) + .is_ok()); + let vote_state = + VoteStateV4::deserialize(borrowed_account.get_data(), &new_node_pubkey).unwrap(); + assert_eq!(vote_state.bls_pubkey_compressed, Some(bls_pubkey)); + assert!(vote_state.has_bls_pubkey()); + + // Test replay attack, can't use someone else's BLS pubkey and PoP + let (others_bls_pubkey, others_bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&Pubkey::new_unique()); + let new_node_pubkey = solana_pubkey::new_rand(); + let signers: HashSet = vec![authorized_withdrawer, new_node_pubkey] + .into_iter() + .collect(); + assert_eq!( + authorize( + &mut borrowed_account, + VoteStateTargetVersion::V4, + &new_node_pubkey, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey: others_bls_pubkey, + bls_proof_of_possession: others_bls_proof_of_possession + }), + &signers, + &clock, + true, + ), + Err(InstructionError::InvalidArgument), + ); + } } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 4f05b430ce9241..c597e6e5e758c9 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -128,6 +128,7 @@ solana-sha256-hasher = { workspace = true } solana-stake-interface = { workspace = true } solana-svm-log-collector = { workspace = true } solana-vote-interface = { workspace = true } +solana-vote-program = { workspace = true } spl-pod = { workspace = true } symlink = { workspace = true } test-case = { workspace = true } diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index 0e483f6e0a5f87..0d6f3bb502ac2c 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -650,8 +650,9 @@ mod tests { solana_vote_interface::{ instruction::{self as vote_instruction, CreateVoteAccountConfig}, program as vote_program, - state::{Vote, VoteInit, VoteStateV4}, + state::{Vote, VoteInitV2, VoteStateV4}, }, + solana_vote_program::vote_state::create_bls_pubkey_and_proof_of_possession, std::{ sync::{ atomic::{AtomicBool, AtomicU64}, @@ -877,6 +878,8 @@ mod tests { let voter = Keypair::new(); let from = Keypair::new(); let vote_account = Keypair::new(); + let (bls_pubkey, bls_proof_of_possesssion) = + create_bls_pubkey_and_proof_of_possession(&vote_account.pubkey()); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); let bank_forks = BankForks::new_rw_arc(bank); @@ -929,14 +932,16 @@ mod tests { 0, &system_program::id(), )]; - ixs.append(&mut vote_instruction::create_account_with_config( + ixs.append(&mut vote_instruction::create_account_with_config_v2( &from.pubkey(), &vote_account.pubkey(), - &VoteInit { + &VoteInitV2 { node_pubkey: validator.pubkey(), authorized_voter: voter.pubkey(), authorized_withdrawer: Pubkey::new_unique(), - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possesssion, + ..VoteInitV2::default() }, vote_balance, CreateVoteAccountConfig { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 006b88dcd8c152..7cf2edf359c38b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5012,6 +5012,45 @@ impl Bank { self.epoch_schedule().get_leader_schedule_epoch(slot) } + /// Returns whether the specified epoch should use the new vote account + /// keyed leader schedule + pub fn should_use_vote_keyed_leader_schedule(&self, epoch: Epoch) -> Option { + let effective_epoch = self + .feature_set + .activated_slot(&agave_feature_set::enable_vote_address_leader_schedule::id()) + .map(|activation_slot| { + // If the feature was activated at genesis, then the new leader + // schedule should be effective immediately in the first epoch + if activation_slot == 0 { + return 0; + } + + // Calculate the epoch that the feature became activated in + let activation_epoch = self.epoch_schedule.get_epoch(activation_slot); + + // The effective epoch is the epoch immediately after the + // activation epoch + activation_epoch.wrapping_add(1) + }); + + // Starting from the effective epoch, always use the new leader schedule + if let Some(effective_epoch) = effective_epoch { + return Some(epoch >= effective_epoch); + } + + // Calculate the max epoch we can cache a leader schedule for + let max_cached_leader_schedule = self.get_leader_schedule_epoch(self.slot()); + if epoch <= max_cached_leader_schedule { + // The feature cannot be effective by the specified epoch + Some(false) + } else { + // Cannot determine if an epoch should use the new leader schedule if the + // the epoch is too far in the future because we won't know if the feature + // will have been activated by then or not. + None + } + } + /// a bank-level cache of vote accounts and stake delegation info fn update_stakes_cache( &self, diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 62caf8442067dd..3ed5e55fed82a2 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -63,7 +63,7 @@ use { solana_genesis_config::GenesisConfig, solana_hash::Hash, solana_instruction::{error::InstructionError, AccountMeta, Instruction}, - solana_keypair::{keypair_from_seed, Keypair}, + solana_keypair::Keypair, solana_loader_v3_interface::{ instruction::UpgradeableLoaderInstruction, state::UpgradeableLoaderState, }, @@ -113,12 +113,13 @@ use { sanitized::SanitizedTransaction, Transaction, TransactionVerificationMode, }, solana_transaction_error::{TransactionError, TransactionResult as Result}, - solana_vote_interface::state::TowerSync, + solana_vote_interface::state::{TowerSync, VoterWithBLSArgs}, solana_vote_program::{ vote_instruction, vote_state::{ - self, create_v4_account_with_authorized, BlockTimestamp, VoteAuthorize, VoteInit, - VoteStateV4, VoteStateVersions, MAX_LOCKOUT_HISTORY, + self, create_bls_pubkey_and_proof_of_possession, create_v4_account_with_authorized, + BlockTimestamp, VoteAuthorize, VoteInitV2, VoteStateV4, VoteStateVersions, + MAX_LOCKOUT_HISTORY, }, }, spl_generic_token::token, @@ -3232,14 +3233,19 @@ fn test_bank_vote_accounts() { // to have a vote account let vote_keypair = Keypair::new(); - let instructions = vote_instruction::create_account_with_config( + let vote_pubkey = vote_keypair.pubkey(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let instructions = vote_instruction::create_account_with_config_v2( &mint_keypair.pubkey(), - &vote_keypair.pubkey(), - &VoteInit { + &vote_pubkey, + &VoteInitV2 { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_keypair.pubkey(), - authorized_withdrawer: vote_keypair.pubkey(), - commission: 0, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, 10, vote_instruction::CreateVoteAccountConfig { @@ -3314,14 +3320,19 @@ fn test_bank_cloned_stake_delegations() { }; let vote_keypair = Keypair::new(); - let mut instructions = vote_instruction::create_account_with_config( + let vote_pubkey = vote_keypair.pubkey(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let mut instructions = vote_instruction::create_account_with_config_v2( &mint_keypair.pubkey(), - &vote_keypair.pubkey(), - &VoteInit { + &vote_pubkey, + &VoteInitV2 { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_keypair.pubkey(), - authorized_withdrawer: vote_keypair.pubkey(), - commission: 0, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, vote_balance, vote_instruction::CreateVoteAccountConfig { @@ -3620,13 +3631,18 @@ fn test_add_builtin() { assert!(bank.get_account(&mock_vote_program_id()).is_some()); let mock_account = Keypair::new(); + let vote_pubkey = mock_account.pubkey(); let mock_validator_identity = Keypair::new(); - let mut instructions = vote_instruction::create_account_with_config( + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let mut instructions = vote_instruction::create_account_with_config_v2( &mint_keypair.pubkey(), - &mock_account.pubkey(), - &VoteInit { + &vote_pubkey, + &VoteInitV2 { node_pubkey: mock_validator_identity.pubkey(), - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, 1, vote_instruction::CreateVoteAccountConfig { @@ -3667,13 +3683,18 @@ fn test_add_duplicate_static_program() { }); let mock_account = Keypair::new(); + let vote_pubkey = mock_account.pubkey(); let mock_validator_identity = Keypair::new(); - let instructions = vote_instruction::create_account_with_config( + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let instructions = vote_instruction::create_account_with_config_v2( &mint_keypair.pubkey(), - &mock_account.pubkey(), - &VoteInit { + &vote_pubkey, + &VoteInitV2 { node_pubkey: mock_validator_identity.pubkey(), - ..VoteInit::default() + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + ..VoteInitV2::default() }, 1, vote_instruction::CreateVoteAccountConfig { @@ -8283,17 +8304,25 @@ fn test_vote_epoch_panic() { ); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); - let vote_keypair = keypair_from_seed(&[1u8; 32]).unwrap(); + let vote_keypair = Keypair::new(); + let vote_pubkey = vote_keypair.pubkey(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_pubkey); let mut setup_ixs = Vec::new(); - setup_ixs.extend(vote_instruction::create_account_with_config( + setup_ixs.extend(vote_instruction::create_account_with_config_v2( &mint_keypair.pubkey(), - &vote_keypair.pubkey(), - &VoteInit { + &vote_pubkey, + &VoteInitV2 { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_keypair.pubkey(), + authorized_voter: vote_pubkey, authorized_withdrawer: mint_keypair.pubkey(), - commission: 0, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof_of_possession, + inflation_rewards_commission_bps: 0, + inflation_rewards_collector: Pubkey::default(), + block_revenue_commission_bps: 0, + block_revenue_collector: Pubkey::default(), }, 1_000_000_000, vote_instruction::CreateVoteAccountConfig { @@ -9881,7 +9910,10 @@ fn test_rent_state_changes_sysvars() { let validator_pubkey = Pubkey::new_unique(); let validator_stake_lamports = LAMPORTS_PER_SOL; - let validator_vote_account_pubkey = Pubkey::new_unique(); + let validator_vote_account_keypair = Keypair::new(); + let (bls_pubkey, bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&validator_vote_account_keypair.pubkey()); + let validator_vote_account_pubkey = validator_vote_account_keypair.pubkey(); let validator_voting_keypair = Keypair::new(); let validator_vote_account = vote_state::create_v4_account_with_authorized( @@ -9914,7 +9946,10 @@ fn test_rent_state_changes_sysvars() { &validator_vote_account_pubkey, &validator_voting_keypair.pubkey(), &Pubkey::new_unique(), - VoteAuthorize::Voter, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey, + bls_proof_of_possession, + }), )], Some(&mint_keypair.pubkey()), &[&mint_keypair, &validator_voting_keypair], @@ -12300,6 +12335,70 @@ fn test_rehash_accounts_unmodified() { assert_eq!(post_bank_hash, prev_bank_hash); } +#[test] +fn test_should_use_vote_keyed_leader_schedule() { + let genesis_config = genesis_utils::create_genesis_config(10_000).genesis_config; + let epoch_schedule = &genesis_config.epoch_schedule; + let create_test_bank = |bank_epoch: Epoch, feature_activation_slot: Option| -> Bank { + let mut bank = Bank::new_for_tests(&genesis_config); + bank.epoch = bank_epoch; + let mut feature_set = FeatureSet::default(); + if let Some(feature_activation_slot) = feature_activation_slot { + let feature_activation_epoch = bank.epoch_schedule().get_epoch(feature_activation_slot); + assert!(feature_activation_epoch <= bank_epoch); + feature_set.activate( + &agave_feature_set::enable_vote_address_leader_schedule::id(), + feature_activation_slot, + ); + } + bank.feature_set = Arc::new(feature_set); + bank + }; + + // Test feature activation at genesis + let test_bank = create_test_bank(0, Some(0)); + for epoch in 0..10 { + assert_eq!( + test_bank.should_use_vote_keyed_leader_schedule(epoch), + Some(true), + ); + } + + // Test feature activated in previous epoch + let slot_in_prev_epoch = epoch_schedule.get_first_slot_in_epoch(1); + let test_bank = create_test_bank(2, Some(slot_in_prev_epoch)); + for epoch in 0..=(test_bank.epoch + 1) { + assert_eq!( + test_bank.should_use_vote_keyed_leader_schedule(epoch), + Some(epoch >= test_bank.epoch), + ); + } + + // Test feature activated in current epoch + let current_epoch_slot = epoch_schedule.get_last_slot_in_epoch(1); + let test_bank = create_test_bank(1, Some(current_epoch_slot)); + for epoch in 0..=(test_bank.epoch + 1) { + assert_eq!( + test_bank.should_use_vote_keyed_leader_schedule(epoch), + Some(epoch > test_bank.epoch), + ); + } + + // Test feature not activated yet + let test_bank = create_test_bank(1, None); + let max_cached_leader_schedule = epoch_schedule.get_leader_schedule_epoch(test_bank.slot()); + for epoch in 0..=(max_cached_leader_schedule + 1) { + if epoch <= max_cached_leader_schedule { + assert_eq!( + test_bank.should_use_vote_keyed_leader_schedule(epoch), + Some(false), + ); + } else { + assert_eq!(test_bank.should_use_vote_keyed_leader_schedule(epoch), None); + } + } +} + #[test] fn test_apply_builtin_program_feature_transitions_for_new_epoch() { let (genesis_config, _mint_keypair) = create_genesis_config(100_000); diff --git a/svm-feature-set/src/lib.rs b/svm-feature-set/src/lib.rs index 4c72093bb029e9..d117ef455f8734 100644 --- a/svm-feature-set/src/lib.rs +++ b/svm-feature-set/src/lib.rs @@ -53,6 +53,7 @@ pub struct SVMFeatureSet { pub poseidon_enforce_padding: bool, pub fix_alt_bn128_pairing_length_check: bool, pub alt_bn128_little_endian: bool, + pub bls_pubkey_management_in_vote_account: bool, } impl SVMFeatureSet { @@ -102,6 +103,7 @@ impl SVMFeatureSet { poseidon_enforce_padding: true, fix_alt_bn128_pairing_length_check: true, alt_bn128_little_endian: true, + bls_pubkey_management_in_vote_account: true, } } } diff --git a/votor-messages/src/consensus_message.rs b/votor-messages/src/consensus_message.rs index 0588f33b5ae686..1c4372d946ada2 100644 --- a/votor-messages/src/consensus_message.rs +++ b/votor-messages/src/consensus_message.rs @@ -17,7 +17,7 @@ pub type Block = (Slot, Hash); #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "A9wHKYuPgAR7cxidTT51ACVv5WNqHkfj2jVqJLGBC5bv") + frozen_abi(digest = "4nTsJNvSwHrs8FHz2TPbSBiYc8Eattw4v31XDge2c5zA") )] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VoteMessage { @@ -81,7 +81,7 @@ impl CertificateType { #[cfg_attr( feature = "frozen-abi", derive(AbiExample), - frozen_abi(digest = "CLJbmbTECu2MeBmqWNDsfTgkAC2yudxHsmNU9saww8L") + frozen_abi(digest = "7AATkxH9takDKv4pGT3jsqC18nqEgdRTYGUP8G2bbnGp") )] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Certificate { @@ -98,7 +98,7 @@ pub struct Certificate { #[cfg_attr( feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor), - frozen_abi(digest = "4YvBgNbve59tf9i4DSraiSZ3eoMF4Y1V5mDdUCoFv8S2") + frozen_abi(digest = "DaXHFwdUAESLYZvyhsgwjeBUEeUqjp3t7WfkRsSp3cEE") )] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[allow(clippy::large_enum_variant)] From ac2aeae387015d109acc2b46262d35ec35503751 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:37:39 -0800 Subject: [PATCH 02/17] Make offline signing work for vote_authorize_voter_with_bls. --- clap-utils/src/input_parsers.rs | 17 ++-- cli-output/src/cli_output.rs | 22 +++++ cli/src/vote.rs | 147 +++++++++++++++++++++++++------- 3 files changed, 144 insertions(+), 42 deletions(-) diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index 9dfde72512e0db..b77e6f5b2127a5 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -7,6 +7,7 @@ use { clap::ArgMatches, solana_bls_signatures::{ ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, Pubkey as BLSPubkey, + PubkeyCompressed as BLSPubkeyCompressed, }, solana_clock::UnixTimestamp, solana_cluster_type::ClusterType, @@ -119,19 +120,15 @@ pub fn bls_pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option, name: &str) -> Option { + value_of(matches, name) +} + pub fn bls_proof_of_possession_of( matches: &ArgMatches<'_>, name: &str, -) -> Option> { - matches.values_of(name).map(|values| { - values - .map(|value| { - BLSProofOfPossessionCompressed::from_str(value).unwrap_or_else(|_| { - panic!("Failed to parse BLS public key from value: {value}") - }) - }) - .collect() - }) +) -> Option { + value_of(matches, name) } // Return pubkey/signature pairs for a string of the form pubkey=signature diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 29e47453b26903..dcfecc9305ba72 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -2018,6 +2018,28 @@ impl fmt::Display for CliSignOnlyData { } } +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct CliSignOnlyDataWithBLS { + #[serde(flatten)] + pub base: CliSignOnlyData, + pub bls_pubkey: String, + pub bls_proof_of_possession: String, +} + +impl QuietDisplay for CliSignOnlyDataWithBLS {} +impl VerboseDisplay for CliSignOnlyDataWithBLS {} + +impl fmt::Display for CliSignOnlyDataWithBLS { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f)?; + write!(f, "{}", self.base)?; + writeln_name_value(f, "BLS Public Key:", &self.bls_pubkey)?; + writeln_name_value(f, "BLS Proof of Possession:", &self.bls_proof_of_possession)?; + Ok(()) + } +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CliSignature { diff --git a/cli/src/vote.rs b/cli/src/vote.rs index c7f2ac7e7e8bba..4b5ce97ae9758b 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -16,7 +16,11 @@ use { agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED, clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}, solana_account::Account, - solana_bls_signatures::keypair::Keypair as BLSKeypair, + solana_bls_signatures::{ + keypair::Keypair as BLSKeypair, + proof_of_possession::ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, + pubkey::PubkeyCompressed as BLSPubkeyCompressed, + }, solana_clap_utils::{ compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG}, fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, @@ -28,8 +32,9 @@ use { offline::*, }, solana_cli_output::{ - display::build_balance_message, return_signers_with_config, CliEpochVotingHistory, - CliLandedVote, CliVoteAccount, ReturnSignersConfig, + display::build_balance_message, return_signers_data, return_signers_with_config, + CliEpochVotingHistory, CliLandedVote, CliSignOnlyDataWithBLS, CliVoteAccount, + ReturnSignersConfig, }, solana_commitment_config::CommitmentConfig, solana_feature_gate_interface::from_account, @@ -257,13 +262,29 @@ impl VoteSubCommands for App<'_, '_> { .validator(is_valid_signer) .help("Current authorized vote signer."), ) - .arg( + .arg(pubkey!( Arg::with_name("new_authorized") .index(3) - .value_name("NEW_AUTHORIZED_KEYPAIR") - .required(true) - .validator(is_valid_signer) - .help("New authorized vote signer."), + .value_name("NEW_AUTHORIZED_PUBKEY_OR_KEYPAIR") + .required(true), + "New authorized vote signer. pubkey if BLS pubkey and proof of possession are \ + provided, otherwise keypair." + )) + .arg( + Arg::with_name("bls_pubkey") + .long("bls-pubkey") + .value_name("BLS_PUBKEY_COMPRESSED") + .takes_value(true) + .required(false) + .help("New BLS public key in compressed form"), + ) + .arg( + Arg::with_name("bls_proof_of_possession") + .long("bls-proof-of-possession") + .value_name("BLS_PROOF_OF_POSSESSION_COMPRESSED") + .takes_value(true) + .required(false) + .help("New BLS proof of possession in compressed form"), ) .offline_args() .nonce_args(false) @@ -833,8 +854,35 @@ pub fn parse_vote_authorize_with_bls( let vote_account_pubkey = pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?; - let (new_authorized, new_authorized_pubkey) = - signer_of(matches, "new_authorized", wallet_manager)?; + let bls_pubkey = bls_pubkey_of(matches, "bls_pubkey"); + let bls_proof_of_possession = bls_proof_of_possession_of(matches, "bls_proof_of_possession"); + // If both BLS pubkey and proof of possession are provided, use them directly, otherwise need to derive from the new authorized signer + if bls_pubkey.is_some() ^ bls_proof_of_possession.is_some() { + return Err(CliError::BadParameter( + "Both BLS pubkey and proof of possession must be provided together".to_string(), + )); + } + let ( + bls_pubkey_compressed_bytes, + bls_proof_of_possession_compressed_bytes, + new_authorized_pubkey, + ) = if let Some(bls_pubkey) = bls_pubkey { + (bls_pubkey.0, bls_proof_of_possession.unwrap().0, None) + } else { + let (new_authorized, new_authorized_pubkey) = + signer_of(matches, "new_authorized", wallet_manager)?; + let new_authorized_voter_keypair: &dyn Signer = new_authorized.as_deref().unwrap(); + let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = + generate_bls_pubkey_and_proof_of_possession( + &vote_account_pubkey, + new_authorized_voter_keypair, + ); + ( + bls_pubkey_compressed_bytes, + bls_proof_of_possession_compressed_bytes, + new_authorized_pubkey, + ) + }; let sign_only = matches.is_present(SIGN_ONLY_ARG.name); let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); @@ -848,22 +896,25 @@ pub fn parse_vote_authorize_with_bls( let mut bulk_signers = vec![fee_payer, authorized]; - let new_authorized_voter_keypair: &dyn Signer = new_authorized.as_deref().unwrap(); - let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = - generate_bls_pubkey_and_proof_of_possession( - &vote_account_pubkey, - new_authorized_voter_keypair, - ); - let vote_authorize = VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { bls_pubkey: bls_pubkey_compressed_bytes, bls_proof_of_possession: bls_proof_of_possession_compressed_bytes, }); - let new_authorized_pubkey = new_authorized_pubkey.unwrap(); - if checked { - bulk_signers.push(new_authorized); - } + let new_authorized_pubkey = if checked { + let (new_authorized_signer, new_authorized_pubkey) = + signer_of(matches, "new_authorized", wallet_manager)?; + bulk_signers.push(new_authorized_signer); + new_authorized_pubkey.unwrap() + } else { + new_authorized_pubkey + .or_else(|| { + pubkey_of_signer(matches, "new_authorized", wallet_manager) + .ok() + .flatten() + }) + .unwrap() + }; if nonce_account.is_some() { bulk_signers.push(nonce_authority); } @@ -1534,13 +1585,31 @@ pub async fn process_vote_authorize( if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; - return_signers_with_config( - &tx, - &config.output_format, - &ReturnSignersConfig { - dump_transaction_message, - }, - ) + if let VoteAuthorize::VoterWithBLS(args) = &vote_authorize { + let base = return_signers_data( + &tx, + &ReturnSignersConfig { + dump_transaction_message, + }, + ); + let bls_pubkey: BLSPubkeyCompressed = BLSPubkeyCompressed(args.bls_pubkey); + let bls_proof_of_possession: BLSProofOfPossessionCompressed = + BLSProofOfPossessionCompressed(args.bls_proof_of_possession); + let output = CliSignOnlyDataWithBLS { + base, + bls_pubkey: bls_pubkey.to_string(), + bls_proof_of_possession: bls_proof_of_possession.to_string(), + }; + Ok(config.output_format.formatted_string(&output)) + } else { + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) + } } else { tx.try_sign(&config.signers, recent_blockhash)?; if let Some(nonce_account) = &nonce_account { @@ -2121,9 +2190,9 @@ mod tests { let blockhash_string = format!("{blockhash}"); let nonce_account = Pubkey::new_unique(); + let (bls_pubkey, bls_proof_of_possession) = + generate_bls_pubkey_and_proof_of_possession(&pubkey, &keypair2); let vote_authorize = if with_bls { - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&pubkey, &keypair2); VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { bls_pubkey, bls_proof_of_possession, @@ -2132,12 +2201,19 @@ mod tests { VoteAuthorize::Voter }; let test_authorize_voter = if with_bls { + let bls_pubkey_compressed = BLSPubkeyCompressed(bls_pubkey); + let bls_proof_of_possession_compressed = + BLSProofOfPossessionCompressed(bls_proof_of_possession); test_commands.clone().get_matches_from(vec![ "test", "vote-authorize-voter-with-bls", &pubkey_string, &default_keypair_file, - &keypair2_file, + &pubkey2_string, + "--bls-pubkey", + &bls_pubkey_compressed.to_string(), + "--bls-proof-of-possession", + &bls_proof_of_possession_compressed.to_string(), ]) } else { test_commands.clone().get_matches_from(vec![ @@ -2268,12 +2344,15 @@ mod tests { let authorized_sig = authorized_keypair.sign_message(&[0u8]); let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig); let test_authorize_voter = if with_bls { + let bls_pubkey_compressed = BLSPubkeyCompressed(bls_pubkey); + let bls_proof_of_possession_compressed = + BLSProofOfPossessionCompressed(bls_proof_of_possession); test_commands.clone().get_matches_from(vec![ "test", "vote-authorize-voter-with-bls", &pubkey_string, &authorized_keypair.pubkey().to_string(), - &keypair2_file, + &pubkey2_string, "--blockhash", &blockhash_string, "--signer", @@ -2286,6 +2365,10 @@ mod tests { &nonce_account.to_string(), "--nonce-authority", &pubkey2_string, + "--bls-pubkey", + &bls_pubkey_compressed.to_string(), + "--bls-proof-of-possession", + &bls_proof_of_possession_compressed.to_string(), ]) } else { test_commands.clone().get_matches_from(vec![ From 1025a31ecc2bb8fbc4a6271c2cf9c20c90966c2b Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:57:16 -0800 Subject: [PATCH 03/17] Revert unintended change. --- ledger/src/leader_schedule_utils.rs | 54 +++++++---------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/ledger/src/leader_schedule_utils.rs b/ledger/src/leader_schedule_utils.rs index 825f8d58911db4..747784c865e461 100644 --- a/ledger/src/leader_schedule_utils.rs +++ b/ledger/src/leader_schedule_utils.rs @@ -1,7 +1,5 @@ use { - crate::leader_schedule::{ - IdentityKeyedLeaderSchedule, LeaderSchedule, VoteKeyedLeaderSchedule, - }, + crate::leader_schedule::{LeaderSchedule, VoteKeyedLeaderSchedule}, solana_clock::{Epoch, Slot, NUM_CONSECUTIVE_LEADER_SLOTS}, solana_pubkey::Pubkey, solana_runtime::bank::Bank, @@ -10,26 +8,14 @@ use { /// Return the leader schedule for the given epoch. pub fn leader_schedule(epoch: Epoch, bank: &Bank) -> Option { - let use_new_leader_schedule = bank.should_use_vote_keyed_leader_schedule(epoch)?; - if use_new_leader_schedule { - bank.epoch_vote_accounts(epoch).map(|vote_accounts_map| { - Box::new(VoteKeyedLeaderSchedule::new( - vote_accounts_map, - epoch, - bank.get_slots_in_epoch(epoch), - NUM_CONSECUTIVE_LEADER_SLOTS, - )) as LeaderSchedule - }) - } else { - bank.epoch_staked_nodes(epoch).map(|stakes| { - Box::new(IdentityKeyedLeaderSchedule::new( - &stakes, - epoch, - bank.get_slots_in_epoch(epoch), - NUM_CONSECUTIVE_LEADER_SLOTS, - )) as LeaderSchedule - }) - } + bank.epoch_vote_accounts(epoch).map(|vote_accounts_map| { + Box::new(VoteKeyedLeaderSchedule::new( + vote_accounts_map, + epoch, + bank.get_slots_in_epoch(epoch), + NUM_CONSECUTIVE_LEADER_SLOTS, + )) as LeaderSchedule + }) } /// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot @@ -97,34 +83,20 @@ mod tests { super::*, solana_runtime::genesis_utils::{ bootstrap_validator_stake_lamports, create_genesis_config_with_leader, - deactivate_features, }, - test_case::test_case, }; - #[test_case(true; "vote keyed leader schedule")] - #[test_case(false; "identity keyed leader schedule")] - fn test_leader_schedule_via_bank(use_vote_keyed_leader_schedule: bool) { + #[test] + fn test_leader_schedule_via_bank() { let pubkey = solana_pubkey::new_rand(); - let mut genesis_config = + let genesis_config = create_genesis_config_with_leader(0, &pubkey, bootstrap_validator_stake_lamports()) .genesis_config; - if !use_vote_keyed_leader_schedule { - deactivate_features( - &mut genesis_config, - &vec![agave_feature_set::enable_vote_address_leader_schedule::id()], - ); - } - let bank = Bank::new_for_tests(&genesis_config); let leader_schedule = leader_schedule(0, &bank).unwrap(); - assert_eq!( - leader_schedule.get_vote_key_at_slot_index(0).is_some(), - use_vote_keyed_leader_schedule - ); - + assert!(leader_schedule.get_vote_key_at_slot_index(0).is_some()); assert_eq!(leader_schedule[0], pubkey); assert_eq!(leader_schedule[1], pubkey); assert_eq!(leader_schedule[2], pubkey); From 3cda71f19af63a1b517285f79c3acf67b5869464 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:59:26 -0800 Subject: [PATCH 04/17] Revert unintended Cargo.lock change. --- ci/xtask/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/xtask/Cargo.lock b/ci/xtask/Cargo.lock index 05337d8b2b8afe..bcef8ceca51be3 100644 --- a/ci/xtask/Cargo.lock +++ b/ci/xtask/Cargo.lock @@ -378,9 +378,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" From a02b49d3dcdaa2c859f889064f05a0ca49137ece Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:20:42 -0800 Subject: [PATCH 05/17] Fix a bad merge. --- genesis/src/main.rs | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 5d1c823eaff0b6..dd5ab3a80b145a 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -7,7 +7,7 @@ use { clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches}, itertools::Itertools, solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, - solana_bls_signatures::{Pubkey as BLSPubkey, PubkeyCompressed as BLSPubkeyCompressed}, + solana_bls_signatures::PubkeyCompressed as BLSPubkeyCompressed, solana_clap_utils::{ input_parsers::{ bls_pubkeys_of, cluster_type_of, pubkey_of, pubkeys_of, @@ -76,16 +76,6 @@ fn pubkey_from_str(key_str: &str) -> Result> { }) } -fn bls_pubkey_from_str(key_str: &str) -> Result> { - match BLSPubkeyCompressed::from_str(key_str) { - Ok(bls_pubkey) => Ok(bls_pubkey), - Err(_) => { - let bls_pubkey = BLSPubkey::from_str(key_str)?; - Ok(bls_pubkey.try_into()?) - } - } -} - pub fn load_genesis_accounts(file: &str, genesis_config: &mut GenesisConfig) -> io::Result { let mut lamports = 0; let accounts_file = File::open(file)?; @@ -159,16 +149,12 @@ pub fn load_validator_accounts( )) })?, ]; - let bls_pubkeys: Vec = account_details - .bls_pubkey - .as_ref() - .map(|key_str| { - bls_pubkey_from_str(key_str) + let bls_pubkeys: Vec = + account_details.bls_pubkey.map_or(Ok(vec![]), |s| { + BLSPubkeyCompressed::from_str(&s) + .map(|pk| vec![pk]) .map_err(|err| io::Error::other(format!("Invalid BLS pubkey: {err}"))) - }) - .transpose()? - .map(|pk| vec![pk]) - .unwrap_or_default(); + })?; add_validator_accounts( genesis_config, @@ -956,11 +942,12 @@ fn main() -> Result<(), Box> { mod tests { use { super::*, - solana_bls_signatures::keypair::Keypair as BLSKeypair, + solana_bls_signatures::{keypair::Keypair as BLSKeypair, Pubkey as BLSPubkey}, solana_borsh::v1 as borsh1, solana_genesis_config::GenesisConfig, solana_stake_interface as stake, std::{collections::HashMap, fs::remove_file, io::Write, path::Path}, + test_case::test_case, }; #[test] @@ -1346,7 +1333,7 @@ mod tests { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: Some(BLSKeypair::new().public.to_string()), + bls_pubkey: generate_bls_pubkey(), balance_lamports: 100000000000, stake_lamports: 10000000000, }, @@ -1354,7 +1341,7 @@ mod tests { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: Some(BLSKeypair::new().public.to_string()), + bls_pubkey: generate_bls_pubkey(), balance_lamports: 200000000000, stake_lamports: 20000000000, }, @@ -1362,7 +1349,7 @@ mod tests { identity_account: solana_pubkey::new_rand().to_string(), vote_account: solana_pubkey::new_rand().to_string(), stake_account: solana_pubkey::new_rand().to_string(), - bls_pubkey: Some(BLSKeypair::new().public.to_string()), + bls_pubkey: generate_bls_pubkey(), balance_lamports: 300000000000, stake_lamports: 30000000000, }, @@ -1433,7 +1420,7 @@ mod tests { ); assert_eq!( bls_pubkey_compressed_from_input, - bls_pubkey_compressed_from_account, + bls_pubkey_compressed_from_account ); } else { assert!(b64_account.bls_pubkey.is_none()); From 878ba30882ff877ad4a3da074d831e0aa97a02a6 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:27:57 -0800 Subject: [PATCH 06/17] Revert sneaked in change. --- runtime/src/bank.rs | 39 ------------------------ runtime/src/bank/tests.rs | 64 --------------------------------------- 2 files changed, 103 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 7cf2edf359c38b..006b88dcd8c152 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5012,45 +5012,6 @@ impl Bank { self.epoch_schedule().get_leader_schedule_epoch(slot) } - /// Returns whether the specified epoch should use the new vote account - /// keyed leader schedule - pub fn should_use_vote_keyed_leader_schedule(&self, epoch: Epoch) -> Option { - let effective_epoch = self - .feature_set - .activated_slot(&agave_feature_set::enable_vote_address_leader_schedule::id()) - .map(|activation_slot| { - // If the feature was activated at genesis, then the new leader - // schedule should be effective immediately in the first epoch - if activation_slot == 0 { - return 0; - } - - // Calculate the epoch that the feature became activated in - let activation_epoch = self.epoch_schedule.get_epoch(activation_slot); - - // The effective epoch is the epoch immediately after the - // activation epoch - activation_epoch.wrapping_add(1) - }); - - // Starting from the effective epoch, always use the new leader schedule - if let Some(effective_epoch) = effective_epoch { - return Some(epoch >= effective_epoch); - } - - // Calculate the max epoch we can cache a leader schedule for - let max_cached_leader_schedule = self.get_leader_schedule_epoch(self.slot()); - if epoch <= max_cached_leader_schedule { - // The feature cannot be effective by the specified epoch - Some(false) - } else { - // Cannot determine if an epoch should use the new leader schedule if the - // the epoch is too far in the future because we won't know if the feature - // will have been activated by then or not. - None - } - } - /// a bank-level cache of vote accounts and stake delegation info fn update_stakes_cache( &self, diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 3ed5e55fed82a2..8cffcbc3e24b32 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -12335,70 +12335,6 @@ fn test_rehash_accounts_unmodified() { assert_eq!(post_bank_hash, prev_bank_hash); } -#[test] -fn test_should_use_vote_keyed_leader_schedule() { - let genesis_config = genesis_utils::create_genesis_config(10_000).genesis_config; - let epoch_schedule = &genesis_config.epoch_schedule; - let create_test_bank = |bank_epoch: Epoch, feature_activation_slot: Option| -> Bank { - let mut bank = Bank::new_for_tests(&genesis_config); - bank.epoch = bank_epoch; - let mut feature_set = FeatureSet::default(); - if let Some(feature_activation_slot) = feature_activation_slot { - let feature_activation_epoch = bank.epoch_schedule().get_epoch(feature_activation_slot); - assert!(feature_activation_epoch <= bank_epoch); - feature_set.activate( - &agave_feature_set::enable_vote_address_leader_schedule::id(), - feature_activation_slot, - ); - } - bank.feature_set = Arc::new(feature_set); - bank - }; - - // Test feature activation at genesis - let test_bank = create_test_bank(0, Some(0)); - for epoch in 0..10 { - assert_eq!( - test_bank.should_use_vote_keyed_leader_schedule(epoch), - Some(true), - ); - } - - // Test feature activated in previous epoch - let slot_in_prev_epoch = epoch_schedule.get_first_slot_in_epoch(1); - let test_bank = create_test_bank(2, Some(slot_in_prev_epoch)); - for epoch in 0..=(test_bank.epoch + 1) { - assert_eq!( - test_bank.should_use_vote_keyed_leader_schedule(epoch), - Some(epoch >= test_bank.epoch), - ); - } - - // Test feature activated in current epoch - let current_epoch_slot = epoch_schedule.get_last_slot_in_epoch(1); - let test_bank = create_test_bank(1, Some(current_epoch_slot)); - for epoch in 0..=(test_bank.epoch + 1) { - assert_eq!( - test_bank.should_use_vote_keyed_leader_schedule(epoch), - Some(epoch > test_bank.epoch), - ); - } - - // Test feature not activated yet - let test_bank = create_test_bank(1, None); - let max_cached_leader_schedule = epoch_schedule.get_leader_schedule_epoch(test_bank.slot()); - for epoch in 0..=(max_cached_leader_schedule + 1) { - if epoch <= max_cached_leader_schedule { - assert_eq!( - test_bank.should_use_vote_keyed_leader_schedule(epoch), - Some(false), - ); - } else { - assert_eq!(test_bank.should_use_vote_keyed_leader_schedule(epoch), None); - } - } -} - #[test] fn test_apply_builtin_program_feature_transitions_for_new_epoch() { let (genesis_config, _mint_keypair) = create_genesis_config(100_000); From 9461800e371050f2f516f4001a8c61c9bd86ec2c Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:55:38 -0800 Subject: [PATCH 07/17] Address pr comments. --- programs/vote/src/vote_processor.rs | 17 -------- programs/vote/src/vote_state/handler.rs | 53 +++++++++++-------------- programs/vote/src/vote_state/mod.rs | 35 ++++++++++++++++ 3 files changed, 59 insertions(+), 46 deletions(-) diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 9be50632f181e5..dc8557a6b37c6f 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -1835,23 +1835,6 @@ mod tests { instruction_accounts.clone(), Err(InstructionError::InvalidArgument), ); - - // Test that if the account already has a BLS pubkey, VoteAuthorize::Voter - // is rejected - // Initialize a new vote account with BLS pubkey - let instruction_data = serialize(&VoteInstruction::Authorize( - authorized_voter_pubkey, - VoteAuthorize::Voter, - )) - .unwrap(); - process_instruction( - true, - true, - &instruction_data, - transaction_accounts, - instruction_accounts, - Err(InstructionError::InvalidInstructionData), - ); } #[test_case(false ; "VoteStateV3")] diff --git a/programs/vote/src/vote_state/handler.rs b/programs/vote/src/vote_state/handler.rs index 7eabd9ddff3510..84be74dcae716d 100644 --- a/programs/vote/src/vote_state/handler.rs +++ b/programs/vote/src/vote_state/handler.rs @@ -1044,10 +1044,7 @@ mod tests { }, solana_vote_interface::{ authorized_voters::AuthorizedVoters, - state::{ - BlockTimestamp, VoteInit, BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, - MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY, - }, + state::{BlockTimestamp, VoteInit, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY}, }, std::collections::VecDeque, test_case::test_case, @@ -2221,31 +2218,6 @@ mod tests { assert_eq!(result, expected_result); } - #[test] - fn test_bls_pubkey_handling() { - let bls_pubkey_compressed = [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]; - let vote_state_v4 = VoteStateV4::new( - &VoteInitV2 { - node_pubkey: Pubkey::new_unique(), - authorized_voter: Pubkey::new_unique(), - authorized_withdrawer: Pubkey::new_unique(), - block_revenue_commission_bps: 500, - block_revenue_collector: Pubkey::new_unique(), - inflation_rewards_commission_bps: 1000, - inflation_rewards_collector: Pubkey::new_unique(), - authorized_voter_bls_pubkey: bls_pubkey_compressed, - authorized_voter_bls_proof_of_possession: [2u8; - BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], - }, - &Clock::default(), - ); - assert_eq!( - vote_state_v4.bls_pubkey_compressed, - Some(bls_pubkey_compressed) - ); - assert!(vote_state_v4.has_bls_pubkey()); - } - #[test] fn test_get_and_update_authorized_voter_v4_with_bls() { let vote_pubkey = Pubkey::new_unique(); @@ -2282,5 +2254,28 @@ mod tests { Some(bls_pubkey_compressed) ); assert!(vote_state.has_bls_pubkey()); + + // Now update authorized voter again with another BLS pubkey. + let newer_voter = Pubkey::new_unique(); + let newer_bls_pubkey_compressed = [7u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]; + vote_state + .set_new_authorized_voter( + &newer_voter, + 1, + 2, + Some(&newer_bls_pubkey_compressed), + |_| Ok(()), + ) + .unwrap(); + assert_eq!(vote_state.authorized_voters().len(), 3); + assert_eq!( + *vote_state.authorized_voters().last().unwrap().1, + newer_voter + ); + assert_eq!( + vote_state.bls_pubkey_compressed, + Some(newer_bls_pubkey_compressed) + ); + assert!(vote_state.has_bls_pubkey()); } } diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index a555eeeccda128..c0e39b2b580db8 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -4067,6 +4067,10 @@ mod tests { assert!(vote_state.has_bls_pubkey()); // Test replay attack, can't use someone else's BLS pubkey and PoP + let clock = Clock { + epoch: 3, + ..Clock::default() + }; let (others_bls_pubkey, others_bls_proof_of_possession) = create_bls_pubkey_and_proof_of_possession(&Pubkey::new_unique()); let new_node_pubkey = solana_pubkey::new_rand(); @@ -4088,5 +4092,36 @@ mod tests { ), Err(InstructionError::InvalidArgument), ); + + // Test updating to a new BLS pubkey, can only do it in next epoch. + let clock = Clock { + epoch: 5, + ..Clock::default() + }; + let (new_bls_pubkey, new_bls_proof_of_possession) = + create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey); + let new_authorized_voter = solana_pubkey::new_rand(); + let signers: HashSet = vec![authorized_withdrawer, new_authorized_voter] + .into_iter() + .collect(); + assert_eq!( + authorize( + &mut borrowed_account, + VoteStateTargetVersion::V4, + &new_authorized_voter, + VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { + bls_pubkey: new_bls_pubkey, + bls_proof_of_possession: new_bls_proof_of_possession + }), + &signers, + &clock, + true, + ), + Ok(()) + ); + let vote_state = + VoteStateV4::deserialize(borrowed_account.get_data(), &new_authorized_voter).unwrap(); + assert_eq!(vote_state.bls_pubkey_compressed, Some(new_bls_pubkey)); + assert!(vote_state.has_bls_pubkey()); } } From 3e06daa1ce26537fac7271d4f1f4949231c60595 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:23:33 -0800 Subject: [PATCH 08/17] Revert unintended changes and fix test. --- genesis/src/main.rs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/genesis/src/main.rs b/genesis/src/main.rs index dd5ab3a80b145a..2b0f5409347232 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -7,7 +7,7 @@ use { clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches}, itertools::Itertools, solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, - solana_bls_signatures::PubkeyCompressed as BLSPubkeyCompressed, + solana_bls_signatures::{Pubkey as BLSPubkey, PubkeyCompressed as BLSPubkeyCompressed}, solana_clap_utils::{ input_parsers::{ bls_pubkeys_of, cluster_type_of, pubkey_of, pubkeys_of, @@ -76,6 +76,16 @@ fn pubkey_from_str(key_str: &str) -> Result> { }) } +fn bls_pubkey_from_str(key_str: &str) -> Result> { + match BLSPubkeyCompressed::from_str(key_str) { + Ok(bls_pubkey) => Ok(bls_pubkey), + Err(_) => { + let bls_pubkey = BLSPubkey::from_str(key_str)?; + Ok(bls_pubkey.try_into()?) + } + } +} + pub fn load_genesis_accounts(file: &str, genesis_config: &mut GenesisConfig) -> io::Result { let mut lamports = 0; let accounts_file = File::open(file)?; @@ -149,12 +159,16 @@ pub fn load_validator_accounts( )) })?, ]; - let bls_pubkeys: Vec = - account_details.bls_pubkey.map_or(Ok(vec![]), |s| { - BLSPubkeyCompressed::from_str(&s) - .map(|pk| vec![pk]) + let bls_pubkeys: Vec = account_details + .bls_pubkey + .as_ref() + .map(|key_str| { + bls_pubkey_from_str(key_str) .map_err(|err| io::Error::other(format!("Invalid BLS pubkey: {err}"))) - })?; + }) + .transpose()? + .map(|pk| vec![pk]) + .unwrap_or_default(); add_validator_accounts( genesis_config, @@ -942,10 +956,11 @@ fn main() -> Result<(), Box> { mod tests { use { super::*, - solana_bls_signatures::{keypair::Keypair as BLSKeypair, Pubkey as BLSPubkey}, + solana_bls_signatures::keypair::Keypair as BLSKeypair, solana_borsh::v1 as borsh1, solana_genesis_config::GenesisConfig, solana_stake_interface as stake, + solana_vote_program::vote_state::BLS_PUBLIC_KEY_COMPRESSED_SIZE, std::{collections::HashMap, fs::remove_file, io::Write, path::Path}, test_case::test_case, }; @@ -1420,11 +1435,14 @@ mod tests { ); assert_eq!( bls_pubkey_compressed_from_input, - bls_pubkey_compressed_from_account + bls_pubkey_compressed_from_account, ); } else { assert!(b64_account.bls_pubkey.is_none()); - assert!(vote_state.bls_pubkey_compressed.is_none()); + assert_eq!( + vote_state.bls_pubkey_compressed, + Some([0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE]) + ); } // check stake account From 0e2d80c53736767e6a6e975aa76c7fbcb1e54527 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:17:45 -0800 Subject: [PATCH 09/17] Split all cli changes to another PR. --- Cargo.lock | 2 - cli/Cargo.toml | 10 +- cli/src/cli.rs | 72 +-- cli/src/vote.rs | 1468 +++++++++----------------------------------- cli/tests/stake.rs | 9 +- cli/tests/vote.rs | 12 +- 6 files changed, 287 insertions(+), 1286 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c04b9b93427ed4..c301c50014d7fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7763,7 +7763,6 @@ dependencies = [ "agave-feature-set", "agave-logger", "agave-syscalls", - "agave-votor-messages", "assert_matches", "bincode", "bs58", @@ -7785,7 +7784,6 @@ dependencies = [ "solana-account", "solana-account-decoder", "solana-address-lookup-table-interface", - "solana-bls-signatures", "solana-borsh", "solana-clap-utils", "solana-cli-config", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 10efe36c980643..eddb522eeab35c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,7 +19,10 @@ path = "src/main.rs" [features] default = ["remote-wallet-hidraw"] agave-unstable-api = [] -dev-context-only-utils = ["solana-faucet/dev-context-only-utils"] +dev-context-only-utils = [ + "solana-faucet/dev-context-only-utils", + "solana-client/dev-context-only-utils", +] remote-wallet-hidraw = ["solana-remote-wallet/linux-static-hidraw"] remote-wallet-libusb = ["solana-remote-wallet/linux-static-libusb"] @@ -27,7 +30,6 @@ remote-wallet-libusb = ["solana-remote-wallet/linux-static-libusb"] agave-feature-set = { workspace = true } agave-logger = { workspace = true } agave-syscalls = { workspace = true } -agave-votor-messages = { workspace = true } bincode = { workspace = true } bs58 = { workspace = true } clap = { workspace = true } @@ -48,7 +50,6 @@ serde_json = { workspace = true } solana-account = "=3.2.0" solana-account-decoder = { workspace = true } solana-address-lookup-table-interface = { workspace = true } -solana-bls-signatures = { workspace = true } solana-borsh = "=3.0.0" solana-clap-utils = { workspace = true } solana-cli-config = { workspace = true } @@ -65,7 +66,7 @@ solana-feature-gate-interface = { version = "=3.0.0", features = ["bincode"] } solana-fee-calculator = "=3.0.0" solana-fee-structure = "=3.0.0" solana-hash = "=3.1.0" -solana-instruction = { workspace = true } +solana-instruction = "3.1.0" solana-keypair = "=3.1.0" solana-loader-v3-interface = { version = "=6.1.0", features = ["bincode"] } solana-loader-v4-interface = "=3.1.0" @@ -117,6 +118,5 @@ solana-rpc = { workspace = true } solana-sha256-hasher = { workspace = true } solana-test-validator = { workspace = true } solana-tps-client = { workspace = true, features = ["dev-context-only-utils"] } -solana-vote-program = { workspace = true } tempfile = { workspace = true } test-case = { workspace = true } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 34f598df820c25..9c82ce59479041 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,6 +4,7 @@ use { program::*, program_v4::*, spend_utils::*, stake::*, validator_info::*, vote::*, wallet::*, }, clap::{crate_description, crate_name, value_t_or_exit, ArgMatches, Shell}, + log::*, num_traits::FromPrimitive, serde_json::{self, Value}, solana_clap_utils::{self, input_parsers::*, keypair::*}, @@ -34,9 +35,7 @@ use { }, solana_transaction::versioned::VersionedTransaction, solana_transaction_error::TransactionError, - solana_vote_program::vote_state::{ - VoteAuthorize, BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE, - }, + solana_vote_program::vote_state::VoteAuthorize, std::{ collections::HashMap, error, io::stdout, rc::Rc, str::FromStr, sync::Arc, time::Duration, }, @@ -408,24 +407,6 @@ pub enum CliCommand { fee_payer: SignerIndex, compute_unit_price: Option, }, - CreateVoteAccountV2 { - vote_account: SignerIndex, - seed: Option, - identity_account: SignerIndex, - authorized_voter: Option, - bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], - bls_proof_of_possession: [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], - authorized_withdrawer: Pubkey, - sign_only: bool, - dump_transaction_message: bool, - blockhash_query: BlockhashQuery, - nonce_account: Option, - nonce_authority: SignerIndex, - memo: Option, - fee_payer: SignerIndex, - compute_unit_price: Option, - inflation_rewards_commission_bps: u16, - }, // Wallet Commands Address, Airdrop { @@ -777,9 +758,6 @@ pub fn parse_command( ("create-vote-account", Some(matches)) => { parse_create_vote_account(matches, default_signer, wallet_manager) } - ("create-vote-account-with-bls", Some(matches)) => { - parse_create_vote_account_with_bls(matches, default_signer, wallet_manager) - } ("vote-update-validator", Some(matches)) => { parse_vote_update_validator(matches, default_signer, wallet_manager) } @@ -793,9 +771,6 @@ pub fn parse_command( VoteAuthorize::Voter, !CHECKED, ), - ("vote-authorize-voter-with-bls", Some(matches)) => { - parse_vote_authorize_with_bls(matches, default_signer, wallet_manager, !CHECKED) - } ("vote-authorize-withdrawer", Some(matches)) => parse_vote_authorize( matches, default_signer, @@ -810,9 +785,6 @@ pub fn parse_command( VoteAuthorize::Voter, CHECKED, ), - ("vote-authorize-voter-checked-with-bls", Some(matches)) => { - parse_vote_authorize_with_bls(matches, default_signer, wallet_manager, CHECKED) - } ("vote-authorize-withdrawer-checked", Some(matches)) => parse_vote_authorize( matches, default_signer, @@ -1624,46 +1596,6 @@ pub async fn process_command(config: &CliConfig<'_>) -> ProcessResult { ) .await } - CliCommand::CreateVoteAccountV2 { - vote_account, - seed, - identity_account, - authorized_voter, - authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, - sign_only, - dump_transaction_message, - blockhash_query, - nonce_account, - nonce_authority, - memo, - fee_payer, - compute_unit_price, - inflation_rewards_commission_bps, - } => { - process_create_vote_account_v2( - &rpc_client, - config, - *vote_account, - seed, - *identity_account, - authorized_voter, - *authorized_withdrawer, - *bls_pubkey, - *bls_proof_of_possession, - *sign_only, - *dump_transaction_message, - blockhash_query, - nonce_account.as_ref(), - *nonce_authority, - memo.as_ref(), - *fee_payer, - *compute_unit_price, - *inflation_rewards_commission_bps, - ) - .await - } CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, use_lamports_unit, diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 4b5ce97ae9758b..12b7bb9159cda8 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -13,14 +13,8 @@ use { spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, stake::check_current_authority, }, - agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED, clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}, solana_account::Account, - solana_bls_signatures::{ - keypair::Keypair as BLSKeypair, - proof_of_possession::ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, - pubkey::PubkeyCompressed as BLSPubkeyCompressed, - }, solana_clap_utils::{ compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG}, fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, @@ -32,14 +26,11 @@ use { offline::*, }, solana_cli_output::{ - display::build_balance_message, return_signers_data, return_signers_with_config, - CliEpochVotingHistory, CliLandedVote, CliSignOnlyDataWithBLS, CliVoteAccount, - ReturnSignersConfig, + display::build_balance_message, return_signers_with_config, CliEpochVotingHistory, + CliLandedVote, CliVoteAccount, ReturnSignersConfig, }, solana_commitment_config::CommitmentConfig, solana_feature_gate_interface::from_account, - solana_instruction::Instruction, - solana_keypair::Signer, solana_message::Message, solana_pubkey::Pubkey, solana_remote_wallet::remote_wallet::RemoteWalletManager, @@ -51,12 +42,7 @@ use { solana_vote_program::{ vote_error::VoteError, vote_instruction::{self, withdraw, CreateVoteAccountConfig}, - vote_state::{ - create_bls_proof_of_possession, verify_bls_proof_of_possession, VoteAuthorize, - VoteInit, VoteInitV2, VoteStateV4, VoterWithBLSArgs, - BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE, - VOTE_CREDITS_MAXIMUM_PER_SLOT, - }, + vote_state::{VoteAuthorize, VoteInit, VoteStateV4, VOTE_CREDITS_MAXIMUM_PER_SLOT}, }, std::rc::Rc, }; @@ -137,79 +123,6 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) - .subcommand( - SubCommand::with_name("create-vote-account-with-bls") - .about("Create a vote account with BLS pubkey") - .arg( - Arg::with_name("vote_account") - .index(1) - .value_name("ACCOUNT_KEYPAIR") - .takes_value(true) - .required(true) - .validator(is_valid_signer) - .help("Vote account keypair to create"), - ) - .arg( - Arg::with_name("identity_account") - .index(2) - .value_name("IDENTITY_KEYPAIR") - .takes_value(true) - .required(true) - .validator(is_valid_signer) - .help("Keypair of validator that will vote with this account"), - ) - .arg(pubkey!( - Arg::with_name("authorized_withdrawer") - .index(3) - .value_name("WITHDRAWER_PUBKEY") - .takes_value(true) - .required(true) - .long("authorized-withdrawer"), - "Authorized withdrawer." - )) - .arg( - Arg::with_name("inflation_rewards_commission_bps") - .long("inflation_rewards_commission_bps") - .value_name("INFLATION_REWARDS_COMMISSION_BPS") - .takes_value(true) - .default_value("0") - .help("The commission taken on inflation rewards"), - ) - .arg( - Arg::with_name("authorized_voter") - .index(4) - .value_name("AUTHORIZED_VOTER_KEYPAIR") - .takes_value(true) - .required(false) - .validator(is_valid_signer) - .help("Authorized voter [default: validator identity pubkey]."), - ) - .arg( - Arg::with_name("allow_unsafe_authorized_withdrawer") - .long("allow-unsafe-authorized-withdrawer") - .takes_value(false) - .help( - "Allow an authorized withdrawer pubkey to be identical to the \ - validator identity account pubkey or vote account pubkey, which is \ - normally an unsafe configuration and should be avoided.", - ), - ) - .arg( - Arg::with_name("seed") - .long("seed") - .value_name("STRING") - .takes_value(true) - .help( - "Seed for address generation; if specified, the resulting account \ - will be at a derived address of the VOTE ACCOUNT pubkey", - ), - ) - .offline_args() - .nonce_args(false) - .arg(fee_payer_arg()) - .arg(memo_arg()) - .arg(compute_unit_price_arg()), - ) .subcommand( SubCommand::with_name("vote-authorize-voter") .about("Authorize a new vote signing keypair for the given vote account") @@ -241,57 +154,6 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) - .subcommand( - SubCommand::with_name("vote-authorize-voter-with-bls") - .about( - "Authorize a new vote signing keypair for the given vote account, update BLS \ - pubkey", - ) - .arg(pubkey!( - Arg::with_name("vote_account_pubkey") - .index(1) - .value_name("VOTE_ACCOUNT_ADDRESS") - .required(true), - "Vote account in which to set the authorized voter." - )) - .arg( - Arg::with_name("authorized") - .index(2) - .value_name("AUTHORIZED_KEYPAIR") - .required(true) - .validator(is_valid_signer) - .help("Current authorized vote signer."), - ) - .arg(pubkey!( - Arg::with_name("new_authorized") - .index(3) - .value_name("NEW_AUTHORIZED_PUBKEY_OR_KEYPAIR") - .required(true), - "New authorized vote signer. pubkey if BLS pubkey and proof of possession are \ - provided, otherwise keypair." - )) - .arg( - Arg::with_name("bls_pubkey") - .long("bls-pubkey") - .value_name("BLS_PUBKEY_COMPRESSED") - .takes_value(true) - .required(false) - .help("New BLS public key in compressed form"), - ) - .arg( - Arg::with_name("bls_proof_of_possession") - .long("bls-proof-of-possession") - .value_name("BLS_PROOF_OF_POSSESSION_COMPRESSED") - .takes_value(true) - .required(false) - .help("New BLS proof of possession in compressed form"), - ) - .offline_args() - .nonce_args(false) - .arg(fee_payer_arg()) - .arg(memo_arg()) - .arg(compute_unit_price_arg()), - ) .subcommand( SubCommand::with_name("vote-authorize-withdrawer") .about("Authorize a new withdraw signing keypair for the given vote account") @@ -358,41 +220,6 @@ impl VoteSubCommands for App<'_, '_> { .arg(memo_arg()) .arg(compute_unit_price_arg()), ) - .subcommand( - SubCommand::with_name("vote-authorize-voter-checked-with-bls") - .about( - "Authorize a new vote signing keypair for the given vote account, checking \ - the new authority as a signer, also update BLS pubkey", - ) - .arg(pubkey!( - Arg::with_name("vote_account_pubkey") - .index(1) - .value_name("VOTE_ACCOUNT_ADDRESS") - .required(true), - "Vote account in which to set the authorized voter." - )) - .arg( - Arg::with_name("authorized") - .index(2) - .value_name("AUTHORIZED_KEYPAIR") - .required(true) - .validator(is_valid_signer) - .help("Current authorized vote signer."), - ) - .arg( - Arg::with_name("new_authorized") - .index(3) - .value_name("NEW_AUTHORIZED_KEYPAIR") - .required(true) - .validator(is_valid_signer) - .help("New authorized vote signer."), - ) - .offline_args() - .nonce_args(false) - .arg(fee_payer_arg()) - .arg(memo_arg()) - .arg(compute_unit_price_arg()), - ) .subcommand( SubCommand::with_name("vote-authorize-withdrawer-checked") .about( @@ -622,17 +449,6 @@ impl VoteSubCommands for App<'_, '_> { } } -fn generate_bls_pubkey_and_proof_of_possession( - vote_account_pubkey: &Pubkey, - keypair: &dyn Signer, -) -> ( - [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], - [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], -) { - let bls_keypair = BLSKeypair::derive_from_signer(keypair, BLS_KEYPAIR_DERIVE_SEED).unwrap(); - create_bls_proof_of_possession(vote_account_pubkey, &bls_keypair) -} - pub fn parse_create_vote_account( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -702,88 +518,6 @@ pub fn parse_create_vote_account( }) } -pub fn parse_create_vote_account_with_bls( - matches: &ArgMatches<'_>, - default_signer: &DefaultSigner, - wallet_manager: &mut Option>, -) -> Result { - let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?; - let seed = matches.value_of("seed").map(|s| s.to_string()); - let (identity_account, identity_pubkey) = - signer_of(matches, "identity_account", wallet_manager)?; - let (authorized_voter_keypair, authorized_voter) = - signer_of(matches, "authorized_voter", wallet_manager)?; - let authorized_withdrawer = - pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap(); - let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer"); - let sign_only = matches.is_present(SIGN_ONLY_ARG.name); - let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); - let blockhash_query = BlockhashQuery::new_from_matches(matches); - let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?; - let memo = matches.value_of(MEMO_ARG.name).map(String::from); - let (nonce_authority, nonce_authority_pubkey) = - signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; - let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; - let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name); - let inflation_rewards_commission_bps = - value_of(matches, "inflation_rewards_commission_bps").unwrap_or(0); - - let authorized_voter_keypair: &dyn Signer = authorized_voter_keypair - .as_deref() - .or(identity_account.as_deref()) - .unwrap(); - let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = - generate_bls_pubkey_and_proof_of_possession( - &vote_account_pubkey.unwrap(), - authorized_voter_keypair, - ); - if !allow_unsafe { - if authorized_withdrawer == vote_account_pubkey.unwrap() { - return Err(CliError::BadParameter( - "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \ - configuration" - .to_owned(), - )); - } - if authorized_withdrawer == identity_pubkey.unwrap() { - return Err(CliError::BadParameter( - "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \ - configuration" - .to_owned(), - )); - } - } - - let mut bulk_signers = vec![fee_payer, vote_account, identity_account]; - if nonce_account.is_some() { - bulk_signers.push(nonce_authority); - } - let signer_info = - default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; - - Ok(CliCommandInfo { - command: CliCommand::CreateVoteAccountV2 { - vote_account: signer_info.index_of(vote_account_pubkey).unwrap(), - seed, - identity_account: signer_info.index_of(identity_pubkey).unwrap(), - authorized_voter, - bls_pubkey: bls_pubkey_compressed_bytes, - bls_proof_of_possession: bls_proof_of_possession_compressed_bytes, - authorized_withdrawer, - sign_only, - dump_transaction_message, - blockhash_query, - nonce_account, - nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), - memo, - fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), - compute_unit_price, - inflation_rewards_commission_bps, - }, - signers: signer_info.signers, - }) -} - pub fn parse_vote_authorize( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -845,106 +579,6 @@ pub fn parse_vote_authorize( }) } -pub fn parse_vote_authorize_with_bls( - matches: &ArgMatches<'_>, - default_signer: &DefaultSigner, - wallet_manager: &mut Option>, - checked: bool, -) -> Result { - let vote_account_pubkey = - pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); - let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?; - let bls_pubkey = bls_pubkey_of(matches, "bls_pubkey"); - let bls_proof_of_possession = bls_proof_of_possession_of(matches, "bls_proof_of_possession"); - // If both BLS pubkey and proof of possession are provided, use them directly, otherwise need to derive from the new authorized signer - if bls_pubkey.is_some() ^ bls_proof_of_possession.is_some() { - return Err(CliError::BadParameter( - "Both BLS pubkey and proof of possession must be provided together".to_string(), - )); - } - let ( - bls_pubkey_compressed_bytes, - bls_proof_of_possession_compressed_bytes, - new_authorized_pubkey, - ) = if let Some(bls_pubkey) = bls_pubkey { - (bls_pubkey.0, bls_proof_of_possession.unwrap().0, None) - } else { - let (new_authorized, new_authorized_pubkey) = - signer_of(matches, "new_authorized", wallet_manager)?; - let new_authorized_voter_keypair: &dyn Signer = new_authorized.as_deref().unwrap(); - let (bls_pubkey_compressed_bytes, bls_proof_of_possession_compressed_bytes) = - generate_bls_pubkey_and_proof_of_possession( - &vote_account_pubkey, - new_authorized_voter_keypair, - ); - ( - bls_pubkey_compressed_bytes, - bls_proof_of_possession_compressed_bytes, - new_authorized_pubkey, - ) - }; - - let sign_only = matches.is_present(SIGN_ONLY_ARG.name); - let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name); - let blockhash_query = BlockhashQuery::new_from_matches(matches); - let nonce_account = pubkey_of(matches, NONCE_ARG.name); - let memo = matches.value_of(MEMO_ARG.name).map(String::from); - let (nonce_authority, nonce_authority_pubkey) = - signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; - let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; - let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name); - - let mut bulk_signers = vec![fee_payer, authorized]; - - let vote_authorize = VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { - bls_pubkey: bls_pubkey_compressed_bytes, - bls_proof_of_possession: bls_proof_of_possession_compressed_bytes, - }); - - let new_authorized_pubkey = if checked { - let (new_authorized_signer, new_authorized_pubkey) = - signer_of(matches, "new_authorized", wallet_manager)?; - bulk_signers.push(new_authorized_signer); - new_authorized_pubkey.unwrap() - } else { - new_authorized_pubkey - .or_else(|| { - pubkey_of_signer(matches, "new_authorized", wallet_manager) - .ok() - .flatten() - }) - .unwrap() - }; - if nonce_account.is_some() { - bulk_signers.push(nonce_authority); - } - let signer_info = - default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; - - Ok(CliCommandInfo { - command: CliCommand::VoteAuthorize { - vote_account_pubkey, - new_authorized_pubkey, - vote_authorize, - sign_only, - dump_transaction_message, - blockhash_query, - nonce_account, - nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), - memo, - fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), - authorized: signer_info.index_of(authorized_pubkey).unwrap(), - new_authorized: if checked { - signer_info.index_of(Some(new_authorized_pubkey)) - } else { - None - }, - compute_unit_price, - }, - signers: signer_info.signers, - }) -} - pub fn parse_vote_update_validator( matches: &ArgMatches<'_>, default_signer: &DefaultSigner, @@ -1173,144 +807,6 @@ pub async fn process_create_vote_account( fee_payer: SignerIndex, compute_unit_price: Option, ) -> ProcessResult { - let build_instructions = - |(lamports, identity_pubkey, vote_account_pubkey, vote_account_address)| { - let vote_init = VoteInit { - node_pubkey: identity_pubkey, - authorized_voter: authorized_voter.unwrap_or(identity_pubkey), - authorized_withdrawer, - commission, - }; - let space = VoteStateV4::size_of() as u64; - let mut create_vote_account_config = CreateVoteAccountConfig { - space, - ..CreateVoteAccountConfig::default() - }; - let to = if let Some(seed) = seed { - create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); - &vote_account_address - } else { - &vote_account_pubkey - }; - - vote_instruction::create_account_with_config( - &config.signers[0].pubkey(), - to, - &vote_init, - lamports, - create_vote_account_config, - ) - }; - process_create_vote_account_internal( - rpc_client, - config, - vote_account, - seed, - identity_account, - sign_only, - dump_transaction_message, - blockhash_query, - nonce_account, - nonce_authority, - memo, - fee_payer, - compute_unit_price, - build_instructions, - ) - .await -} - -#[allow(clippy::too_many_arguments)] -pub async fn process_create_vote_account_v2( - rpc_client: &RpcClient, - config: &CliConfig<'_>, - vote_account: SignerIndex, - seed: &Option, - identity_account: SignerIndex, - authorized_voter: &Option, - authorized_withdrawer: Pubkey, - authorized_voter_bls_pubkey: [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], - authorized_voter_bls_proof_of_possession: [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], - sign_only: bool, - dump_transaction_message: bool, - blockhash_query: &BlockhashQuery, - nonce_account: Option<&Pubkey>, - nonce_authority: SignerIndex, - memo: Option<&String>, - fee_payer: SignerIndex, - compute_unit_price: Option, - inflation_rewards_commission_bps: u16, -) -> ProcessResult { - let build_instructions = - |(lamports, identity_pubkey, vote_account_pubkey, vote_account_address)| { - let vote_init_v2 = VoteInitV2 { - node_pubkey: identity_pubkey, - authorized_voter: authorized_voter.unwrap_or(identity_pubkey), - authorized_withdrawer, - authorized_voter_bls_pubkey, - authorized_voter_bls_proof_of_possession, - inflation_rewards_commission_bps, - ..VoteInitV2::default() - }; - let space = VoteStateV4::size_of() as u64; - let mut create_vote_account_config = CreateVoteAccountConfig { - space, - ..CreateVoteAccountConfig::default() - }; - let to = if let Some(seed) = seed { - create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); - &vote_account_address - } else { - &vote_account_pubkey - }; - - vote_instruction::create_account_with_config_v2( - &config.signers[0].pubkey(), - to, - &vote_init_v2, - lamports, - create_vote_account_config, - ) - }; - process_create_vote_account_internal( - rpc_client, - config, - vote_account, - seed, - identity_account, - sign_only, - dump_transaction_message, - blockhash_query, - nonce_account, - nonce_authority, - memo, - fee_payer, - compute_unit_price, - build_instructions, - ) - .await -} - -#[allow(clippy::too_many_arguments)] -async fn process_create_vote_account_internal( - rpc_client: &RpcClient, - config: &CliConfig<'_>, - vote_account: SignerIndex, - seed: &Option, - identity_account: SignerIndex, - sign_only: bool, - dump_transaction_message: bool, - blockhash_query: &BlockhashQuery, - nonce_account: Option<&Pubkey>, - nonce_authority: SignerIndex, - memo: Option<&String>, - fee_payer: SignerIndex, - compute_unit_price: Option, - build_instructions: F, -) -> ProcessResult -where - F: Fn((u64, Pubkey, Pubkey, Pubkey)) -> Vec, -{ let vote_account = config.signers[vote_account]; let vote_account_pubkey = vote_account.pubkey(); let vote_account_address = if let Some(seed) = seed { @@ -1338,24 +834,43 @@ where let fee_payer = config.signers[fee_payer]; let nonce_authority = config.signers[nonce_authority]; + let space = VoteStateV4::size_of() as u64; let compute_unit_limit = match blockhash_query { BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default, BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated, }; - let build_message = |lamports| { - let ixs = build_instructions(( + let vote_init = VoteInit { + node_pubkey: identity_pubkey, + authorized_voter: authorized_voter.unwrap_or(identity_pubkey), + authorized_withdrawer, + commission, + }; + let mut create_vote_account_config = CreateVoteAccountConfig { + space, + ..CreateVoteAccountConfig::default() + }; + let to = if let Some(seed) = seed { + create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed)); + &vote_account_address + } else { + &vote_account_pubkey + }; + + let ixs = vote_instruction::create_account_with_config( + &config.signers[0].pubkey(), + to, + &vote_init, lamports, - identity_pubkey, - vote_account_pubkey, - vote_account_address, - )) + create_vote_account_config, + ) .with_memo(memo) .with_compute_unit_config(&ComputeUnitConfig { compute_unit_price, compute_unit_limit, }); + if let Some(nonce_account) = &nonce_account { Message::new_with_nonce( ixs, @@ -1494,46 +1009,21 @@ pub async fn process_vote_authorize( } } } - VoteAuthorize::Withdrawer => { - check_unique_pubkeys( - (&authorized.pubkey(), "authorized_account".to_string()), - (new_authorized_pubkey, "new_authorized_pubkey".to_string()), - )?; - if let Some(vote_state) = vote_state { - check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())? - } - } - VoteAuthorize::VoterWithBLS(args) => { - if let Some(vote_state) = vote_state { - let current_epoch = rpc_client.get_epoch_info().await?.epoch; - let current_authorized_voter = vote_state - .authorized_voters - .get_authorized_voter(current_epoch) - .ok_or_else(|| { - CliError::RpcRequestError( - "Invalid vote account state; no authorized voters found".to_string(), - ) - })?; - check_current_authority( - &[current_authorized_voter, vote_state.authorized_withdrawer], - &authorized.pubkey(), - )?; - if let Some(signer) = new_authorized_signer { - if signer.is_interactive() { - return Err(CliError::BadParameter(format!( - "invalid new authorized vote signer {new_authorized_pubkey:?}. \ - Interactive vote signers not supported" - )) - .into()); - } - } - verify_bls_proof_of_possession( - vote_account_pubkey, - &args.bls_pubkey, - &args.bls_proof_of_possession, - )?; + VoteAuthorize::Withdrawer => { + check_unique_pubkeys( + (&authorized.pubkey(), "authorized_account".to_string()), + (new_authorized_pubkey, "new_authorized_pubkey".to_string()), + )?; + if let Some(vote_state) = vote_state { + check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())? } } + VoteAuthorize::VoterWithBLS(_) => { + return Err(CliError::BadParameter( + "VoterWithBLS authorization not yet supported".to_string(), + ) + .into()); + } } let vote_ix = if new_authorized_signer.is_some() { @@ -1585,31 +1075,13 @@ pub async fn process_vote_authorize( if sign_only { tx.try_partial_sign(&config.signers, recent_blockhash)?; - if let VoteAuthorize::VoterWithBLS(args) = &vote_authorize { - let base = return_signers_data( - &tx, - &ReturnSignersConfig { - dump_transaction_message, - }, - ); - let bls_pubkey: BLSPubkeyCompressed = BLSPubkeyCompressed(args.bls_pubkey); - let bls_proof_of_possession: BLSProofOfPossessionCompressed = - BLSProofOfPossessionCompressed(args.bls_proof_of_possession); - let output = CliSignOnlyDataWithBLS { - base, - bls_pubkey: bls_pubkey.to_string(), - bls_proof_of_possession: bls_proof_of_possession.to_string(), - }; - Ok(config.output_format.formatted_string(&output)) - } else { - return_signers_with_config( - &tx, - &config.output_format, - &ReturnSignersConfig { - dump_transaction_message, - }, - ) - } + return_signers_with_config( + &tx, + &config.output_format, + &ReturnSignersConfig { + dump_transaction_message, + }, + ) } else { tx.try_sign(&config.signers, recent_blockhash)?; if let Some(nonce_account) = &nonce_account { @@ -2157,7 +1629,6 @@ mod tests { solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source, solana_signer::Signer, tempfile::NamedTempFile, - test_case::test_case, }; fn make_tmp_file() -> (String, NamedTempFile) { @@ -2165,9 +1636,8 @@ mod tests { (String::from(tmp_file.path().to_str().unwrap()), tmp_file) } - #[test_case(true; "With BLS")] - #[test_case(false; "Without BLS")] - fn test_parse_vote_authorize(with_bls: bool) { + #[test] + fn test_parse_command() { let test_commands = get_clap_app("test", "desc", "version"); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); @@ -2183,54 +1653,25 @@ mod tests { write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); let default_signer = DefaultSigner::new("", &default_keypair_file); - let (keypair2_file, mut tmp_file) = make_tmp_file(); - write_keypair(&keypair2, tmp_file.as_file_mut()).unwrap(); - let blockhash = Hash::default(); let blockhash_string = format!("{blockhash}"); let nonce_account = Pubkey::new_unique(); - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&pubkey, &keypair2); - let vote_authorize = if with_bls { - VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { - bls_pubkey, - bls_proof_of_possession, - }) - } else { - VoteAuthorize::Voter - }; - let test_authorize_voter = if with_bls { - let bls_pubkey_compressed = BLSPubkeyCompressed(bls_pubkey); - let bls_proof_of_possession_compressed = - BLSProofOfPossessionCompressed(bls_proof_of_possession); - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter-with-bls", - &pubkey_string, - &default_keypair_file, - &pubkey2_string, - "--bls-pubkey", - &bls_pubkey_compressed.to_string(), - "--bls-proof-of-possession", - &bls_proof_of_possession_compressed.to_string(), - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &default_keypair_file, - &pubkey2_string, - ]) - }; + // Test VoteAuthorize SubCommand + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &default_keypair_file, + &pubkey2_string, + ]); assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -2250,31 +1691,20 @@ mod tests { let (authorized_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap(); - let test_authorize_voter = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter-with-bls", - &pubkey_string, - &authorized_keypair_file, - &keypair2_file, - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair_file, - &pubkey2_string, - ]) - }; - + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair_file, + &pubkey2_string, + ]); assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -2293,36 +1723,23 @@ mod tests { } ); - let test_authorize_voter = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter-with-bls", - &pubkey_string, - &authorized_keypair_file, - &keypair2_file, - "--blockhash", - &blockhash_string, - "--sign-only", - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair_file, - &pubkey2_string, - "--blockhash", - &blockhash_string, - "--sign-only", - ]) - }; + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair_file, + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--sign-only", + ]); assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::Static(blockhash), @@ -2343,61 +1760,32 @@ mod tests { let authorized_sig = authorized_keypair.sign_message(&[0u8]); let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig); - let test_authorize_voter = if with_bls { - let bls_pubkey_compressed = BLSPubkeyCompressed(bls_pubkey); - let bls_proof_of_possession_compressed = - BLSProofOfPossessionCompressed(bls_proof_of_possession); - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter-with-bls", - &pubkey_string, - &authorized_keypair.pubkey().to_string(), - &pubkey2_string, - "--blockhash", - &blockhash_string, - "--signer", - &authorized_signer, - "--signer", - &signer2, - "--fee-payer", - &pubkey2_string, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - "--bls-pubkey", - &bls_pubkey_compressed.to_string(), - "--bls-proof-of-possession", - &bls_proof_of_possession_compressed.to_string(), - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "vote-authorize-voter", - &pubkey_string, - &authorized_keypair.pubkey().to_string(), - &pubkey2_string, - "--blockhash", - &blockhash_string, - "--signer", - &authorized_signer, - "--signer", - &signer2, - "--fee-payer", - &pubkey2_string, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - ]) - }; + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ + "test", + "vote-authorize-voter", + &pubkey_string, + &authorized_keypair.pubkey().to_string(), + &pubkey2_string, + "--blockhash", + &blockhash_string, + "--signer", + &authorized_signer, + "--signer", + &signer2, + "--fee-payer", + &pubkey2_string, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]); assert_eq!( parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Validated( @@ -2421,47 +1809,15 @@ mod tests { ], } ); - } - - #[test_case(true; "With BLS")] - #[test_case(false; "Without BLS")] - fn test_parse_vote_authorize_checked(with_bls: bool) { - let test_commands = get_clap_app("test", "desc", "version"); - let keypair = Keypair::new(); - let pubkey = keypair.pubkey(); - let pubkey_string = pubkey.to_string(); - - let default_keypair = Keypair::new(); - let (default_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); - let default_signer = DefaultSigner::new("", &default_keypair_file); + // Test checked VoteAuthorize SubCommand let (voter_keypair_file, mut tmp_file) = make_tmp_file(); let voter_keypair = Keypair::new(); write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap(); - let authorized_keypair = Keypair::new(); - let (authorized_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap(); - - let vote_authorize = if with_bls { - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&pubkey, &voter_keypair); - VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { - bls_pubkey, - bls_proof_of_possession, - }) - } else { - VoteAuthorize::Voter - }; - let command = if with_bls { - "vote-authorize-voter-checked-with-bls" - } else { - "vote-authorize-voter-checked" - }; let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - &command, + "vote-authorize-voter-checked", &pubkey_string, &default_keypair_file, &voter_keypair_file, @@ -2472,7 +1828,7 @@ mod tests { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -2493,7 +1849,7 @@ mod tests { let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - &command, + "vote-authorize-voter-checked", &pubkey_string, &authorized_keypair_file, &voter_keypair_file, @@ -2504,7 +1860,7 @@ mod tests { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: voter_keypair.pubkey(), - vote_authorize, + vote_authorize: VoteAuthorize::Voter, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -2526,33 +1882,14 @@ mod tests { let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - &command, + "vote-authorize-voter-checked", &pubkey_string, &authorized_keypair_file, - &voter_keypair.pubkey().to_string(), + &pubkey2_string, ]); assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err()); - } - - #[test_case(true; "With BLS")] - #[test_case(false; "Without BLS")] - fn test_parse_vote_create_vote_account(with_bls: bool) { - let test_commands = get_clap_app("test", "desc", "version"); - - let keypair2 = Keypair::new(); - let pubkey2 = keypair2.pubkey(); - let pubkey2_string = pubkey2.to_string(); - let sig2 = keypair2.sign_message(&[0u8]); - let signer2 = format!("{}={}", keypair2.pubkey(), sig2); - - let default_keypair = Keypair::new(); - let (default_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); - let default_signer = DefaultSigner::new("", &default_keypair_file); - - let blockhash = Hash::default(); - let blockhash_string = format!("{blockhash}"); + // Test CreateVoteAccount SubCommand let (identity_keypair_file, mut tmp_file) = make_tmp_file(); let identity_keypair = Keypair::new(); let authorized_withdrawer = Keypair::new().pubkey(); @@ -2561,73 +1898,34 @@ mod tests { let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let nonce_account = Pubkey::new_unique(); - - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &identity_keypair); - - let test_create_vote_account = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--inflation_rewards_commission_bps", - "10", - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--commission", - "10", - ]) - }; - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - bls_pubkey, - bls_proof_of_possession, - authorized_withdrawer, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - inflation_rewards_commission_bps: 10, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--commission", + "10", + ]); assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2636,64 +1934,32 @@ mod tests { } ); - let test_create_vote_account2 = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - ]) - }; - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, - inflation_rewards_commission_bps: 0, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + ]); assert_eq!( parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2702,78 +1968,39 @@ mod tests { } ); - let test_create_vote_account = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--inflation_rewards_commission_bps", - "10", - "--blockhash", - &blockhash_string, - "--sign-only", - "--fee-payer", - &default_keypair.pubkey().to_string(), - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--commission", - "10", - "--blockhash", - &blockhash_string, - "--sign-only", - "--fee-payer", - &default_keypair.pubkey().to_string(), - ]) - }; - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, - inflation_rewards_commission_bps: 10, - sign_only: true, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Static(blockhash), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: true, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Static(blockhash), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--sign-only", + "--fee-payer", + &default_keypair.pubkey().to_string(), + ]); assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Static(blockhash), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2784,98 +2011,49 @@ mod tests { let identity_sig = identity_keypair.sign_message(&[0u8]); let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig); - let test_create_vote_account = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--inflation_rewards_commission_bps", - "10", - "--blockhash", - &blockhash_string, - "--signer", - &identity_signer, - "--signer", - &signer2, - "--fee-payer", - &default_keypair_file, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair.pubkey().to_string(), - &authorized_withdrawer.to_string(), - "--commission", - "10", - "--blockhash", - &blockhash_string, - "--signer", - &identity_signer, - "--signer", - &signer2, - "--fee-payer", - &default_keypair_file, - "--nonce", - &nonce_account.to_string(), - "--nonce-authority", - &pubkey2_string, - ]) - }; - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, - inflation_rewards_commission_bps: 10, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Validated( - Source::NonceAccount(nonce_account), - blockhash, - ), - nonce_account: Some(nonce_account), - nonce_authority: 3, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer, - commission: 10, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Validated( - Source::NonceAccount(nonce_account), - blockhash, - ), - nonce_account: Some(nonce_account), - nonce_authority: 3, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair.pubkey().to_string(), + &authorized_withdrawer.to_string(), + "--commission", + "10", + "--blockhash", + &blockhash_string, + "--signer", + &identity_signer, + "--signer", + &signer2, + "--fee-payer", + &default_keypair_file, + "--nonce", + &nonce_account.to_string(), + "--nonce-authority", + &pubkey2_string, + ]); assert_eq!( parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer, + commission: 10, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Validated( + Source::NonceAccount(nonce_account), + blockhash + ), + nonce_account: Some(nonce_account), + nonce_authority: 3, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2886,77 +2064,39 @@ mod tests { ); // test init with an authed voter - let authed_keypair = Keypair::new(); - let authed = authed_keypair.pubkey(); - let (authed_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&authed_keypair, tmp_file.as_file_mut()).unwrap(); + let authed = solana_pubkey::new_rand(); let (keypair_file, mut tmp_file) = make_tmp_file(); let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &authed_keypair); - - let test_create_vote_account3 = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - &authed_keypair_file, - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &authorized_withdrawer.to_string(), - "--authorized-voter", - &authed.to_string(), - ]) - }; - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: Some(authed), - authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, - inflation_rewards_commission_bps: 0, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: Some(authed), - authorized_withdrawer, - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + + let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &authorized_withdrawer.to_string(), + "--authorized-voter", + &authed.to_string(), + ]); assert_eq!( parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: Some(authed), + authorized_withdrawer, + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(keypair), @@ -2969,68 +2109,33 @@ mod tests { let keypair = Keypair::new(); write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); // succeed even though withdrawer unsafe (because forcefully allowed) - let test_create_vote_account4 = if with_bls { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account-with-bls", - &keypair_file, - &identity_keypair_file, - &identity_keypair_file, - "--allow-unsafe-authorized-withdrawer", - ]) - } else { - test_commands.clone().get_matches_from(vec![ - "test", - "create-vote-account", - &keypair_file, - &identity_keypair_file, - &identity_keypair_file, - "--allow-unsafe-authorized-withdrawer", - ]) - }; - let (bls_pubkey, bls_proof_of_possession) = - generate_bls_pubkey_and_proof_of_possession(&keypair.pubkey(), &identity_keypair); - let expected_command = if with_bls { - CliCommand::CreateVoteAccountV2 { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer: identity_keypair.pubkey(), - inflation_rewards_commission_bps: 0, - bls_pubkey, - bls_proof_of_possession, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - } else { - CliCommand::CreateVoteAccount { - vote_account: 1, - seed: None, - identity_account: 2, - authorized_voter: None, - authorized_withdrawer: identity_keypair.pubkey(), - commission: 100, - sign_only: false, - dump_transaction_message: false, - blockhash_query: BlockhashQuery::Rpc(Source::Cluster), - nonce_account: None, - nonce_authority: 0, - memo: None, - fee_payer: 0, - compute_unit_price: None, - } - }; + let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &keypair_file, + &identity_keypair_file, + &identity_keypair_file, + "--allow-unsafe-authorized-withdrawer", + ]); assert_eq!( parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: expected_command, + command: CliCommand::CreateVoteAccount { + vote_account: 1, + seed: None, + identity_account: 2, + authorized_voter: None, + authorized_withdrawer: identity_keypair.pubkey(), + commission: 100, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::Rpc(Source::Cluster), + nonce_account: None, + nonce_authority: 0, + memo: None, + fee_payer: 0, + compute_unit_price: None, + }, signers: vec![ Box::new(read_keypair_file(&default_keypair_file).unwrap()), Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3038,29 +2143,6 @@ mod tests { ], } ); - } - - #[test] - fn test_parse_vote_other_commands() { - let test_commands = get_clap_app("test", "desc", "version"); - let keypair = Keypair::new(); - let pubkey = keypair.pubkey(); - let pubkey_string = pubkey.to_string(); - - let default_keypair = Keypair::new(); - let (default_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); - let default_signer = DefaultSigner::new("", &default_keypair_file); - - let blockhash = Hash::default(); - let blockhash_string = format!("{blockhash}"); - - let (identity_keypair_file, mut tmp_file) = make_tmp_file(); - let identity_keypair = Keypair::new(); - write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap(); - let (keypair_file, mut tmp_file) = make_tmp_file(); - let keypair = Keypair::new(); - write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); let test_update_validator = test_commands.clone().get_matches_from(vec![ "test", diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index e67a3e6c9d4490..b2bddcb59c4fc9 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -31,7 +31,6 @@ use { state::{Lockup, StakeAuthorize, StakeStateV2}, }, solana_test_validator::{TestValidator, TestValidatorGenesis}, - solana_vote_program::vote_state::create_bls_pubkey_and_proof_of_possession, test_case::test_case, }; @@ -78,17 +77,14 @@ async fn test_stake_delegation_force() { // Create vote account let vote_keypair = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_keypair.pubkey()); config.signers = vec![&default_signer, &vote_keypair]; - config.command = CliCommand::CreateVoteAccountV2 { + config.command = CliCommand::CreateVoteAccount { vote_account: 1, seed: None, identity_account: 0, authorized_voter: None, authorized_withdrawer, - bls_pubkey, - bls_proof_of_possession, + commission: 0, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -97,7 +93,6 @@ async fn test_stake_delegation_force() { memo: None, fee_payer: 0, compute_unit_price: None, - inflation_rewards_commission_bps: 0, }; process_command(&config).await.unwrap(); diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 96ebaf99a5c3f5..983ca318799d0c 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -16,9 +16,7 @@ use { solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery, solana_signer::{null_signer::NullSigner, Signer}, solana_test_validator::TestValidator, - solana_vote_program::vote_state::{ - create_bls_pubkey_and_proof_of_possession, VoteAuthorize, VoteStateV4, - }, + solana_vote_program::vote_state::{VoteAuthorize, VoteStateV4}, test_case::test_case, }; @@ -49,18 +47,15 @@ async fn test_vote_authorize_and_withdraw(compute_unit_price: Option) { // Create vote account let vote_account_keypair = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_account_keypair.pubkey()); let vote_account_pubkey = vote_account_keypair.pubkey(); config.signers = vec![&default_signer, &vote_account_keypair]; - config.command = CliCommand::CreateVoteAccountV2 { + config.command = CliCommand::CreateVoteAccount { vote_account: 1, seed: None, identity_account: 0, authorized_voter: None, - bls_pubkey, - bls_proof_of_possession, authorized_withdrawer: config.signers[0].pubkey(), + commission: 0, sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::Rpc(Source::Cluster), @@ -69,7 +64,6 @@ async fn test_vote_authorize_and_withdraw(compute_unit_price: Option) { memo: None, fee_payer: 0, compute_unit_price, - inflation_rewards_commission_bps: 0, }; process_command(&config).await.unwrap(); let vote_account = rpc_client From f186e760996e70fabe8748cd5ccec5a8b57b672c Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:59:04 -0800 Subject: [PATCH 10/17] Disable the feature in TestValidator for now and revert multinode-demo change. --- multinode-demo/validator.sh | 2 +- test-validator/src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index a8ab1de7ec1f82..de7fe2896c1752 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -319,7 +319,7 @@ setup_validator_accounts() { fi echo "Creating validator vote account" - wallet create-vote-account-with-bls "$vote_account" "$identity" "$authorized_withdrawer"|| return $? + wallet create-vote-account "$vote_account" "$identity" "$authorized_withdrawer"|| return $? fi echo "Validator vote account configured" diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index efc4c013e2151d..414984e9f40eb0 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -1050,7 +1050,10 @@ impl TestValidator { } for feature in feature_set { - genesis_utils::activate_feature(&mut genesis_config, feature); + // TODO: remove after cli change for bls_pubkey_management_in_vote_account is checked in + if feature != agave_feature_set::bls_pubkey_management_in_vote_account::id() { + genesis_utils::activate_feature(&mut genesis_config, feature); + } } let ledger_path = match &config.ledger_path { From 6060fa529744eeab3c8023f51fba55d8be1074c6 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:05:02 -0800 Subject: [PATCH 11/17] Revert cli related changes. --- clap-utils/src/input_parsers.rs | 16 +--------------- cli-output/src/cli_output.rs | 22 ---------------------- cli/src/cli.rs | 1 - 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index 774addb8f44890..3c239e2263e362 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -5,10 +5,7 @@ use { }, chrono::DateTime, clap::ArgMatches, - solana_bls_signatures::{ - ProofOfPossessionCompressed as BLSProofOfPossessionCompressed, Pubkey as BLSPubkey, - PubkeyCompressed as BLSPubkeyCompressed, - }, + solana_bls_signatures::{Pubkey as BLSPubkey, PubkeyCompressed as BLSPubkeyCompressed}, solana_clock::UnixTimestamp, solana_cluster_type::ClusterType, solana_commitment_config::CommitmentConfig, @@ -126,17 +123,6 @@ pub fn bls_pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option, name: &str) -> Option { - value_of(matches, name) -} - -pub fn bls_proof_of_possession_of( - matches: &ArgMatches<'_>, - name: &str, -) -> Option { - value_of(matches, name) -} - // Return pubkey/signature pairs for a string of the form pubkey=signature pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option> { matches.values_of(name).map(|values| { diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index dcfecc9305ba72..29e47453b26903 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -2018,28 +2018,6 @@ impl fmt::Display for CliSignOnlyData { } } -#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CliSignOnlyDataWithBLS { - #[serde(flatten)] - pub base: CliSignOnlyData, - pub bls_pubkey: String, - pub bls_proof_of_possession: String, -} - -impl QuietDisplay for CliSignOnlyDataWithBLS {} -impl VerboseDisplay for CliSignOnlyDataWithBLS {} - -impl fmt::Display for CliSignOnlyDataWithBLS { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f)?; - write!(f, "{}", self.base)?; - writeln_name_value(f, "BLS Public Key:", &self.bls_pubkey)?; - writeln_name_value(f, "BLS Proof of Possession:", &self.bls_proof_of_possession)?; - Ok(()) - } -} - #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CliSignature { diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 9c82ce59479041..244365254e1ba8 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,7 +4,6 @@ use { program::*, program_v4::*, spend_utils::*, stake::*, validator_info::*, vote::*, wallet::*, }, clap::{crate_description, crate_name, value_t_or_exit, ArgMatches, Shell}, - log::*, num_traits::FromPrimitive, serde_json::{self, Value}, solana_clap_utils::{self, input_parsers::*, keypair::*}, From 655d66503dfa07da0e85e1479b33e1914e83c656 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:06:58 -0800 Subject: [PATCH 12/17] Revert multinode-demo change for now. --- multinode-demo/validator.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index de7fe2896c1752..67ec8e281d7ad7 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -319,7 +319,7 @@ setup_validator_accounts() { fi echo "Creating validator vote account" - wallet create-vote-account "$vote_account" "$identity" "$authorized_withdrawer"|| return $? + wallet create-vote-account "$vote_account" "$identity" "$authorized_withdrawer" || return $? fi echo "Validator vote account configured" From b74ae0b33d20abf21ecaf5e1962b29d30b7fbd14 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:07:56 -0800 Subject: [PATCH 13/17] Revert more cli change. --- cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index eddb522eeab35c..d1dd03965fd706 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -66,7 +66,7 @@ solana-feature-gate-interface = { version = "=3.0.0", features = ["bincode"] } solana-fee-calculator = "=3.0.0" solana-fee-structure = "=3.0.0" solana-hash = "=3.1.0" -solana-instruction = "3.1.0" +solana-instruction = { workspace = true } solana-keypair = "=3.1.0" solana-loader-v3-interface = { version = "=6.1.0", features = ["bincode"] } solana-loader-v4-interface = "=3.1.0" From 4822297878d160bb5aa7803b4292c12677a34cd2 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:38:19 -0800 Subject: [PATCH 14/17] Fix local-net and deactivate_features test. --- runtime/src/genesis_utils.rs | 5 ++++- test-validator/src/lib.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index dfd8eafaf8d1d0..1535623945cbc6 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -291,7 +291,10 @@ pub fn activate_all_features(genesis_config: &mut GenesisConfig) { fn do_activate_all_features(genesis_config: &mut GenesisConfig) { // Activate all features at genesis in development mode for feature_id in FeatureSet::default().inactive() { - if IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id() { + if (IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id()) + // Skip bls_pubkey_management_in_vote_account feature activation until cli change is in place + && *feature_id + != agave_feature_set::bls_pubkey_management_in_vote_account::id() { activate_feature(genesis_config, *feature_id); } } diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 414984e9f40eb0..26c0c2c9852176 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -1561,6 +1561,7 @@ mod test { agave_feature_set::deprecate_rewards_sysvar::id(), agave_feature_set::disable_fees_sysvar::id(), alpenglow::id(), + agave_feature_set::bls_pubkey_management_in_vote_account::id(), ] .into_iter() .for_each(|feature| { From a39da3498b6e76925344fe2017828f730b7450eb Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:44:55 -0800 Subject: [PATCH 15/17] Make linter happy. --- runtime/src/genesis_utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 1535623945cbc6..a319df674b7476 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -294,7 +294,8 @@ fn do_activate_all_features(genesis_config: &mut Genes if (IS_ALPENGLOW || *feature_id != agave_feature_set::alpenglow::id()) // Skip bls_pubkey_management_in_vote_account feature activation until cli change is in place && *feature_id - != agave_feature_set::bls_pubkey_management_in_vote_account::id() { + != agave_feature_set::bls_pubkey_management_in_vote_account::id() + { activate_feature(genesis_config, *feature_id); } } From 68f6a5632dece5ce44c339e680fc160f7c014c11 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:07:25 -0800 Subject: [PATCH 16/17] Try revert more changes. --- ledger/src/staking_utils.rs | 39 +++++++++--------------------- local-cluster/src/local_cluster.rs | 12 +++------ program-test/tests/setup.rs | 12 +++------ rpc/Cargo.toml | 1 - rpc/src/rpc_pubsub.rs | 13 +++------- 5 files changed, 24 insertions(+), 53 deletions(-) diff --git a/ledger/src/staking_utils.rs b/ledger/src/staking_utils.rs index 2e2925720ba5c0..9928601eb9e33f 100644 --- a/ledger/src/staking_utils.rs +++ b/ledger/src/staking_utils.rs @@ -18,10 +18,7 @@ pub(crate) mod tests { solana_vote::vote_account::{VoteAccount, VoteAccounts}, solana_vote_program::{ vote_instruction, - vote_state::{ - create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4, - VoteStateVersions, - }, + vote_state::{VoteInit, VoteStateV4, VoteStateVersions}, }, std::sync::Arc, }; @@ -44,22 +41,17 @@ pub(crate) mod tests { bank.process_transaction(&tx).unwrap(); } - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); - process_instructions( bank, &[from_account, vote_account, validator_identity_account], - &vote_instruction::create_account_with_config_v2( + &vote_instruction::create_account_with_config( &from_account.pubkey(), &vote_pubkey, - &VoteInitV2 { + &VoteInit { node_pubkey: validator_identity_account.pubkey(), authorized_voter: vote_pubkey, authorized_withdrawer: vote_pubkey, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + commission: 0, }, amount, vote_instruction::CreateVoteAccountConfig { @@ -105,19 +97,15 @@ pub(crate) mod tests { let node1 = solana_pubkey::new_rand(); let vote_pubkey1 = solana_pubkey::new_rand(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey1); - // Node 1 has stake of 3 for i in 0..3 { stakes.push(( i, - VoteStateV4::new( - &VoteInitV2 { + VoteStateV4::new_with_defaults( + &vote_pubkey1, + &VoteInit { node_pubkey: node1, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + ..VoteInit::default() }, &Clock::default(), ), @@ -127,17 +115,14 @@ pub(crate) mod tests { // Node 1 has stake of 5 let node2 = solana_pubkey::new_rand(); let vote_pubkey2 = solana_pubkey::new_rand(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey2); stakes.push(( 5, - VoteStateV4::new( - &VoteInitV2 { + VoteStateV4::new_with_defaults( + &vote_pubkey2, + &VoteInit { node_pubkey: node2, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + ..VoteInit::default() }, &Clock::default(), ), diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 670f5ed868583b..2c4bf183117314 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -54,7 +54,7 @@ use { solana_transaction_error::TransportError, solana_vote_program::{ vote_instruction, - vote_state::{self, create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4}, + vote_state::{self, VoteInit, VoteStateV4}, }, std::{ collections::HashMap, @@ -1012,18 +1012,14 @@ impl LocalCluster { == 0 { // 1) Create vote account - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_account_pubkey); - let instructions = vote_instruction::create_account_with_config_v2( + let instructions = vote_instruction::create_account_with_config( &from_account.pubkey(), &vote_account_pubkey, - &VoteInitV2 { + &VoteInit { node_pubkey, authorized_voter: vote_account_pubkey, authorized_withdrawer: vote_account_pubkey, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + commission: 0, }, amount, vote_instruction::CreateVoteAccountConfig { diff --git a/program-test/tests/setup.rs b/program-test/tests/setup.rs index bb0ba4838ef184..3b6373e8ee9682 100644 --- a/program-test/tests/setup.rs +++ b/program-test/tests/setup.rs @@ -12,7 +12,7 @@ use { solana_transaction::Transaction, solana_vote_program::{ vote_instruction, - vote_state::{self, create_bls_pubkey_and_proof_of_possession, VoteInitV2, VoteStateV4}, + vote_state::{self, VoteInit, VoteStateV4}, }, }; @@ -56,18 +56,14 @@ pub async fn setup_vote(context: &mut ProgramTestContext) -> Pubkey { )); let vote_lamports = Rent::default().minimum_balance(VoteStateV4::size_of()); let vote_keypair = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_keypair.pubkey()); let user_keypair = Keypair::new(); - instructions.append(&mut vote_instruction::create_account_with_config_v2( + instructions.append(&mut vote_instruction::create_account_with_config( &context.payer.pubkey(), &vote_keypair.pubkey(), - &VoteInitV2 { + &VoteInit { node_pubkey: validator_keypair.pubkey(), authorized_voter: user_keypair.pubkey(), - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + ..VoteInit::default() }, vote_lamports, vote_instruction::CreateVoteAccountConfig { diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index c597e6e5e758c9..4f05b430ce9241 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -128,7 +128,6 @@ solana-sha256-hasher = { workspace = true } solana-stake-interface = { workspace = true } solana-svm-log-collector = { workspace = true } solana-vote-interface = { workspace = true } -solana-vote-program = { workspace = true } spl-pod = { workspace = true } symlink = { workspace = true } test-case = { workspace = true } diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index 0d6f3bb502ac2c..0e483f6e0a5f87 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -650,9 +650,8 @@ mod tests { solana_vote_interface::{ instruction::{self as vote_instruction, CreateVoteAccountConfig}, program as vote_program, - state::{Vote, VoteInitV2, VoteStateV4}, + state::{Vote, VoteInit, VoteStateV4}, }, - solana_vote_program::vote_state::create_bls_pubkey_and_proof_of_possession, std::{ sync::{ atomic::{AtomicBool, AtomicU64}, @@ -878,8 +877,6 @@ mod tests { let voter = Keypair::new(); let from = Keypair::new(); let vote_account = Keypair::new(); - let (bls_pubkey, bls_proof_of_possesssion) = - create_bls_pubkey_and_proof_of_possession(&vote_account.pubkey()); let bank = Bank::new_for_tests(&genesis_config); let blockhash = bank.last_blockhash(); let bank_forks = BankForks::new_rw_arc(bank); @@ -932,16 +929,14 @@ mod tests { 0, &system_program::id(), )]; - ixs.append(&mut vote_instruction::create_account_with_config_v2( + ixs.append(&mut vote_instruction::create_account_with_config( &from.pubkey(), &vote_account.pubkey(), - &VoteInitV2 { + &VoteInit { node_pubkey: validator.pubkey(), authorized_voter: voter.pubkey(), authorized_withdrawer: Pubkey::new_unique(), - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possesssion, - ..VoteInitV2::default() + ..VoteInit::default() }, vote_balance, CreateVoteAccountConfig { From ed371399f01acb416818b798cf0c546fc987af27 Mon Sep 17 00:00:00 2001 From: Wen <113942165+wen-coding@users.noreply.github.com> Date: Fri, 19 Dec 2025 00:24:48 -0800 Subject: [PATCH 17/17] Also revert bank change. --- runtime/src/bank/tests.rs | 99 +++++++++++++-------------------------- 1 file changed, 32 insertions(+), 67 deletions(-) diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 8cffcbc3e24b32..62caf8442067dd 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -63,7 +63,7 @@ use { solana_genesis_config::GenesisConfig, solana_hash::Hash, solana_instruction::{error::InstructionError, AccountMeta, Instruction}, - solana_keypair::Keypair, + solana_keypair::{keypair_from_seed, Keypair}, solana_loader_v3_interface::{ instruction::UpgradeableLoaderInstruction, state::UpgradeableLoaderState, }, @@ -113,13 +113,12 @@ use { sanitized::SanitizedTransaction, Transaction, TransactionVerificationMode, }, solana_transaction_error::{TransactionError, TransactionResult as Result}, - solana_vote_interface::state::{TowerSync, VoterWithBLSArgs}, + solana_vote_interface::state::TowerSync, solana_vote_program::{ vote_instruction, vote_state::{ - self, create_bls_pubkey_and_proof_of_possession, create_v4_account_with_authorized, - BlockTimestamp, VoteAuthorize, VoteInitV2, VoteStateV4, VoteStateVersions, - MAX_LOCKOUT_HISTORY, + self, create_v4_account_with_authorized, BlockTimestamp, VoteAuthorize, VoteInit, + VoteStateV4, VoteStateVersions, MAX_LOCKOUT_HISTORY, }, }, spl_generic_token::token, @@ -3233,19 +3232,14 @@ fn test_bank_vote_accounts() { // to have a vote account let vote_keypair = Keypair::new(); - let vote_pubkey = vote_keypair.pubkey(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); - let instructions = vote_instruction::create_account_with_config_v2( + let instructions = vote_instruction::create_account_with_config( &mint_keypair.pubkey(), - &vote_pubkey, - &VoteInitV2 { + &vote_keypair.pubkey(), + &VoteInit { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_pubkey, - authorized_withdrawer: vote_pubkey, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + authorized_voter: vote_keypair.pubkey(), + authorized_withdrawer: vote_keypair.pubkey(), + commission: 0, }, 10, vote_instruction::CreateVoteAccountConfig { @@ -3320,19 +3314,14 @@ fn test_bank_cloned_stake_delegations() { }; let vote_keypair = Keypair::new(); - let vote_pubkey = vote_keypair.pubkey(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); - let mut instructions = vote_instruction::create_account_with_config_v2( + let mut instructions = vote_instruction::create_account_with_config( &mint_keypair.pubkey(), - &vote_pubkey, - &VoteInitV2 { + &vote_keypair.pubkey(), + &VoteInit { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_pubkey, - authorized_withdrawer: vote_pubkey, - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + authorized_voter: vote_keypair.pubkey(), + authorized_withdrawer: vote_keypair.pubkey(), + commission: 0, }, vote_balance, vote_instruction::CreateVoteAccountConfig { @@ -3631,18 +3620,13 @@ fn test_add_builtin() { assert!(bank.get_account(&mock_vote_program_id()).is_some()); let mock_account = Keypair::new(); - let vote_pubkey = mock_account.pubkey(); let mock_validator_identity = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); - let mut instructions = vote_instruction::create_account_with_config_v2( + let mut instructions = vote_instruction::create_account_with_config( &mint_keypair.pubkey(), - &vote_pubkey, - &VoteInitV2 { + &mock_account.pubkey(), + &VoteInit { node_pubkey: mock_validator_identity.pubkey(), - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + ..VoteInit::default() }, 1, vote_instruction::CreateVoteAccountConfig { @@ -3683,18 +3667,13 @@ fn test_add_duplicate_static_program() { }); let mock_account = Keypair::new(); - let vote_pubkey = mock_account.pubkey(); let mock_validator_identity = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); - let instructions = vote_instruction::create_account_with_config_v2( + let instructions = vote_instruction::create_account_with_config( &mint_keypair.pubkey(), - &vote_pubkey, - &VoteInitV2 { + &mock_account.pubkey(), + &VoteInit { node_pubkey: mock_validator_identity.pubkey(), - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - ..VoteInitV2::default() + ..VoteInit::default() }, 1, vote_instruction::CreateVoteAccountConfig { @@ -8304,25 +8283,17 @@ fn test_vote_epoch_panic() { ); let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); - let vote_keypair = Keypair::new(); - let vote_pubkey = vote_keypair.pubkey(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&vote_pubkey); + let vote_keypair = keypair_from_seed(&[1u8; 32]).unwrap(); let mut setup_ixs = Vec::new(); - setup_ixs.extend(vote_instruction::create_account_with_config_v2( + setup_ixs.extend(vote_instruction::create_account_with_config( &mint_keypair.pubkey(), - &vote_pubkey, - &VoteInitV2 { + &vote_keypair.pubkey(), + &VoteInit { node_pubkey: mint_keypair.pubkey(), - authorized_voter: vote_pubkey, + authorized_voter: vote_keypair.pubkey(), authorized_withdrawer: mint_keypair.pubkey(), - authorized_voter_bls_pubkey: bls_pubkey, - authorized_voter_bls_proof_of_possession: bls_proof_of_possession, - inflation_rewards_commission_bps: 0, - inflation_rewards_collector: Pubkey::default(), - block_revenue_commission_bps: 0, - block_revenue_collector: Pubkey::default(), + commission: 0, }, 1_000_000_000, vote_instruction::CreateVoteAccountConfig { @@ -9910,10 +9881,7 @@ fn test_rent_state_changes_sysvars() { let validator_pubkey = Pubkey::new_unique(); let validator_stake_lamports = LAMPORTS_PER_SOL; - let validator_vote_account_keypair = Keypair::new(); - let (bls_pubkey, bls_proof_of_possession) = - create_bls_pubkey_and_proof_of_possession(&validator_vote_account_keypair.pubkey()); - let validator_vote_account_pubkey = validator_vote_account_keypair.pubkey(); + let validator_vote_account_pubkey = Pubkey::new_unique(); let validator_voting_keypair = Keypair::new(); let validator_vote_account = vote_state::create_v4_account_with_authorized( @@ -9946,10 +9914,7 @@ fn test_rent_state_changes_sysvars() { &validator_vote_account_pubkey, &validator_voting_keypair.pubkey(), &Pubkey::new_unique(), - VoteAuthorize::VoterWithBLS(VoterWithBLSArgs { - bls_pubkey, - bls_proof_of_possession, - }), + VoteAuthorize::Voter, )], Some(&mint_keypair.pubkey()), &[&mint_keypair, &validator_voting_keypair],