diff --git a/Cargo.lock b/Cargo.lock index 6fd670909488d9..c301c50014d7fe 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]] @@ -7488,9 +7488,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", @@ -8719,7 +8719,7 @@ checksum = "5396c179ca7d76f866b102eb3819ca3922bdaa33c9ada8005f4e98fd59ab65f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10553,7 +10553,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -11964,6 +11964,7 @@ dependencies = [ "serde", "solana-account", "solana-bincode", + "solana-bls-signatures", "solana-clock", "solana-epoch-schedule", "solana-fee-calculator", @@ -12197,7 +12198,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12209,7 +12210,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.111", + "syn 2.0.110", "thiserror 1.0.69", ] @@ -12474,9 +12475,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", @@ -12506,7 +12507,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12667,7 +12668,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12679,7 +12680,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "test-case-core", ] @@ -12724,7 +12725,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12735,7 +12736,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -12914,7 +12915,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13220,7 +13221,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13616,7 +13617,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -13746,7 +13747,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13823,7 +13824,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -13834,7 +13835,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14317,7 +14318,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -14338,7 +14339,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14358,7 +14359,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -14379,7 +14380,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -14401,7 +14402,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 b37b2ab72341c3..87c2decacc1078 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/dev-bins/Cargo.lock b/dev-bins/Cargo.lock index 8fa39b0de8411c..2fd4b4d2f54889 100644 --- a/dev-bins/Cargo.lock +++ b/dev-bins/Cargo.lock @@ -6446,9 +6446,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", @@ -9830,6 +9830,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 f2a4133c2fb260..2b0f5409347232 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -960,6 +960,7 @@ mod tests { 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, }; @@ -1438,7 +1439,10 @@ mod tests { ); } 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 diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 952bb77ba72287..3430022f2b371b 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -6280,9 +6280,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", @@ -10362,6 +10362,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..dc8557a6b37c6f 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,53 @@ 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_case(false ; "VoteStateV3")] #[test_case(true ; "VoteStateV4")] fn test_authorize_withdrawer(vote_state_v4_enabled: bool) { @@ -1213,6 +1869,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 +1880,7 @@ mod tests { // should pass let accounts = process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1239,6 +1897,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 +1914,7 @@ mod tests { .unwrap(); process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), @@ -1290,6 +1950,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 +1969,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 +1985,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 +1996,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 +2006,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 +2017,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 +2072,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 +2083,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 +2094,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 +2105,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 +2115,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 +2157,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 +2176,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 +2194,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 +2212,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 +2230,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 +2278,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 +2297,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 +2315,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 +2332,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 +2349,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 +2364,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 +2378,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 +2401,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 +2415,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 +2481,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 +2497,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 +2513,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 +2524,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 +2538,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 +2561,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 +2575,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 +2647,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 +2676,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 +2692,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 +2702,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state( &invalid_vote_state_pubkey(), &Pubkey::default(), @@ -1971,6 +2712,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 +2722,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &tower_sync( &invalid_vote_state_pubkey(), &Pubkey::default(), @@ -2025,6 +2768,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 +2811,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instructions[1].data, transaction_accounts, instructions[1].accounts.clone(), @@ -2089,11 +2834,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 +2850,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &vote_switch( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2113,6 +2861,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &authorize( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2123,6 +2872,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state( &Pubkey::default(), &Pubkey::default(), @@ -2133,6 +2883,7 @@ mod tests { process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_vote_state_switch( &Pubkey::default(), &Pubkey::default(), @@ -2143,6 +2894,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &compact_update_vote_state( &Pubkey::default(), &Pubkey::default(), @@ -2152,6 +2904,7 @@ mod tests { ); process_instruction_as_one_arg( vote_state_v4_enabled, + false, &compact_update_vote_state_switch( &Pubkey::default(), &Pubkey::default(), @@ -2162,11 +2915,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 +2933,7 @@ mod tests { process_instruction_as_one_arg( vote_state_v4_enabled, + false, &update_validator_identity( &Pubkey::new_unique(), &Pubkey::new_unique(), @@ -2187,12 +2943,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 +2961,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 +3005,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 +3046,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 +3090,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 +3169,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2423,6 +3221,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2479,6 +3278,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2521,6 +3321,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2564,6 +3365,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2601,6 +3403,7 @@ mod tests { process_instruction( vote_state_v4_enabled, + false, &instruction_data, transaction_accounts, instruction_accounts, @@ -2650,6 +3453,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..84be74dcae716d 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) } } @@ -1031,6 +1050,9 @@ mod tests { 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 +1115,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 +1127,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 +1150,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 +1161,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 +1224,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 +1238,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 +1247,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 +1281,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 +1331,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 +1357,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 +1424,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 +1485,7 @@ mod tests { &Pubkey::new_unique(), i, i + MAX_LEADER_SCHEDULE_EPOCH_OFFSET, + None, |_| Ok(()), ) .unwrap(); @@ -2194,4 +2217,65 @@ mod tests { assert_eq!(result, expected_result); } + + #[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()); + + // 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 48d74685f9fd88..c0e39b2b580db8 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,132 @@ 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 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(); + 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), + ); + + // 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()); + } } diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index dfd8eafaf8d1d0..a319df674b7476 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -291,7 +291,11 @@ 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/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/test-validator/src/lib.rs b/test-validator/src/lib.rs index efc4c013e2151d..26c0c2c9852176 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 { @@ -1558,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| { 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)]