Solana编程模型简介:账户、数据和交易

  • syndica
  • 发布于 2025-04-08 19:29
  • 阅读 80

本文概述了Solana的编程模型,重点介绍了Solana上构建应用程序的关键结构:账户和交易。文章详细介绍了Solana账户的类型、数据序列化以及如何使用web3.js读取链上数据;同时讲解了Solana交易的结构和执行,并通过Anchor框架部署和交互了一个简单的程序。通过学习,开发者可以充分理解Solana的核心组件,并能进行自己的应用程序开发。

本文概述了 Solana 编程模型,重点介绍了支持构建在 Solana 上的应用程序的关键结构。我们将涵盖:

  1. Solana 账户的基础知识及其在区块链架构中的作用
  2. 如何在 Solana 上序列化和存储数据,包括使用 web3.js 读取链上数据的示例
  3. Solana 上交易的结构和执行,重点介绍使用 Anchor 部署和与简单程序交互的过程,Anchor 是 Solana 广泛采用的开发框架。

希望在本文结束时,开发人员将对 Solana 的核心组件有一个坚实的基础,并为开发自己的应用程序提供实用的后续步骤。

Solana 编程模型

在最基本的层面上,Solana 上的应用基于两个基本结构:

  • 包含数据的 账户,数据可以是可执行数据(程序)或用于保存状态的非可执行数据。
  • 交易 引用账户,并通过一系列指令修改交易中涉及的账户的状态。

Solana DApp 位于更广泛的网络中,包括 验证器客户端 RPC 节点 ,链下 预言机 和其他组件。所有这些组件都通过 Solana 编程模型互连。为了促进区块链上的操作,他们使用这两个基本的链上结构:从 账户 读取数据以及执行修改这些账户的 交易

什么是 Solana 账户?

在 Solana 上,账户存储链上数据,并通过唯一的 32 字节公钥来标识。账户可以比作传统计算机系统上的文件。就像计算机上的文件可以存储各种数据类型并通过文件路径访问一样,Solana 账户可以存储高达 10MB 的任意数据。

Solana 的账户架构比其他区块链(如以太坊)的账户架构 加粗抽象加粗 且灵活得多。Solana 中保存状态的每个实体,无论是 token、可执行程序还是简单数据,都封装在一个或多个账户中。

再次强调,你可以将账户视为类似于文件:一个账户要么是 可执行的,要么是 非可执行的,这意味着它包含程序代码(可执行的)或保存数据/状态(非可执行的)。

Solana 账户数据结构

Solana 上有许多不同类型的账户,我们将在本文中介绍每一种。所有账户,无论类型如何,都共享相同的通用 AccountInfo 结构。该结构包含以下元素:

  • key:该账户由公钥唯一标识。在大多数情况下,这是一个 Ed25519 公钥,它有一个对应的私钥用于签署交易。
  • data:一个字节数组,保存账户的实际数据或状态。如果该账户是一个程序,那么这可能是可执行代码。请注意,Solana 上的所有数据都必须经过序列化才能作为字节传输和存储。开发人员可以选择任何序列化格式,最常见的格式是 Borsh。在本文的后面部分,我们将展示如何使用示例序列化/反序列化账户数据。
  • executable:一个布尔值指示器,用于指定该账户是否是可以执行代码的程序。如果是可执行的,程序的 数据将包含称为指令的不同函数。
  • lamports:账户的 lamports 余额,lamports 是原生 SOL token 的最小子单位 (1 lamport = 0.000000001 SOL)。此余额决定了账户在网络上交互的能力,因为交易费用、staking 存款和最低租金存款都需要 SOL。
  • owner:拥有该账户所有权的程序的公钥。只有拥有程序的指令才能修改账户的数据或减少其 lamports。此限制与 权限 (使用签名批准交易) 的概念相结合,确保所有账户都是安全的。
  • rent_epoch:指示此账户下次应付租金的 epoch。但是请注意,常规租金支付已被 弃用,取而代之的是要求账户保持最低余额。

Solana 账户类型

原生程序

