分类 Solana 下的文章

Solana Validator 限制账本大小以节省磁盘空间


--limit-ledger-size

--limit-ledger-size参数允许您指定节点在磁盘上保留多少个账本 碎片。如果不包含此参数,验证器将保留所有收到的账本数据,直到磁盘空间用尽。否则,验证器将不断清除最旧的数据,以保持在指定--limit-ledger-size 值以下。

日志级别

启动时,增加日志级别,避免过多日志输出

export RUST_LOG=solana=error

账本有多大?我的验证者需要多少存储空间?

分类账是无限大的。您不需要存储整个分类账,而且您可能也没有空间存放它。验证器只存储分类账的一部分。您可以通过--limit-ledger-size在启动验证器时传递选项来控制存储的分类账量。如果您不指定数字,它将使用默认值 2 亿个碎片。这需要约 400-500 GB 的存储空间。您可以传递的最小值是 5000 万个碎片(--limit-ledger-size 50000000)。如果您想存储一个纪元的分类账(这对于监控目的很有用),您需要指定约 2.6 亿到 2.7 亿个碎片。

https://github.com/anza-xyz/agave/blob/4cbc769068b2c039175efdb88ddaeee3f8fa8042/docs/src/operations/guides/validator-start.md
https://github.com/agjell/sol-tutorials/blob/master/solana-validator-faq.md#6b-how-big-is-the-ledger-how-much-storage-space-do-i-need-for-my-validator


Solana中 PDA、ATA 与 普通Account 的区别与关系


普通账户地址

对于账户地址的创建是由一个密钥对来生成的,但在Solana中账户地址与以太坊中的账户地址还是有一些区别的。

以太坊账户地址

以太坊账户地址的生成过程:

  1. 通过私钥生成公钥
  2. 对公钥进行 Keccak-256 哈希
  3. 取哈希值的最后 160 位(20 字节)作为地址
  4. 将地址以 0x 开头,并根据需要选择是否使用 EIP-55 格式

地址中通常是小写字母,但也有大写字母的变种,称为 EIP-55 格式。在 EIP-55 中,某些字符会根据哈希值的大小写进行区分,从而增加地址的错误检查能力。

Solana 账户地址

Solana账户地址的生成过程:

  1. 通过私钥生成公钥,一般通过调用 Keypair.generate() 生成
  2. 公钥直接映射为账户的地址,长度为 32 字节
  3. 为了使用方便,一般对其进行 Base58 编码,将公钥转换为地址字符串

代码:

const { Keypair } = require('@solana/web3.js');

// 生成一个新的密钥对
const keypair = Keypair.generate();

// 获取公钥,实际上就是账户地址
const publicKey = keypair.publicKey;

// 转换公钥为 Base58 编码的字符串(即账户地址)
const address = publicKey.toBase58();

console.log("Solana Account Address (Base58):", address);

可以看到 Solana 中的账户地址就是公钥,平时使用的账户地址,一般都是指 Base58 编码后的字符串。

总结

类型 Solana 以太坊
生成 公钥直接作为地址 公钥通过 Keccak-256 哈希,取哈希的最后 160 位
长度 32 字节(256 位),Base58 编码后 43 字符 20 字节(160 位),16 进制编码后 40 字符
哈希算法 无哈希处理,直接使用公钥 使用 Keccak-256 哈希处理公钥
编码方式 Base58 编码 16 进制编码,0x 前缀
示例 4Erv6yZoXckm6QqsbU6y6TbS8o8wVkAXw7KwHjsAVB9D 0x5b1a49d2c631eeed5d2b8e6abdd87e07d8e1a3b3

虽然 Solana 和以太坊都基于公钥生成地址,但它们在地址生成的具体方式、哈希算法和编码格式上存在不同。
Solana 地址直接使用公钥并通过 Base58 编码,而以太坊则先对公钥进行哈希处理,并且使用 16 进制编码生成地址。

PDA 账户地址

对于PDA账户地址的样子与普通账户是一样的,用户根本无法肉眼看出来一个地址是哪一种账户类型。

当创建一个交易时,一般都需要签名,对于普通账户,可以使用私钥的对交易签名。而对于 PDA 地址,虽然没有私钥,但在 Soalan中它允许以编程的方式对交易进行签名。

一个普通的账户地址是指在Ed25519 曲线(椭圆曲线加密)上的一个点,如 Alice 和 Bob 它们都属于基本账户,都有公钥和私钥。

而 PDA 账户是表示在 Ed25519 曲线之外的点,它只有公钥,没有私钥。需要借助一些预定义的种子集来推导生成。

PDA 地址生成公式

PDA = pda_hash(program_id, seeds)

其中 program_id 字段是指当前程序ID(合约), 以保证生成的pda账户只对当前程序有效。seeds 它是一些种子的集合,如 ["abc", "xyz", "888"]。

示例

import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey("11111111111111111111111111111111");
const string = "helloWorld";

