Solana - 鉴别器 Discriminator

本文介绍了 Anchor 框架中 Discriminator 的概念、作用和必要性。Discriminator 作为唯一标识符,用于区分不同类型的数据和账户结构,确保程序在运行时能够正确处理数据,防止类型相关的错误,增强程序的健壮性和安全性。

Solana 鉴别器 (Discriminator)

什么是鉴别器?

在 Anchor 的上下文中,鉴别器是一个用于区分各种数据类型的唯一标识符。鉴别器对于在运行时区分不同类型的账户数据结构至关重要。此外,鉴别器还会添加到指令的开头,以便 Anchor 中的分发函数用于协调指令到程序中调用的方法。

鉴别器 (Discriminator) 被定义为一个具有 鉴别器() (discriminator()) 方法和一个 DISCRIMINATOR 常量的 trait:

pub trait Discriminator {
    const DISCRIMINATOR: [u8; 8];
    fn discriminator() -> [u8; 8] {
        Self::DISCRIMINATOR
    }
}

这里,DISCRIMINATOR 是一个 8 字节的数组,表示数据类型的唯一标识符。鉴别器() (discriminator()) 方法返回 DISCRIMINATOR 的值。

Anchor 中鉴别器的必要性

诸如 ZeroCopyInstructionDataEventEventData 等其他 trait 都要求类型实现 Discriminator。这意味着希望被序列化、反序列化或在事件或指令中使用的每种数据类型都必须具有唯一的 Discriminator

/// An account data structure capable of zero copy deserialization.
/// 一种能够进行零拷贝反序列化的账户数据结构。

pub trait ZeroCopy: Discriminator + Copy + Clone + Zeroable + Pod {}

/// Calculates the data for an instruction invocation, where the data is
/// 计算指令调用的数据,其中数据是
/// `Sha256(<namespace>:<method_name>)[..8] || BorshSerialize(args)`.
/// `args` is a borsh serialized struct of named fields for each argument given
/// `args` 是一个 borsh 序列化的结构体,包含传递给指令的每个参数的具名字段。
/// to an instruction.

pub trait InstructionData: Discriminator + AnchorSerialize {
 fn data(&self) -> Vec<u8> {
  let mut d = Self::discriminator().to_vec();
  d.append(&mut self.try_to_vec().expect("Should always serialize"));
  d
 }
}

/// An event that can be emitted via a Solana log. See [`emit!`](crate::prelude::emit) for an example.
/// 一个可以通过 Solana 日志发出的事件。有关示例,请参见 [`emit!`](crate::prelude::emit)。

pub trait Event: AnchorSerialize + AnchorDeserialize + Discriminator {
 fn data(&self) -> Vec<u8>;
}

// The serialized event data to be emitted via a Solana log.
// 通过 Solana 日志发出的序列化事件数据。
// TODO: remove this on the next major version upgrade.
// TODO:在下一个主要版本升级中删除此内容。

##[doc(hidden)]
##[deprecated(since = "0.4.2", note = "Please use Event instead")]
pub trait EventData: AnchorSerialize + Discriminator {
 fn data(&self) -> Vec<u8>;
}

例如,InstructionData trait 的 data() 方法创建一个包含 Discriminator 和指令序列化数据的字节数组:

pub trait InstructionData: Discriminator + AnchorSerialize {
    fn data(&self) -> Vec<u8> {
        let mut d = Self::discriminator().to_vec();
        d.append(&mut self.try_to_vec().expect("Should always serialize"));
        d
    }
}

这里,Self::discriminator().to_vec() 创建一个包含数据类型 Discriminator 的向量,self.try_to_vec().expect(“Should always serialize”) 创建一个包含指令序列化数据的向量。然后将两个向量连接起来以创建结果字节数组。

Anchor 账户处理中的鉴别器

此代码块是 #[account] 过程宏实现的一部分,负责为特定的账户结构实现 Discriminator trait。

impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
    const DISCRIMINATOR: [u8; 8] = #discriminator;
}

以下代码通过哈希账户结构的命名空间和账户结构的名称来计算 Discriminator。然后,它取该哈希的前 8 个字节以形成鉴别器。此 Discriminator 用于在序列化和反序列化过程中唯一标识帐户结构。

let discriminator: proc_macro2::TokenStream = {
    // Namespace the discriminator to prevent collisions.
    // 命名空间鉴别器以防止冲突。
    let discriminator_preimage = {
        // For now, zero copy accounts can't be namespaced.
        // 目前,零拷贝账户不能被命名空间化。
        if namespace.is_empty() {
            format!("account:{account_name}")
        } else {
            format!("{namespace}:{account_name}")
        }
    };
    let mut discriminator = [0u8; 8];
    discriminator.copy_from_slice(
        &anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
    );
    format!("{discriminator:?}").parse().unwrap()
};

当账户数据被反序列化时,此函数首先检查数据缓冲区的长度,以确保它至少与鉴别器一样长。然后,它将数据缓冲区的前 8 个字节与预期的鉴别器进行比较。如果它们不匹配,则表明正在使用不正确的账户数据结构,该函数将返回错误。

 fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
     if buf.len() < #discriminator.len() {
         return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
     }
     let given_disc = &buf[..8];
     if &#discriminator != given_disc {
         return Err(anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch).with_account_name(#account_name_str));
     }
     Self::try_deserialize_unchecked(buf)
 }

让我们用一个例子来说明鉴别器的重要性。

考虑一个管理两种账户类型的程序,账户 A 和账户 B。这两个账户都由同一个程序拥有,并且具有相同的字段。现在,假设你有一个名为“foo”的指令,该指令仅设计用于在账户 A 上操作。

但是,用户错误地将账户 B 作为参数传递给“foo”指令。鉴于账户 B 与账户 A 共享相同的所有者和相同的字段,程序如何检测到此错误并抛出错误?

这就是鉴别器发挥作用的地方。它唯一地标识了账户的类型。即使账户 A 和账户 B 在结构上相同并且共享相同的所有者,它们也具有不同的鉴别器。

当执行“foo”指令时,Anchor 框架会检查作为参数传递的账户的鉴别器。如果你已将“foo”声明为 `foo: Account<’info, A>`,Anchor 将确保传递的账户的鉴别器与账户 A 的鉴别器匹配。如果鉴别器不匹配(如果传递了账户 B,则会出现这种情况),Anchor 会引发错误,从而防止对账户 B 产生任何意外影响。

鉴别器帮助 Anchor 确保正在处理的账户确实是预期的账户,从而防止运行时出现与类型相关的错误。当你在 Anchor 中使用 `Account` 类型时,此机制会自动处理,从而为你的程序增加额外的安全层。

结论

总之,Anchor 中的鉴别器在管理和区分各种数据类型和账户结构方面发挥着至关重要的作用。它们充当唯一标识符,使 Anchor 框架能够在运行时正确处理数据。鉴别器确保每个都被视为一个独特的实体,从而防止任何无意的账户操作。这种机制极大地增强了程序的健壮性和安全性,并确保将潜在的类型相关错误保持在最低限度。

  • 原文链接: medium.com/@trungbaotran...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
trungbaotran1701
trungbaotran1701
江湖只有他的大名,没有他的介绍。