瀏覽代碼

slashing: enshrine the slashing program (#4942)

* slashing: enshrine the slashing program

* pr feedback: fallback to `offset` for `end_offset`

* pr feedback: use source_buffer api, update program cache

* reuse stateless builtin migration code

* pr feedback: move build hash to CoreBpfMigrationConfig
Ashwin Sekar 7 月之前
父節點
當前提交
246300b2b6

+ 2 - 0
Cargo.lock

@@ -6951,6 +6951,7 @@ dependencies = [
  "agave-feature-set",
  "solana-bpf-loader-program",
  "solana-compute-budget-program",
+ "solana-hash",
  "solana-loader-v4-program",
  "solana-program-runtime",
  "solana-pubkey",
@@ -9692,6 +9693,7 @@ dependencies = [
  "solana-runtime-transaction",
  "solana-sdk",
  "solana-sdk-ids",
+ "solana-sha256-hasher",
  "solana-stake-program",
  "solana-svm",
  "solana-svm-callback",

+ 1 - 0
builtins/Cargo.toml

@@ -16,6 +16,7 @@ dev-context-only-utils = []
 agave-feature-set = { workspace = true }
 solana-bpf-loader-program = { workspace = true }
 solana-compute-budget-program = { workspace = true }
+solana-hash = { workspace = true }
 solana-loader-v4-program = { workspace = true }
 solana-program-runtime = { workspace = true }
 solana-pubkey = { workspace = true }

+ 4 - 1
builtins/src/core_bpf_migration.rs

@@ -1,4 +1,4 @@
-use solana_pubkey::Pubkey;
+use {solana_hash::Hash, solana_pubkey::Pubkey};
 
 /// Identifies the type of built-in program targeted for Core BPF migration.
 /// The type of target determines whether the program should have a program
@@ -32,6 +32,9 @@ pub struct CoreBpfMigrationConfig {
     pub feature_id: Pubkey,
     /// The type of target to replace.
     pub migration_target: CoreBpfMigrationTargetType,
+    /// If specified, the expected verifiable build hash of the bpf program.
+    /// This will be checked against the buffer account before migration.
+    pub verified_build_hash: Option<Hash>,
     /// Static message used to emit datapoint logging.
     /// This is used to identify the migration in the logs.
     /// Should be unique to the migration, ie:

+ 37 - 1
builtins/src/lib.rs

@@ -69,6 +69,7 @@ pub static BUILTINS: &[BuiltinPrototype] = &[
             source_buffer_address: buffer_accounts::stake_program::id(),
             upgrade_authority_address: None,
             feature_id: agave_feature_set::migrate_stake_program_to_core_bpf::id(),
+            verified_build_hash: None,
             migration_target: CoreBpfMigrationTargetType::Builtin,
             datapoint_name: "migrate_builtin_to_core_bpf_stake_program",
         }),
@@ -128,13 +129,39 @@ pub static BUILTINS: &[BuiltinPrototype] = &[
     }),
 ];
 
-pub static STATELESS_BUILTINS: &[StatelessBuiltinPrototype] = &[];
+pub static STATELESS_BUILTINS: &[StatelessBuiltinPrototype] = &[StatelessBuiltinPrototype {
+    core_bpf_migration_config: Some(CoreBpfMigrationConfig {
+        source_buffer_address: buffer_accounts::slashing_program::id(),
+        upgrade_authority_address: None,
+        feature_id: feature_set::enshrine_slashing_program::id(),
+        verified_build_hash: Some(buffer_accounts::slashing_program::VERIFIED_BUILD_HASH),
+        migration_target: CoreBpfMigrationTargetType::Stateless,
+        datapoint_name: "enshrine_slashing_program",
+    }),
+    program_id: buffer_accounts::slashing_program::PROGRAM_ID,
+    name: "solana_slashing_program",
+}];
 
 /// Live source buffer accounts for builtin migrations.
 mod buffer_accounts {
     pub mod stake_program {
         solana_pubkey::declare_id!("8t3vv6v99tQA6Gp7fVdsBH66hQMaswH5qsJVqJqo8xvG");
     }
+    pub mod slashing_program {
+        use {solana_hash::Hash, solana_pubkey::Pubkey};
+
+        solana_pubkey::declare_id!("S1asHs4je6wPb2kWiHqNNdpNRiDaBEDQyfyCThhsrgv");
+
+        pub(crate) const PROGRAM_ID: Pubkey =
+            Pubkey::from_str_const("S1ashing11111111111111111111111111111111111");
+        // 192ed727334abe822d5accba8b886e25f88b03c76973c2e7290cfb55b9e1115f
+        const HASH_BYTES: [u8; 32] = [
+            0x19, 0x2e, 0xd7, 0x27, 0x33, 0x4a, 0xbe, 0x82, 0x2d, 0x5a, 0xcc, 0xba, 0x8b, 0x88,
+            0x6e, 0x25, 0xf8, 0x8b, 0x03, 0xc7, 0x69, 0x73, 0xc2, 0xe7, 0x29, 0x0c, 0xfb, 0x55,
+            0xb9, 0xe1, 0x11, 0x5f,
+        ];
+        pub(crate) const VERIFIED_BUILD_HASH: Hash = Hash::new_from_array(HASH_BYTES);
+    }
 }
 
 // This module contains a number of arbitrary addresses used for testing Core
@@ -162,6 +189,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_system_program",
         };
     }