原生程序是 Solana 中核心功能所需的内置程序,包含在 Solana 运行时中。这些程序与用户部署的程序不同。原生程序示例:

  • System Program:System Program 负责创建新账户、分配账户数据、将账户分配给自己的程序、从 System Program 拥有的账户转移 lamports 以及管理交易费用的支付。

地址:11111111111111111111111111111111

  • 地址查找表程序:方便使用地址查找表 (ALT),地址查找表通过使用更少的字节引用账户地址来优化交易中的空间

地址:AddressLookupTab1e1111111111111111111111111

  • BPF Loader:负责在 Solana 区块链上部署、升级和执行其他程序

地址:BPFLoaderUpgradeab1e11111111111111111111111

Sysvar 账户

Sysvar 账户是预定义地址上的特殊账户,可为 Solana 程序提供实时集群数据。可以直接或通过程序指令访问这些动态更新的账户。Sysvar 账户示例:

  • Clock Sysvar:包含集群的时间相关数据的信息,包括当前 slot、epoch 和估计的 Unix 时间戳。它会更新每个 slot,以便为程序提供时间上下文,这对于依赖于时间的操作是必需的。

地址:SysvarC1ock11111111111111111111111111111111

  • Fees Sysvar 保存当前 slot 的费用计算器。它提供必要的数据来确定每个 slot 的费用率 governor 所需的交易费用。

地址:SysvarFees111111111111111111111111111111111

  • Instructions Sysvar:在处理期间保存交易的序列化指令。这允许自省,即程序可以引用来自同一交易的其他指令。

地址:Sysvar1nstructions1111111111111111111111111

部署的程序账户

链上程序由多种相关的账户类型组成,每种账户类型在部署过程中都具有特定的作用。

  • 程序账户: 代表链上程序的主账户,它引用可执行字节代码和更新权限。此账户的密钥通用称为“程序 ID”,因为它会调用该程序。
  • 程序可执行数据账户: 包含编译后的字节代码,代表实际的可执行逻辑。可以更新此账户的数据 (即,对于新的程序版本)。
  • 缓冲区账户: 在部署或升级期间使用的临时存储账户,用于保存字节代码,直到将其传输到程序可执行数据账户。Solana 的 BPF Loader (一个原生程序) 允许通过升级授权来升级程序,并使用缓冲区账户来转换链上的可执行数据。

Solana 主网上流行的已部署程序示例:

数据账户

数据账户存储程序所需的状态和数据。它们可以保存所有者程序的逻辑定义的任何信息,每个账户最多 10MB。所有数据账户最初都由 System Program 创建和拥有,然后 System Program 将所有权转移到另一个程序,该程序将初始化和管理该账户的数据。

实际上,应用程序通常需要创建任意数量的数据账户,以便为动态实体(例如个人用户账户、流动性池、金融工具等)启用存储。为了轻松管理这些实体的状态,应用程序利用程序派生地址 (加粗PDAs加粗)。PDA 具有唯一的公钥,由于密钥的生成方式,因此没有相应的私钥。它们通过组合所有者程序 ID 和自定义种子(例如,用户钱包、token 交易对等)以确定方式派生。Solana 运行时允许程序对其以编程方式生成的 PDA 进行签名。

Solana 上的数据序列化

我们已经了解了 Solana 账户如何将链上数据存储为可执行和非可执行字节。处理这些字节的关键步骤是序列化和反序列化:

序列化 将语言友好的数据结构(例如,Rust 结构、枚举、向量)转换为标准化的字节格式,以实现可靠的存储和传输。

反序列化 逆转该过程,将字节转换为内存中的数据结构,以进行指令处理或客户端读取。

Solana 开发人员通常使用 Borsh 或 Bincode 等库来实现高效的二进制序列化。由于账户大小在创建时是固定的,因此仔细规划数据布局非常重要。正确的序列化确保链上程序(通常用 Rust 编写)和链下客户端(通常用 JavaScript/TypeScript 编写)共享一致的数据视图,从而提供数据结构的完整且相同的重建。

示例:反序列化 Token 账户数据

让我们看一个在链上序列化账户数据的简单示例。我们将反序列化 Token 账户中包含的数据。Solana 上的 Token 账户存储有关特定 token 的特定用户钱包的信息。例如,Solana 上拥有 USDC 的用户将拥有自己的 USDC Token 账户,该账户存储 Token Mint (USDC) 和用户的 USDC 余额等信息。

