区块链中文技术社区 - 2025年3月
type: channel
link: https://www.bcskill.com/index.php/2025/03
description: 区块链,EOS,Ethereum,IPFS,Dapp技能学习
items: Solana基础 - Solana RPC HTTP 方法
items: solana 交易 confirmationStatus 有几种状态
items: Solana RPC 方法的常见 JSON 数据结构
items: 使用 spl-token 命令行工具测试Solana代币发行
items: error: rustc 1.79.0-dev is not supported by the following package:
items: Solana基础 - 十分钟开始使用多签工具SQUADS
items: Solana基础 - 给EVM开发者的 Solana开发指南
items: Solana基础 - 在 Anchor 中的跨程序调用(CPI)
items: Solana基础 - 在链上读取另一个锚点程序账户数据
items: Solana基础 - 在 Anchor 中：不同类型的账户

Solana基础 - Solana RPC HTTP 方法
type: item
date: 2025-03-28T19:55:43+08:00
description: getAccountInfo 返回与提供的公钥的账户相关的所有信息,和以太坊 eth_getAccount类似 curl https://api.devnet.solana.com -s -X \ POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5", { "encoding": "base58" } ] } ' 返回 { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "2.2.3", "slot": 370321974 }, "value": { "data": [ "", "base58" ], "executable": false, "lamports": 4994414840, "owner": "11111111111111111111111111111111", "rentEpoch": 18446744073709551615, "space": 0 } }, "id": 1 } getBalance 返回所提供公钥的账户的 Lamport 余额, 和以太坊eth_getBalance类似 curl https://api.devnet.solana.com -s -X \ POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5" ] } ' 返回 { "jsonrpc": "2.0", "result": { "context": { "apiVersion": "2.2.3", "slot": 370322177 }, "value": 4994414840 }, "id": 1 } getBlock 返回账本中已确认区块的身份和交易信息，和以太坊 eth_getBlockByNumber类似 curl https://api.devnet.solana.com -s -X \ POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "getBlock", "params": [ 358300848, { "encoding": "json", "maxSupportedTransactionVersion": 0, "transactionDetails": "full", "rewards": false } ] } ' 如果类似如下提示，这通常意味着你尝试访问的特定槽（Slot）存在问题，具体可能是该槽被跳过或者在长期存储中缺失 { "jsonrpc": "2.0", "error": { "code": -32009, "message": "Slot 100000430 was skipped, or missing in long-term storage" }, "id": 1 } { "jsonrpc": "2.0", "result": { "blockHeight": 346328196, "blockTime": 1738488135, "blockhash": "HGrdL4Z5vEvQVWchG2pnJEQ5dMxQhqiXncpL6GJvZimx", "parentSlot": 358300847, "previousBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ", "transactions": [ ... { "meta": { "computeUnitsConsumed": 2100, "err": null, "fee": 5000, "innerInstructions": [], "loadedAddresses": { "readonly": [], "writable": [] }, "logMessages": [ "Program Vote111111111111111111111111111111111111111 invoke [1]", "Program Vote1111111111111…