const [PDA, bump] = PublicKey.findProgramAddressSync(
  [Buffer.from(string)],
  programId,
);

console.log(`PDA: ${PDA}`);
console.log(`Bump: ${bump}`);

对于 bump 字段,它是由于在生成地址过程中,程序自动引入一个bump种子,它是这个0-255的数字。首先计算时先从255开始,如果计算得出的地址不是有效的 PDA 地址,则减少一,直到找到有效的地址为止。

PDA 地址的计算过程为

ATA账户

ATA(Associated Token Account) 账户是一个特殊的 PDA 账户,同样也是没有私钥的。它与pda 账户唯一的区别在于在生成pda地址的过程中,添加了一个新的种子,而这个种子其实就是 Mint Account 地址,而 Mint Account 只有 spl-token 才有的概念,因此对于 ATA 账户只能用在 spl-token 代币相关的地方。
其地址生成是使用 Owner 地址和 Mint Account 地址确定性通过 PDA 派生的,它是一个 spl-token 代币地址。可以将关联代币账户视为特定铸币和所有者的“默认”代币账户

示例

import { getAssociatedTokenAddressSync } from "@solana/spl-token";

const associatedTokenAccountAddress = getAssociatedTokenAddressSync(
  USDC_MINT_ADDRESS,
  OWNER_ADDRESS,
);

函数定义 https://github.com/solana-labs/solana-program-library/blob/d72289c79/token/js/src/state/mint.ts#L190

/**
 * Get the address of the associated token account for a given mint and owner
 *
 * @param mint                     Token mint account
 * @param owner                    Owner of the new account
 * @param allowOwnerOffCurve       Allow the owner account to be a PDA (Program Derived Address)
 * @param programId                SPL Token program account
 * @param associatedTokenProgramId SPL Associated Token program account
 *
 * @return Address of the associated token account
 */
export function getAssociatedTokenAddressSync(
    mint: PublicKey,
    owner: PublicKey,
    allowOwnerOffCurve = false,
    programId = TOKEN_PROGRAM_ID,
    associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
): PublicKey {
    if (!allowOwnerOffCurve && !PublicKey.isOnCurve(owner.toBuffer())) throw new TokenOwnerOffCurveError();

    const [address] = PublicKey.findProgramAddressSync(
        [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()],
        associatedTokenProgramId
    );

    return address;
}

可以看到,这里和生成 PDA 地址时调用的函数都是 PublicKey.findProgramAddressSync() 函数,唯一的区别就是参数不一样而已。
这里主要有两点:

  • 种子使用固定的 owner、 mint 和 programId,用户没有办法自定义种子。
  • 第二个参数程序ID 是 associatedTokenProgram,而不是原来的 TokenProgram 或 Token 2022 Program。

对于底层最终的实现,就是将一系的值连接起来并sha256计算,使用这个结果创建 PublicKey 对象,实现代码参考 createProgramAddressSync(seeds: Array<Buffer | Uint8Array>, programId: PublicKey) 函数。

从ATA账号的创建原理可以看到,ATA 账户也是一个PDA账户,只不过是一个特殊的PDA账户,它只能用在 spl-token 代币程序中,同时与 associatedTokenProgram 有一定的关系。

参考资料

https://solana.com/zh/docs/core/pda
https://solana.com/zh/docs/core/tokens#associated-token-account

转载:https://learnblockchain.cn/article/12528


使用Solana主网账户和程序


通常,本地测试依赖于本地验证器上默认不可用的程序和帐户。
Solana CLI 允许以下操作:

  • 下载程序和账户
  • 将程序和帐户加载到本地验证器

如何从主网加载账户

可以将JUP代币铸币账户下载到文件中:

# solana account -u <source cluster> --output <output format> --output-file <destination file name/path> <address of account to fetch>
solana account -u m --output json-compact --output-file jup.json JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN

然后通过在启动验证器时传递帐户的文件和目标地址(在本地集群上)将其加载到您的本地网络:

# solana-test-validator --account <address to load the account to> <path to account file> --reset
solana-test-validator --account JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN jup.json --reset

同样,也可以下载 Openbook 程序:

# solana program dump -u <source cluster> <address of account to fetch> <destination file name/path>
solana program dump -u m srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX openbook.so

然后通过在启动验证器时传递程序的文件和目标地址(在本地集群上)将其加载到您的本地网络:

# solana-test-validator --bpf-program <address to load the program to> <path to program file> --reset
solana-test-validator --bpf-program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX openbook.so --reset

https://solana.com/zh/developers/cookbook/development/using-mainnet-accounts-programs


SysvarRent111111111111111111111111111111111 账户用途


在 Solana 区块链中, SysvarRent111111111111111111111111111111111 是一个系统变量(Sysvar)账户。系统变量账户是 Solana 网络中预定义的特殊账户,用于存储与网络运行相关的全局信息,这些信息可以被智能合约和客户端程序访问。

该账户的具体作用