Solana 上的 Token 账户通常使用 PDA 构建。回想一下,PDA 是一个由程序创建的账户,其地址派生自自定义种子,用于存储/组织该程序的某些数据。在我们上面的汽车租赁程序的示例中,单个的汽车预订被构造为单独的账户。

类似地,在 Solana 的 SPL Token Program 的情况下,每个 token (USDC、JUP 等) 都有一个 Token Mint 账户,并且单个用户的 token 余额都存储在单独的 Token 账户中。每个 Token 账户的地址都是一个 PDA,该 PDA 使用 SPL Associated Token 账户程序 从用户的主地址和 Token Mint 地址派生而来。为 Token 账户使用 PDA 大大简化了应用程序的 token 管理,因为每个用户对 于每个 token 都有一个规范地址。

为了让应用程序读取和处理 Token 账户中的数据,它们必须使用 标准 SPL Token 格式 来反序列化它,这种格式遵循固定长度的 165 字节数组:

例如,我们将使用 @solana/web3.js 库来反序列化 Binance 交易所拥有的 USDC Token 账户中的数据。

首先,我们必须知道钱包的公钥,以及 USDC Mint 账户的密钥。我们还需要知道 Token Program 和 Associated Token Program 的密钥:

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

const WALLET_PUBLIC_KEY = new PublicKey("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM");
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");

使用 findProgramAddressSync 方法,我们可以派生 USDC Token 账户密钥,该密钥属于钱包。此函数还返回一个 bump seed,该 bump seed 确保生成的地址在 Solana 上有效。

const [tokenAccountAddress, bumpSeed] = PublicKey.findProgramAddressSync(
    [WALLET_PUBLIC_KEY.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), USDC_MINT.toBuffer()],
    ASSOCIATED_TOKEN_PROGRAM_ID
);
console.log("Wallet's Token Account Address:", tokenAccountAddress.toBase58());
Wallet's Token Account Address: FGETo8T8wMcN2wCjav8VK6eh3dLk63evNDPxzLSJra8B

现在,我们可以使用派生的地址从 Token 账户读取数据。我们将通过 RPC 端点使用与 Solana 主网的连接。如果你还没有 RPC 端点,你可以 在 Syndica 上创建一个免费账户,并使用随账户一起提供的现成可用的 Default API key

const SYNDICA_API_KEY = "YOUR-API-KEY";
const connection = new Connection("https://solana-mainnet.api.syndica.io/api-key/" + SYNDICA_API_KEY);

async function fetchTokenAccountData() {
  try {
    const accountInfo = await connection.getAccountInfo(tokenAccountAddress);
    if (!accountInfo || !accountInfo.data) {
      console.log("No data found for this token account.");
      return;
    }
    console.log("Raw data length:", accountInfo.data.length);
    console.log("Raw data (hex):", accountInfo.data.slice(0, 32).toString('hex'));
  } catch (error) {
    console.error("Error fetching token account data:", error);
  }
}

fetchTokenAccountData();
Raw data length: 165
Raw data (hex): c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61

你可以看到原始数据只是字节,我们需要对其进行反序列化。我们将使用 @solana/buffer-layout@solana/buffer-layout-utils 库来定义布局并反序列化数据。我们定义了 RawAccount,它描述了 token 账户的解码数据的形状,以及 AccountLayout,它指定如何将链上字节读取到 RawAccount 中定义的字段:

export interface RawAccount {
    mint: PublicKey;
    owner: PublicKey;
    amount: bigint;
    delegateOption: 1 | 0;
    delegate: PublicKey;
    state: number;
    isNativeOption: 1 | 0;
    isNative: bigint;
    delegatedAmount: bigint;
    closeAuthorityOption: 1 | 0;
    closeAuthority: PublicKey;
  }

export const AccountLayout = struct<RawAccount>([\
    publicKey("mint"),\
    publicKey("owner"),\
    u64("amount"),\
    u32("delegateOption"),\
    publicKey("delegate"),\
    u8("state"),\
    u32("isNativeOption"),\
    u64("isNative"),\
    u64("delegatedAmount"),\
    u32("closeAuthorityOption"),\
    publicKey("closeAuthority"),\
]);