solana 交易 confirmationStatus 有几种状态
type: item
date: 2025-03-28T16:01:00+08:00
description: 在 Solana 中，交易的 confirmationStatus 用于表示交易的确认状态，主要有以下几种状态： 1. finalized 含义 ：这是最高级别的确认状态。当交易达到 finalized 状态时，意味着该交易已经被集群中的大多数验证节点确认，并且不会被回滚。在区块链的共识机制下，这是一个非常安全的状态，表明交易已经被永久记录在区块链上。 应用场景 ：在需要确保交易不可逆转的场景中，如涉及资金转移、重要数据记录等，通常会等待交易达到 finalized 状态。 2. confirmed 含义 ：表示交易已经被集群中的一个验证节点确认。虽然交易处于 confirmed 状态，但仍然存在一定的风险，因为在某些情况下，该交易可能会被回滚。不过，这种情况相对较少发生。 应用场景 ：在一些对交易确认速度要求较高，而对交易最终确定性要求不是特别严格的场景中，可以使用 confirmed 状态。例如，在一些实时性要求较高的游戏或交互场景中，当用户完成操作后，可以快速告知用户交易已 confirmed ，让用户继续后续操作。 3. processed 含义 ：表示交易已经被集群接收并处理，但尚未被任何验证节点确认。这是交易确认过程中的初始状态，意味着交易已经进入了集群的处理流程，但还没有得到足够的确认。 应用场景 ：在开发过程中，当需要快速反馈交易是否已经被集群接收时，可以使用 processed 状态。例如，在用户提交交易后，立即告知用户交易已被接收并正在处理中。 代码示例 在使用 @solana/web3.js 进行交易时，可以指定 confirmationStatus 参数。以下是一个示例代码： const { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } = require('@solana/web3.js'); // 连接到 Solana 网络 const connection = new Connection('https://api-devnet.solana.com', 'confirmed'); // 生成发送方和接收方的密钥对 const fromKeypair = Keypair.generate(); const toPublicKey = new PublicKey('...'); // 替换为实际的接收方公钥 // 创建交易 const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: fromKeypair.publicKey, toPubkey: toPublicKey, lamports: 1 * LAMPORTS_PER_SOL, }) ); // 发送并确认交易，指定 confirmationStatus 为 'finalized' sendAndConfirmTransaction(connection, transaction, [fromKeypair], { commitment: 'finalized' }) .then((signature) => { console.log(`交易已成功发送，签名: ${signature}`); }) .catch((error) => { console.error('转账过程中出现错误:', error); }); 在上述代码中， commitment 参数指定了交易的确认状态，这里设置为 finalized ，表示等待交易达到最终确认状态。你可以根据实际需求将其设置为 confirmed 或 processed 。

Solana RPC 方法的常见 JSON 数据结构
type: item
date: 2025-03-28T11:08:32+08:00
description: 各种 Solana RPC 方法将返回更复杂的响应作为结构化 JSON 对象，并填充特定的键值。 这些 JSON 数据结构中最常见的包括： 交易 内在指示 代币余额 交易 交易与其他区块链上的交易截然不同。请务必查看交易剖析以了解 Solana 上的交易。 JSON 交易的JSON结构定义如下： "transaction": { "message": { "accountKeys": [ "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR", "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY", "11111111111111111111111111111111" ], "header": { "numReadonlySignedAccounts": 0, "numReadonlyUnsignedAccounts": 1, "numRequiredSignatures": 1 }, "instructions": [ { "accounts": [ 0, 1 ], "data": "3Bxs411Dtc7pkFQj", "programIdIndex": 2, "stackHeight": null } ], "recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec" }, "signatures": [ "2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh" ] } message: - 定义交易的内容。 accountKeys: <array[string]>- 交易使用的 base-58 编码公钥列表，包括指令和签名。第一个 message.header.numRequiredSignatures公钥必须签署交易。 header: - 详细说明交易所需的账户类型和签名。 numRequiredSignatures: <number>- 使交易有效所需的签名总数。签名必须与第一个numRequiredSignatures签名相匹配message.accountKeys。 numReadonlySignedAccounts: <number>- 最后 numReadonlySignedAccounts一个签名密钥是只读帐户。程序可以处理在单个 PoH 条目中加载只读帐户的多个交易，但不允许贷记或借记 lamport 或修改帐户数据。针对同一读写帐户的交易将按顺序进行评估。 numReadonlyUnsignedAccounts: <number>- 最后一个 numReadonlyUnsignedAccounts未签名的密钥是只读账户。 recentBlockhash: <string>- 账本中最近一个区块的 Base58 编码哈希值，用于防止交易重复并提供交易生命周期。 instructions: <array[object]> - 将按顺序执行并在一个原子事务中提交的程序指令列表（如果全部成功）。 programIdIndex: <number>- 数组索引，message.accountKeys指示执行该指令的程序账户。 accounts: <array[number]>- 数组中有序索引的列表， message.accountKeys指示要传递给程序的帐户。 data: <string>- 程序输入以 base-58 字符串编码的数据。 addressTableLookups: <array[object]|undefined> - 交易使用的地址表查找列表，用于从链上地址查找表中动态加载地址。如果maxSupportedTransactionVersion 未设置，则为未定义。 accountKey: <string>- 用于地址查找表帐户的 base-58 编码公钥。 writableIndexes: <array[number]>- 用于从查找表加载可写帐户地址的索引列表。 readonlyIndexes: <array[number]>- 用于从查找表加载只读帐户地址的索引列表。 signatures: <array[string]>- 应用于交易的 base-58 编码签名列表。列表的长度始终为 message.header.numRequiredSignatures且不为空。索引处的签名 对应于中i索引处的公钥。第一个用作 交易 ID。i``message.accountKeys JSON 解析 交易的 JSON 解析结构遵循与…