SysvarRent111111111111111111111111111111111 账户专门用于存储租金(Rent)相关的信息。在 Solana 中,租金是为了确保链上存储的有效性和经济性而设计的一种机制。具体来说,该账户存储了以下重要信息:

  1. 租金费率(Rent Exemption) :

    • 这个账户记录了使账户免受租金影响所需的最低余额。如果一个账户的余额低于这个阈值,那么该账户需要定期支付租金,否则可能会被系统清除。
    • 智能合约和客户端程序可以通过读取这个系统变量来确定需要存入多少资金才能使账户免受租金的影响。
  2. 租金计算参数 :

    • 它还包含了用于计算租金的参数,例如每字节的租金费率和租金收取的周期。
    • 这些参数对于开发者和用户来说非常重要,因为它们决定了在 Solana 链上存储数据的成本。

代码示例

在 Solana 的 Rust 编程中,可以通过以下方式访问这个系统变量账户:

use anchor_lang::prelude::*;
use solana_program::sysvar::rent::Rent;

#[program]
pub mod my_solana_program {
    use super::*;

    pub fn read_rent_info(ctx: Context<ReadRentInfo>) -> Result<()> {
        let rent = &ctx.accounts.rent;
        // 读取租金豁免金额
        let rent_exemption = rent.minimum_balance(1024); // 假设账户大小为 1024 字节
        msg!("Rent exemption for 1024 bytes: {}", rent_exemption);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct ReadRentInfo<'info> {
    #[account(address = solana_program::sysvar::rent::ID)]
    pub rent: Account<'info, Rent>,
}

代码解释

在这个示例中,我们定义了一个名为 read_rent_info 的程序指令,用于读取租金信息。通过 Account 结构体,我们指定了 rent 账户的地址为 solana_program::sysvar::rent::ID ,即 SysvarRent111111111111111111111111111111111 。然后,我们可以通过 rent.minimum_balance 方法计算出指定大小的账户所需的租金豁免金额。

通过这种方式,开发者可以在智能合约中动态地获取租金信息,从而更好地管理账户的存储成本。


SysvarC1ock11111111111111111111111111111111 账户用途


在 Solana 区块链中, SysvarC1ock11111111111111111111111111111111 是一个系统变量账户(Sysvar),它代表着 Solana 网络中的时钟信息。下面详细介绍它的作用、使用场景以及相关代码示例。

作用

SysvarC1ock11111111111111111111111111111111 账户存储了 Solana 网络的全局时钟状态,包含了以下关键信息:

  • Unix 时间戳 :表示当前的 Unix 时间,精确到秒。这对于需要依赖时间的智能合约非常重要,比如设定特定时间的任务、活动的开始和结束时间等。
  • Slot 编号 :Slot 是 Solana 中用于衡量时间的基本单位,每个 Slot 代表一个短暂的时间间隔。通过 Slot 编号,可以了解当前区块链的进度和交易的顺序。
  • Epoch 编号 :Epoch 是一组连续的 Slot,通常用于表示区块链的一个周期。Epoch 编号有助于管理和更新网络的状态,例如验证者的轮换等。

使用场景

这个系统变量账户在 Solana 智能合约开发中有广泛的应用,以下是一些常见的使用场景:

  • 时间相关的条件判断 :智能合约可以根据当前的 Unix 时间戳来判断是否满足特定的时间条件,从而执行相应的操作。
  • 交易的时间限制 :在创建或处理交易时,可以使用时钟信息来设置交易的有效时间范围,防止过期交易被处理。
  • 数据的时效性 :对于需要保证数据时效性的应用,如价格预言机,智能合约可以根据时钟信息来判断数据是否过期。

代码示例

在 Rust 编写的 Solana 智能合约中,可以通过以下方式访问 SysvarC1ock11111111111111111111111111111111 账户:

use anchor_lang::prelude::*;
use solana_program::sysvar::clock::Clock;

#[program]
pub mod my_program {
    use super::*;

    pub fn my_function(ctx: Context<MyContext>) -> Result<()> {
        // 获取时钟系统变量
        let clock = Clock::get()?;

        // 打印当前的 Unix 时间戳
        msg!("Current Unix timestamp: {}", clock.unix_timestamp);

        // 打印当前的 Slot 编号
        msg!("Current slot: {}", clock.slot);

        // 打印当前的 Epoch 编号
        msg!("Current epoch: {}", clock.epoch);

        Ok(())
    }
}

#[derive(Accounts)]
pub struct MyContext {}

代码解释

  • use solana_program::sysvar::clock::Clock; :引入 Clock 结构体,用于访问时钟系统变量。
  • let clock = Clock::get()?; :调用 Clock::get() 方法获取当前的时钟信息。
  • msg! 宏用于在 Solana 日志中打印信息,方便调试和监控。
    通过以上代码,智能合约可以轻松地获取和使用 Solana 网络的时钟信息,从而实现各种时间相关的逻辑。