@@ -181,6 +209,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_vote_program",
         };
     }
@@ -200,6 +229,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_bpf_loader_deprecated_program",
         };
     }
@@ -219,6 +249,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_bpf_loader_program",
         };
     }
@@ -238,6 +269,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_bpf_loader_upgradeable_program",
         };
     }
@@ -257,6 +289,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_compute_budget_program",
         };
     }
@@ -276,6 +309,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_zk_token_proof_program",
         };
     }
@@ -295,6 +329,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_loader_v4_program",
         };
     }
@@ -314,6 +349,7 @@ pub mod test_only {
             upgrade_authority_address: Some(upgrade_authority::id()),
             feature_id: feature::id(),
             migration_target: super::CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "migrate_builtin_to_core_bpf_zk_elgamal_proof_program",
         };
     }

+ 5 - 0
feature-set/src/lib.rs

@@ -1030,6 +1030,10 @@ pub mod mask_out_rent_epoch_in_vm_serialization {
     solana_pubkey::declare_id!("RENtePQcDLrAbxAsP3k8dwVcnNYQ466hi2uKvALjnXx");
 }
 
+pub mod enshrine_slashing_program {
+    solana_pubkey::declare_id!("sProgVaNWkYdP2eTRAy1CPrgb3b9p8yXCASrPEqo6VJ");
+}
+
 pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::new(|| {
     [
         (secp256k1_program_enabled::id(), "secp256k1 program"),
@@ -1263,6 +1267,7 @@ pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::n
         (require_static_nonce_account::id(), "SIMD-0242: Static Nonce Account Only"),
         (raise_block_limits_to_60m::id(), "Raise block limit to 60M SIMD-0256"),
         (mask_out_rent_epoch_in_vm_serialization::id(), "SIMD-0267: Sets rent_epoch to a constant in the VM"),
+        (enshrine_slashing_program::id(), "SIMD-0204: Slashable event verification"),
         /*************** ADD NEW FEATURES HERE ***************/
     ]
     .iter()

+ 2 - 0
programs/sbf/Cargo.lock

@@ -5550,6 +5550,7 @@ dependencies = [
  "agave-feature-set",
  "solana-bpf-loader-program",
  "solana-compute-budget-program",
+ "solana-hash",
  "solana-loader-v4-program",
  "solana-program-runtime",
  "solana-pubkey",
@@ -7556,6 +7557,7 @@ dependencies = [
  "solana-rayon-threadlimit",
  "solana-runtime-transaction",
  "solana-sdk",
+ "solana-sha256-hasher",
  "solana-stake-program",
  "solana-svm",
  "solana-svm-callback",

+ 1 - 0
runtime/Cargo.toml

@@ -79,6 +79,7 @@ solana-pubkey = { workspace = true }
 solana-rayon-threadlimit = { workspace = true }
 solana-runtime-transaction = { workspace = true }
 solana-sdk = { workspace = true }
+solana-sha256-hasher = { workspace = true }
 solana-stake-program = { workspace = true }
 solana-svm = { workspace = true }
 solana-svm-callback = { workspace = true }

+ 4 - 1
runtime/src/bank/builtins/core_bpf_migration/error.rs

@@ -1,5 +1,5 @@
 use {
-    solana_sdk::{instruction::InstructionError, pubkey::Pubkey},
+    solana_sdk::{hash::Hash, instruction::InstructionError, pubkey::Pubkey},
     thiserror::Error,
 };
 
@@ -45,4 +45,7 @@ pub enum CoreBpfMigrationError {
     /// Upgrade authority mismatch
     #[error("Upgrade authority mismatch. Expected: {0:?}, Got: {1:?}")]
     UpgradeAuthorityMismatch(Pubkey, Option<Pubkey>),
+    /// Invalid verified build hash
+    #[error("Invalid build hash. Expected: {0:?}, Got: {1:?}")]
+    BuildHashMismatch(Hash, Hash),
 }

+ 90 - 26
runtime/src/bank/builtins/core_bpf_migration/mod.rs

@@ -219,7 +219,15 @@ impl Bank {
 
         let target =
             TargetBuiltin::new_checked(self, builtin_program_id, &config.migration_target)?;
-        let source = SourceBuffer::new_checked(self, &config.source_buffer_address)?;
+        let source = if let Some(expected_hash) = config.verified_build_hash {
+            SourceBuffer::new_checked_with_verified_build_hash(
+                self,
+                &config.source_buffer_address,
+                expected_hash,
+            )?
+        } else {
+            SourceBuffer::new_checked(self, &config.source_buffer_address)?
+        };
 
         // Attempt serialization first before modifying the bank.
         let new_target_program_account = self.new_target_program_account(&target)?;
@@ -260,19 +268,7 @@ impl Bank {
             new_target_program_account.lamports(),
             new_target_program_data_account.lamports(),
         )?;
-
-        // Update the bank's capitalization.
-        match lamports_to_burn.cmp(&lamports_to_fund) {
-            Ordering::Greater => {
-                self.capitalization
-                    .fetch_sub(checked_sub(lamports_to_burn, lamports_to_fund)?, Relaxed);
-            }
-            Ordering::Less => {
-                self.capitalization
-                    .fetch_add(checked_sub(lamports_to_fund, lamports_to_burn)?, Relaxed);
-            }
-            Ordering::Equal => (),
-        }
+        self.update_captalization(lamports_to_burn, lamports_to_fund)?;
 
         // Store the new program accounts and clear the source buffer account.
         self.store_account(&target.program_address, &new_target_program_account);
@@ -354,8 +350,26 @@ impl Bank {
             source.buffer_account.lamports(),
         )?;
         let lamports_to_fund = new_target_program_data_account.lamports();
+        self.update_captalization(lamports_to_burn, lamports_to_fund)?;
 
-        // Update the bank's capitalization.
+        // Store the new program data account and clear the source buffer account.
+        self.store_account(
+            &target.program_data_address,
+            &new_target_program_data_account,
+        );
+        self.store_account(&source.buffer_address, &AccountSharedData::default());
+
+        // Update the account data size delta.
+        self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size);
+
+        Ok(())
+    }
+
+    fn update_captalization(
+        &mut self,
+        lamports_to_burn: u64,
+        lamports_to_fund: u64,
+    ) -> Result<(), CoreBpfMigrationError> {
         match lamports_to_burn.cmp(&lamports_to_fund) {
             Ordering::Greater => {
                 self.capitalization
@@ -366,17 +380,7 @@ impl Bank {
                     .fetch_add(checked_sub(lamports_to_fund, lamports_to_burn)?, Relaxed);
             }
             Ordering::Equal => (),
-        }
-
-        // Store the new program data account and clear the source buffer account.
-        self.store_account(
-            &target.program_data_address,
-            &new_target_program_data_account,
-        );
-        self.store_account(&source.buffer_address, &AccountSharedData::default());
-
-        // Update the account data size delta.
-        self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size);
+        };
 
         Ok(())
     }
@@ -652,6 +656,7 @@ pub(crate) mod tests {
             upgrade_authority_address,
             feature_id: Pubkey::new_unique(),
             migration_target: CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "test_migrate_builtin",
         };
 
@@ -706,10 +711,16 @@ pub(crate) mod tests {
         ) = test_context
             .calculate_post_migration_capitalization_and_accounts_data_size_delta_off_chain(&bank);
 
+        let expected_hash = {
+            let data = test_elf();
+            let end_offset = data.iter().rposition(|&x| x != 0).map_or(0, |i| i + 1);
+            solana_sha256_hasher::hash(&data[..end_offset])
+        };
         let core_bpf_migration_config = CoreBpfMigrationConfig {
             source_buffer_address,
             upgrade_authority_address,
             feature_id: Pubkey::new_unique(),
+            verified_build_hash: Some(expected_hash),
             migration_target: CoreBpfMigrationTargetType::Stateless,
             datapoint_name: "test_migrate_stateless_builtin",
         };
@@ -775,6 +786,7 @@ pub(crate) mod tests {
             upgrade_authority_address: Some(Pubkey::new_unique()), // Mismatch.
             feature_id: Pubkey::new_unique(),
             migration_target: CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "test_migrate_builtin",
         };
 
@@ -785,6 +797,57 @@ pub(crate) mod tests {
         )
     }
 
+    #[test]
+    fn test_migrate_fail_verified_build_mismatch() {
+        let mut bank = create_simple_test_bank(0);
+
+        let builtin_id = Pubkey::new_unique();
+        let source_buffer_address = Pubkey::new_unique();
+
+        let upgrade_authority_address = Some(Pubkey::new_unique());
+
+        {
+            let builtin_name = String::from("test_builtin");
+            let account =
+                AccountSharedData::new_data(1, &builtin_name, &native_loader::id()).unwrap();
+            bank.store_account_and_update_capitalization(&builtin_id, &account);
+            bank.transaction_processor.add_builtin(
+                &bank,
+                builtin_id,
+                builtin_name.as_str(),
+                ProgramCacheEntry::default(),
+            );
+            account
+        };
+
+        let test_context = TestContext::new(
+            &bank,
+            &builtin_id,
+            &source_buffer_address,
+            upgrade_authority_address,
+        );
+        let TestContext {
+            target_program_address: builtin_id,
+            source_buffer_address,
+            ..
+        } = test_context;
+
+        let core_bpf_migration_config = CoreBpfMigrationConfig {
+            source_buffer_address,
+            upgrade_authority_address: None,
+            feature_id: Pubkey::new_unique(),
+            migration_target: CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: Some(Hash::default()),
+            datapoint_name: "test_migrate_builtin",
+        };
+
+        assert_matches!(
+            bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
+                .unwrap_err(),
+            CoreBpfMigrationError::BuildHashMismatch(_, _)
+        )
+    }
+
     #[test]
     fn test_migrate_none_authority_with_some_buffer_authority() {
         let mut bank = create_simple_test_bank(0);
@@ -834,6 +897,7 @@ pub(crate) mod tests {
             upgrade_authority_address: None, // None.
             feature_id: Pubkey::new_unique(),
             migration_target: CoreBpfMigrationTargetType::Builtin,
+            verified_build_hash: None,
             datapoint_name: "test_migrate_builtin",
         };
 

+ 26 - 0
runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs

@@ -4,6 +4,7 @@ use {
     solana_sdk::{
         account::{AccountSharedData, ReadableAccount},
         bpf_loader_upgradeable::{self, UpgradeableLoaderState},
+        hash::Hash,
         pubkey::Pubkey,
     },
 };
@@ -47,6 +48,31 @@ impl SourceBuffer {
         }
         Err(CoreBpfMigrationError::InvalidBufferAccount(*buffer_address))
     }
+
+    /// [`SourceBuffer::new_checked`] but also verifies the build hash
+    /// https://github.com/Ellipsis-Labs/solana-verifiable-build
+    pub(crate) fn new_checked_with_verified_build_hash(
+        bank: &Bank,
+        buffer_address: &Pubkey,
+        expected_hash: Hash,
+    ) -> Result<Self, CoreBpfMigrationError> {
+        let buffer = Self::new_checked(bank, buffer_address)?;
+        let data = buffer.buffer_account.data();
+
+        let offset = bpf_loader_upgradeable::UpgradeableLoaderState::size_of_buffer_metadata();
+        let end_offset = data.iter().rposition(|&x| x != 0).map_or(offset, |i| i + 1);
+        let buffer_program_data = &data[offset..end_offset];
+        let hash = solana_sha256_hasher::hash(buffer_program_data);
+
+        if hash != expected_hash {
+            return Err(CoreBpfMigrationError::BuildHashMismatch(
+                hash,
+                expected_hash,
+            ));
+        }
+
+        Ok(buffer)
+    }
 }
 
 #[cfg(test)]

+ 2 - 0
svm/examples/Cargo.lock

@@ -5408,6 +5408,7 @@ dependencies = [
  "agave-feature-set",
  "solana-bpf-loader-program",
  "solana-compute-budget-program",
+ "solana-hash",
  "solana-loader-v4-program",
  "solana-program-runtime",
  "solana-pubkey",
@@ -7380,6 +7381,7 @@ dependencies = [
  "solana-rayon-threadlimit",
  "solana-runtime-transaction",
  "solana-sdk",
+ "solana-sha256-hasher",
  "solana-stake-program",
  "solana-svm",
  "solana-svm-callback",