现在我们可以从 Token 账户反序列化数据:

async function DeserializeTokenAccount() {
  try {
    const accountInfo = await connection.getAccountInfo(tokenAccountAddress);
    if (!accountInfo || !accountInfo.data) {
      console.log("No data found.");
      return;
    }

    const deserializedData = AccountLayout.decode(accountInfo.data);

    console.log("\nDeserialized Account Data:");
    console.log("Mint:", deserializedData.mint.toBase58());
    console.log("Owner:", deserializedData.owner.toBase58());
    console.log("Amount:", deserializedData.amount.toString());
    console.log("Delegate Option:", deserializedData.delegateOption);
    console.log("Delegate:", deserializedData.delegate.toBase58());
    console.log("State:", deserializedData.state);
    console.log("isNativeOption:", deserializedData.isNativeOption);
    console.log("isNative:", deserializedData.isNative.toString());
    console.log("Delegated Amount:", deserializedData.delegatedAmount.toString());
    console.log("closeAuthorityOption:", deserializedData.closeAuthorityOption);
    console.log("Close Authority:", deserializedData.closeAuthority.toBase58());
  } catch (error) {
    console.error("Error during deserialization:", error);
  }
}

DeserializeTokenAccount();
Deserialized Account Data:
Mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Owner: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
Amount: 570747950108573
Delegate Option: 0
Delegate: 11111111111111111111111111111111
State: 1
isNativeOption: 0
isNative: 0
Delegated Amount: 0
closeAuthorityOption: 0
Close Authority: 11111111111111111111111111111111

上面的方法适用于反序列化存储在账户中的任何任意数据。但是,由于 SPL token 账户在 Solana 中是标准的,因此实际上没有必要手动定义上面的数据格式。@solana/spl-token 库使用 getAccount 函数简化了反序列化这个通用账户结构的过程。我们还可以使用 getAccountInfo RPC 方法轻松获取此账户的其他常规账户字段。

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

async function DeserializeTokenAccount() {
  try {
    const tokenAccount = await getAccount(connection, tokenAccountAddress);

    console.log("\Deserialized Token Account Data (using SPL library):");
    console.log("Address:", tokenAccount.address.toBase58());
    console.log("Mint:", tokenAccount.mint.toBase58());
    console.log("Owner:", tokenAccount.owner.toBase58());
    console.log("Amount:", tokenAccount.amount.toString());
    console.log("Delegate:", tokenAccount.delegate ? tokenAccount.delegate.toBase58() : "None");
    console.log("Delegated Amount:", tokenAccount.delegatedAmount.toString());
    console.log("Close Authority:", tokenAccount.closeAuthority ? tokenAccount.closeAuthority.toBase58() : "None");
    console.log("Is Native:", tokenAccount.isNative);
    console.log("Is Frozen:", tokenAccount.isFrozen);

    const accountInfo = await connection.getAccountInfo(tokenAccountAddress);

    if (!accountInfo) {
      console.log("\nNo additional general account info found.");
      return;
    }

    console.log("\nAccount Info:");
    console.log("Lamports:", accountInfo.lamports);
    console.log("Executable:", accountInfo.executable);
    console.log("Owner Program:", accountInfo.owner.toBase58());
    console.log("Account Data Length:", accountInfo.data.length);
  } catch (error) {
    console.error("Error fetching account data:", error);
  }
}

DeserializeTokenAccount();
Deserialized Token Account Data (using SPL library):
Address: FGETo8T8wMcN2wCjav8VK6eh3dLk63evNDPxzLSJra8B
Mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Owner: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
Amount: 570747950108573
Delegate: None
Delegated Amount: 0
Close Authority: None
Is Native: false
Is Frozen: false
Raw data length: 165
Raw data (hex): c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61

Account Info:
Lamports: 2039310
Executable: false
Owner Program: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Account Data Length: 165

如你所见,token 账户是一个数据账户(不可执行的),并且由 Token Program 拥有。token 本身的拥有者 (Binance) 位于账户数据字段中的单独的拥有者字段中。

