Upgradeable loader (#13689)

This commit is contained in:
Jack May
2020-12-14 15:35:10 -08:00
committed by GitHub
parent 79fb646872
commit 9e90394583
23 changed files with 3871 additions and 324 deletions

View File

@ -0,0 +1,213 @@
//! @brief An Upgradeable Solana BPF loader.
//!
//! The upgradeable BPF loader is responsible for deploying, upgrading, and
//! executing BPF programs. The upgradeable loader allows a program's authority
//! to update the program at any time. This ability break's the "code is law"
//! contract the usually enforces the policy that once a program is on-chain it
//! becomes immutable. Because of this, care should be taken before executing
//! upgradeable programs which still have a functioning authority. For more
//! information refer to `loader_upgradeable_instruction.rs`
use crate::{
instruction::{AccountMeta, Instruction, InstructionError},
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
pubkey::Pubkey,
system_instruction, sysvar,
};
use bincode::serialized_size;
crate::declare_id!("BPFLoaderUpgradeab1e11111111111111111111111");
/// Upgradeable loader account states
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
pub enum UpgradeableLoaderState {
/// Account is not initialized.
Uninitialized,
/// A Buffer account.
Buffer,
/// An Program account.
Program {
/// Address of the ProgramData account.
programdata_address: Pubkey,
},
// A ProgramData account.
ProgramData {
/// Slot that the program was last modified.
slot: u64,
/// Address of the Program's upgrade authority.
upgrade_authority_address: Option<Pubkey>,
// The raw program data follows this serialized structure in the
// account's data.
},
}
impl UpgradeableLoaderState {
/// Length of an buffer account's data.
pub fn buffer_len(program_len: usize) -> Result<usize, InstructionError> {
Ok(serialized_size(&Self::Buffer)
.map(|len| len as usize)
.map_err(|_| InstructionError::InvalidInstructionData)?
+ program_len)
}
/// Offset into the ProgramData account's data of the program bits.
pub fn buffer_data_offset() -> Result<usize, InstructionError> {
Self::buffer_len(0)
}
/// Length of an executable account's data.
pub fn program_len() -> Result<usize, InstructionError> {
serialized_size(&Self::Program {
programdata_address: Pubkey::default(),
})
.map(|len| len as usize)
.map_err(|_| InstructionError::InvalidInstructionData)
}
/// Length of a ProgramData account's data.
pub fn programdata_len(program_len: usize) -> Result<usize, InstructionError> {
Ok(serialized_size(&Self::ProgramData {
slot: 0,
upgrade_authority_address: Some(Pubkey::default()),
})
.map(|len| len as usize)
.map_err(|_| InstructionError::InvalidInstructionData)?
+ program_len)
}
/// Offset into the ProgramData account's data of the program bits.
pub fn programdata_data_offset() -> Result<usize, InstructionError> {
Self::programdata_len(0)
}
}
/// Returns the instructions required to initialize a Buffer account.
pub fn create_buffer(
payer_address: &Pubkey,
buffer_address: &Pubkey,
lamports: u64,
program_len: usize,
) -> Result<Vec<Instruction>, InstructionError> {
Ok(vec![
system_instruction::create_account(
payer_address,
buffer_address,
lamports,
UpgradeableLoaderState::buffer_len(program_len)? as u64,
&id(),
),
Instruction::new(
id(),
&UpgradeableLoaderInstruction::InitializeBuffer,
vec![AccountMeta::new(*buffer_address, false)],
),
])
}
/// Returns the instructions required to write a chunk of program data to a
/// buffer account.
pub fn write(buffer_address: &Pubkey, offset: u32, bytes: Vec<u8>) -> Instruction {
Instruction::new(
id(),
&UpgradeableLoaderInstruction::Write { offset, bytes },
vec![AccountMeta::new(*buffer_address, true)],
)
}
/// Returns the instructions required to deploy a program with a specified
/// maximum program length. The maximum length must be large enough to
/// accommodate any future upgrades.
pub fn deploy_with_max_program_len(
payer_address: &Pubkey,
program_address: &Pubkey,
buffer_address: &Pubkey,
upgrade_authority_address: Option<&Pubkey>,
program_lamports: u64,
max_data_len: usize,
) -> Result<Vec<Instruction>, InstructionError> {
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
let mut metas = vec![
AccountMeta::new(*payer_address, true),
AccountMeta::new(programdata_address, false),
AccountMeta::new(*program_address, false),
AccountMeta::new(*buffer_address, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(crate::system_program::id(), false),
];
if let Some(address) = upgrade_authority_address {
metas.push(AccountMeta::new_readonly(*address, false));
}
Ok(vec![
system_instruction::create_account(
payer_address,
program_address,
program_lamports,
UpgradeableLoaderState::program_len()? as u64,
&id(),
),
Instruction::new(
id(),
&UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len },
metas,
),
])
}
/// Returns the instructions required to upgrade a program.
pub fn upgrade(
program_address: &Pubkey,
buffer_address: &Pubkey,
authority_address: &Pubkey,
spill_address: &Pubkey,
) -> Instruction {
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
Instruction::new(
id(),
&UpgradeableLoaderInstruction::Upgrade,
vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new_readonly(*program_address, false),
AccountMeta::new(*buffer_address, false),
AccountMeta::new(*spill_address, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*authority_address, true),
],
)
}
/// Returns the instructions required to set a program's authority.
pub fn set_authority(
program_address: &Pubkey,
current_authority_address: &Pubkey,
new_authority_address: Option<&Pubkey>,
) -> Instruction {
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
let mut metas = vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new_readonly(*current_authority_address, true),
];
if let Some(address) = new_authority_address {
metas.push(AccountMeta::new_readonly(*address, false));
}
Instruction::new(id(), &UpgradeableLoaderInstruction::SetAuthority, metas)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_account_lengths() {
assert_eq!(
4,
serialized_size(&UpgradeableLoaderState::Uninitialized).unwrap()
);
assert_eq!(36, UpgradeableLoaderState::program_len().unwrap());
assert_eq!(
45,
UpgradeableLoaderState::programdata_data_offset().unwrap()
);
assert_eq!(
45 + 42,
UpgradeableLoaderState::programdata_len(42).unwrap()
);
}
}

