Skip to main content

迁移程序的数据账户

你如何迁移一个程序的数据账户?

当你创建一个程序时,与该程序关联的每个数据账户都将具有特定的数据结构。如果你需要升级一个程序派生账户,那么你将得到一堆具有旧结构的剩余程序派生账户。

通过账户版本控制,您可以将旧账户升级到新的结构。

info

tip 注意 这只是在程序拥有的账户(POA)中迁移数据的众多方法之一。

场景

为了对账户数据进行版本控制和迁移,我们将为每个账户提供一个ID。该ID允许我们在将其传递给程序时识别账户的版本,从而正确处理账户。

假设有以下账户状态和程序:

#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
}

#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}

在我们账户的第一个版本中,我们执行以下操作:

IDAction
1Include a 'data version' field in your data. It can be a simple incrementing ordinal (e.g. u8) or something more sophisticated
2Allocating enough space for data growth
3Initializing a number of constants to be used across program versions
4Add an update account function under fn conversion_logic for future upgrades

假设我们现在希望升级程序的账户,包括一个新的必需字段:somestring字段。

如果我们之前没有为账户分配额外的空间,我们将无法升级该账户,而被卡住。

升级账户

在我们的新程序中,我们希望为内容状态添加一个新属性。下面的变化展示了我们如何利用初始的程序结构,并在现在使用时进行修改。

1. 添加账户转换逻辑

/// Current state (DATA_VERSION 1). If version changes occur, this
/// should be copied to another (see AccountContentOld below)
/// We've added a new field: 'somestring'
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentCurrent {
pub somevalue: u64,
pub somestring: String,
}

/// Old content state (DATA_VERSION 0).
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct AccountContentOld {
pub somevalue: u64,
}

/// Maintains account data
#[derive(BorshDeserialize, BorshSerialize, Debug, Default, PartialEq)]
pub struct ProgramAccountState {
is_initialized: bool,
data_version: u8,
account_data: AccountContentCurrent,
}
Line(s)Note
6We've added Solana's solana_program::borsh::try_from_slice_unchecked to simplify reading subsets of data from the larger data block
13-26Here we've preserved the old content structure, AccountContentOld line 24, before extending the AccountContentCurrent starting in line 17.
60We bump the DATA_VERSION constant
71We now have a 'previous' version and we want to know it's size
86The Coup de grâce is adding the plumbing to upgrade the previous content state to the new (current) content state

然后,我们更新指令,添加一个新的指令来更新somestring,并更新处理器来处理新的指令。请注意,"升级"数据结构是通过pack/unpack封装起来的。

//! instruction Contains the main VersionProgramInstruction enum

use {
crate::error::DataVersionError,
borsh::{BorshDeserialize, BorshSerialize},
solana_program::{borsh::try_from_slice_unchecked, msg, program_error::ProgramError},
};

#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)]
/// All custom program instructions
pub enum VersionProgramInstruction {
InitializeAccount,
SetU64Value(u64),
SetString(String), // Added with data version change
FailInstruction,
}

impl VersionProgramInstruction {
/// Unpack inbound buffer to associated Instruction
/// The expected format for input is a Borsh serialized vector
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let payload = try_from_slice_unchecked::<VersionProgramInstruction>(input).unwrap();
// let payload = VersionProgramInstruction::try_from_slice(input).unwrap();
match payload {
VersionProgramInstruction::InitializeAccount => Ok(payload),
VersionProgramInstruction::SetU64Value(_) => Ok(payload),
VersionProgramInstruction::SetString(_) => Ok(payload), // Added with data version change
_ => Err(DataVersionError::InvalidInstruction.into()),
}
}
}

资料