使用 spl-token 命令行工具测试Solana代币发行
type: item
date: 2025-03-28T10:41:06+08:00
description: 安装 CLI 安装Solana CLI sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" 添加环境变量 export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" sudo vi /etc/profile export PATH=$PATH:/usr/local/go/bin source /etc/profile 设置集群 solana config set --url https://api.devnet.solana.com 领取测试代币 solana airdrop 5 创建代币 spl-token create-token 返回 Creating token 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA Address: 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT Decimals: 9 Signature: adBFA3wdYahsRPZGWuHhFMSZ83D7Znr9M5D7SKtif31ArQWRS5HdzXa1ifEbtNZHNRmnPSce91R96fK9BdE6PwP https://explorer.solana.com/tx/adBFA3wdYahsRPZGWuHhFMSZ83D7Znr9M5D7SKtif31ArQWRS5HdzXa1ifEbtNZHNRmnPSce91R96fK9BdE6PwP?cluster=devnet 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 是部署时临时创建的一个Keypair,这个作为当前创建Token的唯一标识符 8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5 是当前部署的地址， js代码 import { createMint } from '@solana/spl-token'; import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js'; const payer = Keypair.generate(); const mintAuthority = Keypair.generate(); const freezeAuthority = Keypair.generate(); const connection = new Connection( clusterApiUrl('devnet'), 'confirmed' ); const mint = await createMint( connection, payer, mintAuthority.publicKey, freezeAuthority.publicKey, 9 // We are using 9 to match the CLI decimal default exactly ); console.log(mint.toBase58()); // 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 刚部署完，没有供应量，因为还没有mint spl-token supply 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT js代码 const mintInfo = await getMint( connection, mint ) console.log(mintInfo.supply); // 0 创建代币持有ATA账户 spl-token create-account 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 返回 Creating account 3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2 Signature: 3jK2bbob5JqpAiXxUFTqDfKJZMZ5kpgUASVUNaWi8dp3N9bsZPEXba2rB4E9KUyUUYCmAFuYQwB7uRXGQihQfvG7 https://solscan.io/tx/3jK2bbob5JqpAiXxUFTqDfKJZMZ5kpgUASVUNaWi8dp3N9bsZPEXba2rB4E9KUyUUYCm…

error: rustc 1.79.0-dev is not supported by the following package:
type: item
date: 2025-03-26T10:40:38+08:00
description: anchor build时报以下错误 info: uninstalling toolchain 'solana' info: toolchain 'solana' uninstalled error: rustc 1.79.0-dev is not supported by the following package: Note that this is the rustc version that ships with Solana tools and not your system's rustc version. Use `solana-install update` or head over to https://docs.solanalabs.com/cli/install to install a newer version. bytemuck_derive@1.9.2 requires rustc 1.84 Either upgrade rustc or select compatible dependency versions with `cargo update <name>@<current-ver> --precise <compatible-ver>` where `<compatible-ver>` is the latest version supporting rustc 1.79.0-dev 执行以下命令 cargo update -p bytemuck_derive --precise 1.7.0