View File

@ -7,6 +7,7 @@ extern crate self as solana_program;
pub mod account_info;
pub mod bpf_loader;
pub mod bpf_loader_deprecated;
pub mod bpf_loader_upgradeable;
pub mod clock;
pub mod decode_error;
pub mod entrypoint;
@ -18,6 +19,7 @@ pub mod hash;
pub mod incinerator;
pub mod instruction;
pub mod loader_instruction;
pub mod loader_upgradeable_instruction;
pub mod log;
pub mod message;
pub mod native_token;

View File

@ -0,0 +1,104 @@
//! Upgradeable loader instruction definitions
#[repr(u8)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum UpgradeableLoaderInstruction {
/// Initialize a Buffer account.
///
/// A Buffer account is an intermediary that once fully populated is used
/// with the `DeployWithMaxDataLen` instruction to populate the program's
/// ProgramData account.
///
/// The `InitializeBuffer` instruction requires no signers and MUST be
/// included within the same Transaction as the system program's
/// `CreateAccount` instruction that creates the account being initialized.
/// Otherwise another party may initialize the account.
///
/// # Account references
/// 0. [writable] source account to initialize.
InitializeBuffer,
/// Write program data into a Buffer account.
///
/// # Account references
/// 0. [writable, signer] Buffer account to write program data to.
Write {
/// Offset at which to write the given bytes.
offset: u32,
/// Serialized program data
#[serde(with = "serde_bytes")]
bytes: Vec<u8>,
},
/// Deploy an executable program.
///
/// A program consists of a Program and ProgramData account pair.
/// - The Program account's address will serve as the program id any
/// instructions that execute this program.
/// - The ProgramData account will remain mutable by the loader only and
/// holds the program data and authority information. The ProgramData
/// account's address is derived from the Program account's address and
/// created by the DeployWithMaxDataLen instruction.
///
/// The ProgramData address is derived from the Program account's address as
/// follows:
///
/// `let (program_data_address, _) = Pubkey::find_program_address(
/// &[program_address],
/// &bpf_loader_upgradeable::id()
/// );`
///
/// The `DeployWithMaxDataLen` instruction does not require the ProgramData
/// account be a signer and therefore MUST be included within the same
/// Transaction as the system program's `CreateAccount` instruction that
/// creates the Program account. Otherwise another party may initialize
/// the account.
///
/// # Account references
/// 0. [Signer] The payer account that will pay to create the ProgramData
/// account.
/// 1. [writable] The uninitialized ProgramData account.
/// 2. [writable] The uninitialized Program account.
/// 3. [writable] The Buffer account where the program data has been
/// written.
/// 4. [] Rent sysvar.
/// 5. [] Clock sysvar.
/// 6. [] System program (`solana_sdk::system_program::id()`).
/// 7. [] The program's authority, optional, if omitted then the program
/// will no longer upgradeable.
DeployWithMaxDataLen {
/// Maximum length that the program can be upgraded to.
max_data_len: usize,
},
/// Upgrade a program.
///
/// A program can be updated as long as the program's authority has not been
/// set to `None`.
///
/// The Buffer account must contain sufficient lamports to fund the
/// ProgramData account to be rent-exempt, any additional lamports left over
/// will be transferred to the spill, account leaving the Buffer account
/// balance at zero.
///
/// # Account references
/// 0. [writable] The ProgramData account.
/// 1. [] The Program account.
/// 2. [Writable] The Buffer account where the program data has been
/// written.
/// 3. [writable] The spill account.
/// 4. [] Rent sysvar.
/// 5. [] Clock sysvar.
/// 6. [signer] The program's authority.
Upgrade,
/// Set a new authority that is allowed to upgrade the program. To
/// permanently disable program updates omit the new authority.
///
/// # Account references
/// 0. `[writable]` The ProgramData account to change the authority of.
/// 1. `[signer]` The current authority.
/// 2. `[]` The new authority, optional, if omitted then the program will
/// not be upgradeable.
SetAuthority,
}