我们现在已经彻底介绍了账户及其包含的数据的要素。现在,我们将转向交易及其在 Solana 编程模型中的作用,以了解如何在链上创建和更新这些账户。要更深入地了解在 Solana 上读取 链上数据以及如何在应用程序中构造数据,我们建议使用以下资源:

Solana 客户端库:

solana-web3.js v1: https://github.com/solana-labs/solana-web3.js

solana-web3.js v2: https://github.com/anza-xyz/solana-web3.js

Gill: https://github.com/solana-foundation/gill

Solana RPC 指南: https://learnblockchain.cn/article/16837/

在 Solana 上流式传输数据: https://learnblockchain.cn/article/16885/

程序派生地址: https://solana.com/docs/core/pda

Solana 程序示例: https://solana.com/docs/programs/examples

什么是 Solana 交易?

Solana 交易是区块链上账户状态更改的单个原子单位。它的结构为 签名消息 的数组,总计不能超过 1232 字节。

签名 为交易中包含的状态更改提供授权。它们是使用单个 Solana 账户的私钥以加密方式生成的。例如,要将 SOL 简单地转移到不同的钱包 (账户),发送账户必须提供签名,因为他们的 lamports 将被扣除/转移。

消息 包含指令序列。每个指令都是一个可执行逻辑单元,它将更改某些账户的状态。消息还包含有关交易中涉及的所有账户的附加信息。它指定交易中的哪些账户是只读的,哪些账户将被修改,哪些是签名者。通过要求每个交易指定哪些账户是只读的而不是可变的,Solana 运行时可以安全地并行执行不修改同一账户的交易。这是 Solana 交易结构的图形:

指令 是 Solana 区块链上执行逻辑的基本单元。每个指令都指定要执行的程序、所需的账户以及执行所需的数据。每个指令包含以下内容:

program_id:包含指令执行逻辑的程序账户地址。

accounts:指令将从中读取或写入的所有账户,每个账户都使用 AccountMeta 结构指定:

  • pubkey:账户地址
  • is_writable:一个布尔值,指示是否可以修改账户的 lamports 或账户数据。此值在特定指令的上下文中设置。例如,如果 SOL 正在从一个钱包转移到另一个钱包,则两个账户都必须被视为可写的,因为它们都被修改了。
  • is_signer:一个布尔值,指示是否期望该账户签署交易。如果交易的一个或多个指令尝试扣除 lamports 或修改账户数据,则为 true。

data:一个字节数组,用于指定程序要执行的指令处理程序(函数)以及所需的附加数据(函数参数)。

交易中的每个指令都按顺序执行,交易的构造决定了顺序。Solana 上交易的一个重要特征是它们是原子的,这意味着如果任何一个指令失败,所有交易要么完全成功(所有指令都已成功处理),要么完全失败。原子性可防止在交易的某些部分成功而其他部分失败时可能出现的不一致状态。例如,在转移 token 时,从发送者处扣除和贷记到接收者都必须作为单个原子操作发生。

示例:部署简单的 Anchor 程序

部署一个简单的程序来演示账户和交易如何在 Solana 编程模型中一起工作会大有帮助。除了读取和编写一些代码外,你还可以与该程序交互并观察其链上状态。在此示例中,我们将使用 Anchor 部署一个简单的程序。

什么是 Anchor?

Anchor 是一个强大的框架,用于使用 Rust 开发安全且高效的 Solana 程序。它通过抽象低级账户管理并通过宏和特征提供清晰的结构来简化编写、测试和部署 Solana 程序的过程。Anchor 提供了方便的工具,如样板代码生成、命令行界面和 Solana DApp 开发工作区。

要开始,你需要安装 Rust、Solana CLI、Anchor、Node 并在 devnet 上设置一个钱包。这些步骤在 Anchor 文档中进行了解释:https://www.anchor-lang.com/docs/installation

你还需要 安装 pnpm 来安装 Node 依赖项并运行此示例的测试。

完成设置后,克隆此 repo,其中包含 Solana 程序示例:https://github.com/solana-developers/program-examples.git

转到 anchor account-data 示例并验证所有内容都已正确设置:

>> cd program-examples/basics/account-data/anchor

>> pnpm install

>> anchor % rustc --version
rustc 1.79.0 (129f3b996 2024-06-10)

>> solana --version
solana-cli 2.1.14 (src:3ad46824; feat:3271415109, client:Agave)

>> avm --version
avm 0.30.1

>> anchor --version
anchor-cli 0.30.1

>> node --version
v23.6.1

>> pnpm --version
1.22.19

>> solana address
H5WuqB8TNXDLHaf9grdU3iBqmXJB5uGFLwNBn3WEog5y

>> solana balance
5 SOL

你应该探索项目结构,这是 Anchor 项目的典型结构。程序的指令在 src/instructions 中定义。

├── Anchor.toml
├── Cargo.toml
├── package.json
├── pnpm-lock.yaml
├── programs
│   └── anchor-program-example
│       ├── Cargo.toml
│       ├── Xargo.toml
│       └── src
│           ├── constants.rs
│           ├── instructions
│           │   ├── create.rs
│           │   └── mod.rs
│           ├── lib.rs
│           └── state
│               ├── address_info.rs
│               └── mod.rs
├── tests
│   ├── bankrun.test.ts
│   └── test.ts
└── tsconfig.json

在本例中,create.rs 中只有一个指令,即 create_address_info 指令:

pub fn create_address_info(
    ctx: Context<CreateAddressInfo>,
    name: String,
    house_number: u8,
    street: String,
    city: String,
) -> Result<()> {
    *ctx.accounts.address_info = AddressInfo {
        name,
        house_number,
        street,
        city,
    };
    Ok(())
}

你应该进一步探索代码,以了解此指令的确切工作方式。为此,你可以参考本文末尾的 Anchor 文档和其他资源。但是,你应该了解的主要内容是,此指令只是创建一个新的数据账户,其中包含一些与邮寄地址(姓名、门牌号等)相关的任意数据。

我们将在 Solana 的 devnet 上构建和部署此程序,然后在新交易中调用 create_address_info 指令。

anchor.toml 中,将提供程序网络从 Localnet 更改为 Devnet:

[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"

构建程序并将其部署到 devnet:

>> anchor build
Compiling proc-macro2 v1.0.89
Compiling unicode-ident v1.0.13
Compiling version_check v0.9.5
...
Compiling account-data-anchor-program-example v0.1.0 (/Users/me/program-examples-old/basics/account-data/anchor/programs/anchor-program-example)
    Finished `release` profile [optimized] target(s) in 52.27s
+ ./platform-tools/rust/bin/rustc --version
+ ./platform-tools/rust/bin/rustc --print sysroot
+ set +e

>> anchor deploy
Deploying cluster: https://api.devnet.solana.com
Upgrade authority: /Users/me/.config/solana/id.json
Deploying program "anchor_program_example"...
Program path: /Users/me/program-examples/basics/account-data/anchor/target/deploy/anchor_program_example.so...
Program Id: AufzsYrJxjTf9Y7hDRQHNznVekpXmSbM7gQWVrwya3ic

Signature: 2SfWoL17xMjfzG7Zw4UvdX9BLMpECm4QvAhc39wGNuDiVDQ1hoWQ6P3wU9vKeUA2BPrDh4LhwjzJE1b4m13kW64t

Deploy success

部署程序后,你可以通过在 Solana Explorer 上搜索其 Program ID 来检查它。请务必使用 Devnet 网络:https://explorer.solana.com/?cluster=devnet

请注意,executable 字段为 True,因为此账户标识可执行数据(它是一个程序账户)。可执行字节代码位于单独的数据账户中,你可以在 Executable Data 旁边看到它。由于 upgradeable 为 True,因此升级权限可以更改可执行数据。授权密钥将是你的钱包账户。

在 Explorer 页面上的 Transaction History 部分中,单击第一个交易;此交易包含有关你的合约部署的信息。交易日志显示以下内容:

[\
  "Program 11111111111111111111111111111111 invoke [1]",\
  "Program ```ts
   const tx_result = await program.methods
      .createAddressInfo(addressInfo.name, addressInfo.houseNumber, addressInfo.street, addressInfo.city)
      .accounts({
        addressInfo: addressInfoAccount.publicKey,
        payer: payer.publicKey,
      })
      .signers([addressInfoAccount])
      .rpc();

    console.log(`Tx Signature:      : ${tx_result}`); // 记录交易签名

更新你的 anchor.toml 文件,使其仅针对此测试文件:

[scripts]
test = "pnpm ts-mocha -p ./tsconfig.json -t 1000000 tests/test.ts"

使用 anchor run test 运行测试,你应该会看到以下输出:

Account Data!
Payer Address      : H5WuqB8TNXDLHaf9grdU3iBqmXJB5uGFLwNBn3WEog5y
Address Info Acct  : E4jbD4vyt2tuJvzDPyWytPmQd4a8FwhFaeE7r3cJYuje
Tx Signature:      : 5EAXaXaG9HoohM7x6c5gqRJmfY9qoCgTrrBxL1QvsLb5qbpXJ2Ncj9jtyGoiK4GMoEda6iYvdtU9Lw5gN1BkBrKP
    ✔ Create the address info account (1019ms)
Name     : Joe C
House Num: 136
Street   : Mile High Dr.
City     : Solana Beach
    ✔ Read the new account's data (55ms)

地址信息已成功写入由你的程序账户拥有的一个新数据账户。使用 Devnet Explorer 搜索交易签名。你将能够查看发生的事件的精美摘要:

在此页面上,你还可以查看交易的链上日志:

[\
  "Program AufzsYrJxjTf9Y7hDRQHNznVekpXmSbM7gQWVrwya3ic invoke [1]",\
  "Program log: Instruction: CreateAddressInfo",\
  "Program 11111111111111111111111111111111 invoke [2]",\
  "Program 11111111111111111111111111111111 success",\
  "Program AufzsYrJxjTf9Y7hDRQHNznVekpXmSbM7gQWVrwya3ic consumed 7016 of 200000 compute units",\
  "Program AufzsYrJxjTf9Y7hDRQHNznVekpXmSbM7gQWVrwya3ic success"\
]

Solana 执行了你的程序的 create_address_info 指令。这调用了系统程序来创建账户,然后你的程序将数据写入新账户。

这个程序几乎和 Solana 程序一样简单。为了熟悉 Anchor 项目的典型结构,你应该查看代码库并参考官方 Anchor 文档。下面,我们提供了一些有用的链接,可以帮助你继续学习更多关于 Solana 的开发:

Solana 开发框架:

Anchor: https://www.anchor-lang.com

Steel: https://github.com/regolith-labs/steel

Solana 课程和教程:

Official Solana Courses: https://solana.com/developers/courses

Solana Playground: https://beta.solpg.io/

Ethereum 和 EVM 开发者过渡指南: https://solana.com/developers/evm-to-svm/complete-guide

The Solana Toolkit: https://solana.com/docs/toolkit

Running a Local Validator: https://solana.com/docs/toolkit/local-validator

结论

在本文中,我们探讨了 Solana 编程模型的基本要素。我们介绍了 Solana 用于存储数据和代码的灵活账户结构,类似于典型操作系统上的文件。我们还解释了交易如何在链上执行程序以执行状态更改。我们通过两个实际示例演示了如何读取和写入数据:通过 web3.js 读取链上数据以及使用 Anchor 部署一个简单的程序。现在你已经熟悉 Solana 编程模型的基础知识,你可以更深入地研究 Solana 模型支持的无数特性,并开始构建在 Solana 上强大的、高性能的去中心化应用程序的旅程。

Syndica 赋能正在推进 Solana 生态系统的主要企业和架构师。在构建 Web3 云的过程中,我们提供一流的 RPC 节点基础设施、开发者 API、强大的端到端平台以及下一个最前沿的 Solana 客户端验证器,Sig,用 Zig 编写。我们的团队提供专门的支持、可靠性和以用户为中心的导向,以实现与 Solana 的无缝集成,使你能够专注于构建你的企业。

立即在 syndica.io 免费开始,并在此处查看我们的文档 here

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

0 条评论

请先 登录 后评论
syndica
syndica
News & research from Syndica: low latency Solana RPC, data streams, Sig Validator & more