Solana基础 - 十分钟开始使用多签工具SQUADS
type: item
date: 2025-03-26T09:07:00+08:00
description: 十分钟开始使用多签工具SQUADS 关于squads Squads是Solana区块链上领先的多重签名(multisig)解决方案，已经为超过100亿美元的资产提供安全保障。它提供直观的用户界面和完善的安全机制，让团队能够安全地管理数字资产、程序升级权限和验证者节点。作为一个去中心化的自托管工具，Squads支持多种功能，包括资金库管理、代币发行、NFT操作以及与Solana生态系统中其他DeFi应用的集成。其核心特点是要求多方签名才能执行交易，大大提升了资产安全性，特别适合DAO组织和Web3项目团队使用。 什么是多重签名(multisig) 多重签名(Multisig)是一种数字资产安全管理机制，要求多个预设的密钥持有者共同授权才能执行某项操作。与传统的单一签名不同，多重签名通常设置为"M-of-N"模式，即在N个授权者中需要至少M个授权者同意才能完成交易。例如，在3-of-5的多重签名设置中，需要5个授权者中的至少3个同意才能执行交易。这种机制有效防止了单点故障，即使某个密钥丢失或被盗，资产仍然是安全的。 快速开始 接下来我们会有一个简单的教程展示如何使用Typescript在测试网上与SQUADS 协议交互。 首先，创建目录和文件结构： mkdir squads_quickstart cd squads_quickstart 创建 tsconfig.json: { "compilerOptions": { "module": "commonjs", "target": "es6", "esModuleInterop": true } } 创建 package.json: { "scripts": { "test": "npx mocha -r ts-node/register 'main.ts' --timeout 10000" }, "dependencies": { "@solana/web3.js": "^1.73.0", "@sqds/multisig": "^2.1.3" }, "devDependencies": { "@types/chai": "^4.3.3", "@types/mocha": "^10.0.6", "chai": "^4.3.6", "mocha": "^10.3.0", "ts-mocha": "^10.0.0", "typescript": "^4.8.3" } } 创建main.ts 本文将会带领读者创建一个脚本逐步进行如下几步，创建多签，提出一个新的转账，进行投票并且执行。 首先， 设置多签成员并且创建地址 describe("Interacting with the Squads V4 SDK", () => { const creator = Keypair.generate(); const secondMember = Keypair.generate(); before(async () => { const airdropSignature = await connection.requestAirdrop( creator.publicKey, 1 * LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); }); const createKey = Keypair.generate(); // 从多签账户中派生PDA const [multisigPda] = multisig.getMultisigPda({ createKey: createKey.publicKey, }); it("Create a new multisig", async () => { const programConfigPda = multisig.getProgramConfigPda({})[0]; console.log("Program Config PDA: ", programConfigPda.toBase58()); const programConfig = await multisig.accounts.ProgramConfig.fromAccountAddress( connection, programConfigPda ); const configTreasury = programConfig.treasury; // 生成多签 const signature = await multisig.rpc.multisigCreateV2({ connection, //一次性密钥 createKey, // 创建者和费用支付这 creator, multis…

Solana基础 - 给EVM开发者的 Solana开发指南
type: item
date: 2025-03-25T10:44:00+08:00
description: 本文介绍了EVM开发者如何转向Solana平台，包括Solana的架构、技术优势、开发工具及账户模型的不同，强调程序的无状态特性以及数据存储的外部化。同时，文章比较了Ethereum和Solana的交易处理模型、费用机制及开发工具，帮助开发者顺利过渡。 概述 以太坊虚拟机（EVM）多年来一直是去中心化应用（dApp）开发的基础，但 Solana 的创新架构提供了诸如高吞吐量、低延迟和成本效率等独特优势。本指南旨在为希望在 Solana 上构建应用的 EVM 开发者设计。通过利用你对以太坊和 EVM 的现有知识，本指南将帮助你理解 Solana 的关键概念、工具和工作流程。 在本指南结束时，你将对 Solana 的架构有一个清晰的理解，并了解其与以太坊的不同之处。你将学习账户、执行模型和交易处理等关键概念如何与 EVM 进行比较，帮助你将开发思维从基于 Solidity 的智能合约转变为 Solana 的无状态 Rust 程序。 你将做什么 理解 EVM 和 SVM 之间的关键差异，包括执行模型、账户结构和费用机制 学习在 Solana 上如何存储和访问数据，与以太坊的存储模型进行比较 探索两个网络的交易处理模型，以理解执行效率 将你的思维方式从基于 Solidity 的智能合约开发转向基于 Rust 的 Solana 程序及其基于账户的架构 你需要什么 以太坊智能合约开发（Solidity、Hardhat、Foundry）经验 基本 EVM 概念（交易、存储、gas 费用、合约执行）的知识 不需要有 Rust 或 Solana 的先前经验。我们将以适合以太坊开发者的方式解释关键差异。 为什么选择 Solana？ 作为以太坊开发者，探索 Solana 提供了独特的机会，得益于其技术优势和相当明显的生态系统增长。 技术优势 高吞吐量的并行执行：Solana 利用 Sealevel，一个并行执行引擎，允许多个交易同时处理，而不是以太坊的顺序执行模型。这使得在没有网络拥堵的情况下实现更高的吞吐量。 无状态程序：与以太坊智能合约不同，后者存储自己的状态，Solana 程序是无状态的，并通过外部账户进行数据存储。这种代码和状态的分离提高了效率并减少了链上存储成本。 单体设计：与以太坊的模块化路线图不同，Solana 旨在原生扩展，无需依赖 rollup 或分片。这意味着更简单的开发、更少的跨链复杂性、没有流动性分散，更好的用户体验。 成本效率：Solana 的 Layer 1 架构被设计为低交易费用，为开发者和用户提供了经济可行的环境，而无需依赖附加的扩展解决方案。 生态系统增长与开发者机会 链 GDP 增长：在 2024 年第四季度，Solana 的总应用收入（称为链 GDP）环比增长 213%，从 2.68 亿美元增长到 8.4 亿美元。值得注意的是，仅 11 月，当月的应用收入就达到了 3.67 亿美元。 DeFi 总锁定价值（TVL）：在 2024 年第四季度末，Solana 成为第二大 DeFi TVL 区块链，达到 86 亿美元，标志着环比增长 213%。 去中心化交易所（DEX）主导地位：Raydium 作为 Solana 的一家领先 DEX，在 2024 年第四季度的平均日交易量达 32 亿美元，较去年大幅增长。Raydium 在全球 DEX 交易量的份额也环比上升超过 66%，使其成为按交易量计算的第二大 DEX，仅次于 Uniswap。 流动性质押增长：Solana 的流动性质押率环比上升 33%，达到了 11.2%。作为一家领先的流动性质押提供商，Sanctum 现在支持超过 100 种流动性质押 Token（LST）。 网络的技术能力，加上不断扩展的生态系统，使其成为开发者的一个吸引人选择。通过学习如何在 Solana 上构建应用，你可以利用这些机会并为网络的增长做出贡献。 如需详细分析，请参考 Messari 的 State of Solana Q4 2024 报告。 以太坊与 Solana 之间的关键架构差异 在开始之前，熟悉以太坊和 Solana 的术语差异至关重要。 以太坊与 Solana：术语对比表 以太坊 (EVM) Solana (SVM) 描述 智能合约 程序 在以太坊中，智能合约存储逻辑和状态。在 Solana 中，程序是包含可执行代码但无状态的账户，数据存储在单独的账户中。 Wei Lamports 在以太坊中，wei是ETH的最小单位，其中1 ETH = 10¹⁸ wei。在 Solana 中，lamports是SOL的最小单位，其中1 SOL = 1,000,000,000 lamports。 Gas 计算单位 在以太坊中，Gas 测量交易所需的计算工作量，其中费用 = gas 使用量 ×（…

Solana基础 - 在 Anchor 中的跨程序调用(CPI)
type: item
date: 2025-03-25T10:42:45+08:00
description: 跨程序调用 (CPI) 是 Solana 的术语，用于描述一个程序调用另一个程序的公共函数。 我们之前已经进行过 CPI，当我们向系统程序发送一个 转账 SOL 交易。以下是相关代码片段，以作提醒： pub fn send_sol(ctx: Context<SendSol>, amount: u64) -> Result<()> { let cpi_context = CpiContext::new( ctx.accounts.system_program.to_account_info(), system_program::Transfer { from: ctx.accounts.signer.to_account_info(), to: ctx.accounts.recipient.to_account_info(), } ); let res = system_program::transfer(cpi_context, amount); if res.is_ok() { return Ok(()); } else { return err!(Errors::TransferFailed); } } Cpi 在 CpiContext 中字面上的意思是“跨程序调用”。 调用除系统程序外的其他程序的公共函数的工作流程并没有太大的不同——我们将在本教程中教授这一点。 本教程只关注如何调用另一个使用 Anchor 构建的 Solana 程序。如果其他程序是用纯 Rust 开发的，则以下指南将无法使用。 在我们的示例中，Alice 程序将调用 Bob 程序上的一个函数。 Bob 程序 我们从使用 Anchor 的 CLI 创建一个新项目开始： anchor init bob 然后在 bob/lib.rs 中复制并粘贴下面的代码。该账户有两个函数，一个用于初始化存储一个 u64 的账户，另一个函数 add_and_store 接受两个 u64 变量，将它们相加并存储在由结构体 BobData 定义的账户中。 use anchor_lang::prelude::*; use std::mem::size_of; // 替换为你的 <PROGRAM_ID> declare_id!("8GYu5JYsvAYoinbFTvW4AACYB5GxGstz21FmZe3MNFn4"); #[program] pub mod bob { use super::*; pub fn initialize(ctx: Context<Initialize>) -> Result<()> { msg!("数据账户已初始化: {}", ctx.accounts.bob_data_account.key()); Ok(()) } pub fn add_and_store(ctx: Context<BobAddOp>, a: u64, b: u64) -> Result<()> { let result = a + b; // 修改/更新数据账户 ctx.accounts.bob_data_account.result = result; Ok(()) } } #[account] pub struct BobData { pub result: u64, } #[derive(Accounts)] pub struct BobAddOp<'info> { #[account(mut)] pub bob_data_account: Account<'info, BobData>, } #[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = signer, space = size_of::<BobData>() + 8)] pub bob_data_account: Account<'info, BobData>, #[account(mut)] pub signer: Signer<'info>, pub system_program: Program<'info, System>, } 本教程的目标是创建另一个程序 alice，调用 bob.add_and_store。 在项目（bob）内，使用 anchor new 命令创建一个新程序： anchor new alice 命令行将打印出 创建了新程序。 在开始编写 Alice 的程序之前，必须将下面的代码片段添加到 Alice 的 Cargo.toml 文件的 [dependencies] 部分，位置为 programs/alice/Cargo.toml。 [dependencies] bob = {path = …

Solana基础 - 在链上读取另一个锚点程序账户数据
type: item
date: 2025-03-25T10:28:35+08:00
description: 本文详细介绍了在Solana链上程序中如何读取不属于自己的账户数据，通过创建data_holder和data_reader两个程序，展示了如何初始化并读取PDA中的数据，并探讨了Anchor框架下的数据反序列化机制及其限制。 在 Solidity 中，读取另一个合约的存储需要调用 view 函数或者存储变量是公共的。在 Solana 中，离线客户端可以直接读取存储账户。这个教程展示了一个链上 Solana 程序如何读取它不拥有的账户中的数据。 我们将设置两个程序： data_holder 和 data_reader。 data_holder 将初始化并拥有一个 PDA，其数据将被 data_reader 读取。 设置存储数据的 data_holder 程序: Shell 1 以下代码是一个基本的 Solana 程序，它初始化账户 Storage，其中包含 u64 字段 x，并在初始化时将值 9 存储在其中： Typescript 代码： import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { DataHolder } from "../target/types/data_holder"; describe("data-holder", () => { anchor.setProvider(anchor.AnchorProvider.env()); const program = anchor.workspace.DataHolder as Program<DataHolder>; it("Is initialized!", async () => { const seeds = []; const [storage, _bump] = anchor.web3.PublicKey.findProgramAddressSync( seeds, program.programId ); await program.methods .initialize() .accounts({ storage: storage }) .rpc(); let storageStruct = await program.account.storage.fetch( storage ); console.log("The value of x is: ",storageStruct.x.toString()); console.log("Storage account address: ", storage.toBase58()); }); }); 测试将打印出 PDA 的地址，我们稍后会提到这个地址： 读取器 为了让 data_reader 读取另一个账户，必须通过 Context 结构将该账户的公共密钥作为交易的一部分传递。这与传递任何其他类型的账户没有区别。 账户中的数据以序列化字节的形式存储。 为了反序列化账户，data_reader 程序需要读取它的 Rust 结构定义。我们需要以下账户定义，并且它与 data_holder 中的 Storage 结构完全相同： #[account] pub struct Storage { x: u64, } 这个结构与 data_reader 中的结构完全相同——连名称也必须相同（稍后我们会介绍为什么）。读取账户的代码在以下两行中： let mut data_slice: &[u8] = &data_account.data.borrow(); let data_struct: Storage = AccountDeserialize::try_deserialize( &mut data_slice, )?; data_slice 是账户中数据的原始字节。如果你运行 solana account <pda address>（使用我们在部署 data_holder 时生成的 PDA 地址），你可以在那里看到数据，包括我们储存在红框中的数字 9： 黄框中前 8 个字节是账户鉴别符，稍后我们将对此进行描述。 反序列化发生在此步骤： let data_struct: Storage = AccountDeserialize::try_deserialize( &mut data_slice, )?; 在这里传递类型 Storage（我们上面定义的结构），告诉 Solana 如何（尝试）反序列化数据。 现在让我们在新文件夹中创建一个单独的 anchor 项目 anchor new data_reader。 完整的 Rust 代码如下： use anchor_lang::pr…

Solana基础 - 在 Anchor 中：不同类型的账户
type: item
date: 2025-03-25T10:14:57+08:00
description: 本文详细介绍了 Solana Anchor 框架中的 [derive(Accounts)] 宏，解释了 Solana 并行交易处理机制及其账户访问控制的重要性，并深入探讨了 Account、UncheckedAccount、Signer 和 Program 四种账户类型的使用场景和实现细节。 Anchor中的#[derive(Accounts)]：不同类型的账户 #[derive(Accounts)]在Solana Anchor中是一个类似属性的宏，用于包含所有在执行期间将被函数访问的账户的引用。 在Solana中，交易将要访问的每个账户必须提前指定 Solana之所以如此快速的一个原因是它以并行的方式执行交易。也就是说，如果Alice和Bob都想进行一笔交易，Solana将尝试同时处理他们的交易。然而，如果他们的交易因访问同一存储而发生冲突，就会出现问题。例如，假设Alice和Bob都在尝试写入同一账户。显然，他们的交易不能并行执行。 为了让Solana知道Alice和Bob的交易不能并行处理，Alice和Bob都必须提前指定所有他们的交易将更新的账户。 由于Alice和Bob都指定了一个（存储）账户，Solana运行时可以推断出两个交易之间存在冲突。必须选择一个（推测是支付了更高优先级费用的那个），另一个最终将失败。 这就是为什么每个函数都有自己单独的#[derive(Accounts)]结构体。结构体中的每个字段都是程序在执行期间打算（但并不要求）访问的账户。 一些以太坊开发者可能会注意到这一要求与EIP 2930访问列表交易的相似性。 账户的类型向Anchor信号你打算如何与该账户交互。 最常用的账户类型：Account、Unchecked Account、System Program和Signer 在我们初始化存储的代码中，我们看到了三种不同的“类型”账户： Account Signer Program 这里是代码片段： 当我们读取账户余额时，我们看到了第四种类型： UncheckedAccount 这里是我们使用的代码： 我们用绿色框标出的每个项目都是通过文件顶部的anchor_lang::prelude::*;引入的。 Account、UncheckedAccount、Signer和Program的目的是在继续之前对传入的账户进行某种检查，并且还提供与这些账户交互的函数。 我们将在接下来的部分进一步解释这四种类型。 Account Account类型会检查被加载的账户的所有者是否确实被程序拥有。如果所有者不匹配，则不会加载。这作为一种重要的安全措施，以防止意外读取程序未创建的数据。 在以下示例中，我们创建了一个密钥对账户，并尝试将其传递给foo。因为该账户不属于程序，所以交易失败。 Rust： use anchor_lang::prelude::*; declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs"); ##[program] pub mod account_types { use super::*; pub fn foo(ctx: Context<Foo>) -> Result<()> { // 我们不对账户SomeAccount做任何操作 Ok(()) } } ##[derive(Accounts)] pub struct Foo<'info> { some_account: Account<'info, SomeAccount>, } ##[account] pub struct SomeAccount {} Typescript: import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { AccountTypes } from "../target/types/account_types"; describe("account_types", () => { async function airdropSol(publicKey, amount) { let airdropTx = await anchor .getProvider() .connection.requestAirdrop( publicKey, amount * anchor.web3.LAMPORTS_PER_SOL ); await confirmTransaction(airdropTx); } async function confirmTransaction(tx) { const latestBlockHash = aw…