Skip to content

Solana 本草纲目 #143

@jht6

Description

@jht6

交易

交易的数据结构为:

pub struct Transaction {
    pub signatures: Vec<Signature>,
    pub message: Message,
}

交易本身不包含指令,它包含的 Message 是指令序列的预编译表示。
Message 的构造函数负责将每个指令所需的账户列表重新排序,合并为 Solana 运行时所需的去重账户扁平列表。

signatures是签名列表,由Message.account_keys数组中的前几个账户签名, 签名个数由Message.MessageHeader.num_required_signatures确定。

签名占位符

new_unsigned API 实现中,创建未签名的交易,签名列表也要按照message.header.num_required_signatures指定的数量构造,每个元素都使用Signature::default()创建,作为一个签名的占位符,其真实值为一个64字节的全0字节序列,使用println!打印时会转为base58编码,打印出内容1111111111111111111111111111111111111111111111111111111111111111是64个1

Message

Message结构为:

pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec<Pubkey>,
    pub recent_blockhash: Hash,
    pub instructions: Vec<CompiledInstruction>,
}

MessageHeader

MessageHeader结构为:

pub struct MessageHeader {
    pub num_required_signatures: u8,
    pub num_readonly_signed_accounts: u8,
    pub num_readonly_unsigned_accounts: u8,
}

账户根据是否签名者、是否可写分为四类:

  • 可写、签名者:例如钱包主账户,需对交易签名,且其数据可被修改(余额变动)
  • 只读、签名者:例如多签账户、质押授权账户等,需对交易签名,但账户数据不会变化,只读
  • 可写、非签名者:例如收币账户等,不需对交易签名,但其数据可被修改(余额变动)
  • 只读、非签名者:例如程序账户(系统程序账户、代币元数据账户),仅用于读取程序代码,不对交易签名

new_with_compiled_instructions() API 实现中,对上述三个字段的赋值操作为:

  • pub num_required_signatures: from_keypairs_len as u8,签名者数量
  • pub num_readonly_signed_accounts: 0,已签名的只读账户数量,这里常规交易场景直接设为0;只有当多签合约相关场景中才会出现需要签名且只读的账户,因为这些账户只需要签名,其状态不会变更
  • pub num_readonly_unsigned_accounts: program_ids.len() as u8,合约账户是未签名且只读账户

Presigner

pub struct Presigner {
    pubkey: Pubkey,
    signature: Signature,
}

包含一个公钥和一个签名, 只验证签名是否和消息、公钥匹配,不持有私钥,无法生成新签名

用于提前生成并提供签名的场景:

  • 离线签名:签名在安全环境下提前生成,之后在不安全环境只提供签名本身
  • 多方签名:某些签名由外部系统或硬件提前生成

最大账户锁定数量

有一个常量定义如下:

/// Maximum number of accounts that a transaction may lock.
/// 128 was chosen because it is the minimum number of accounts
/// needed for the Neon EVM implementation.
pub const MAX_TX_ACCOUNT_LOCKS: usize = 128;

MAX_TX_ACCOUNT_LOCKS 是交易锁定账户数量的上限,设为 128,主要是为了兼容 Neon EVM(Solana 上的以太坊兼容虚拟机实现)的需求。

简单投票交易

投票交易是 Solana 共识机制的核心部分。每个验证者节点会定期向网络广播“我认为哪些区块是有效的”——这就是投票。

简单投票交易是最常见、最基础的投票交易形式,有如下特征:

  • 只有一个指令,且 program_idVote111111111111111111111111111111111111111
  • 有 1 或 2 个签名
  • 是一个 legacy 交易

签名数量分两种场景:

  • 1 个签名:普通投票,验证者自己签名。
  • 2 个签名:验证者账户的投票权被委托给了另一个账户(即设置了 authorized voter),需要两者都签名。

Transfer指令

transfer指令中的data数据是字节流,里面包含了transfer指令的类型值与转账的lamports值:

[0x02][lamports的8字节LE表示]

Rust SDK 中关键代码:

pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, false),
    ];
    Instruction::new_with_bincode(ID, &SystemInstruction::Transfer { lamports }, account_metas)
}


pub fn new_with_bincode<T: serde::Serialize>(
        program_id: Pubkey,
        data: &T,
        accounts: Vec<AccountMeta>,
    ) -> Self {
        let data = bincode::serialize(data).unwrap();
        Self {
            program_id,
            accounts,
            data,
        }
    }

SystemInstruction::Transfer 是一个enum值,其索引为2,在bincode序列化时处理为字节流。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions