如何在 Solana 上使用查找表

  • QuickNode
  • 发布于 2024-09-26 10:11
  • 阅读 50

本文详细介绍了如何在Solana区块链上使用版本化交易和地址查找表,通过代码示例展示了如何创建、填充和使用地址查找表来优化交易大小。

概述

2022 年 10 月 10 日(Epoch 358),Solana 通过一个称为“版本化交易”的概念增加了对新交易版本类型的支持。在此更改之后,Solana 运行时现在支持两种类型的交易:“legacy”(旧交易)和“0”(包含地址查找表的交易)。地址查找表为开发者提供了一种新的方式,可以有效地将许多地址加载到交易中,因此如果你因账户问题而遇到存储大小问题,这可能会帮助你解决!

地址查找表程序(Program ID: AddressLookupTab1e1111111111111111111111111)允许你将公钥存储在链上查找表中,并在你的版本化交易中调用查找表。由于传输到 Solana 验证器的序列化交易不得超过 1,232 字节(源代码参考),利用查找表可以减少交易大小,并启用更复杂的交易指令(例如,更多的账户,更多的跨程序调用等)。Solana 查找表“有效地将 32 字节的地址‘压缩’为 1 字节的索引值”(来源)。这意味着通过使用查找表,我们的交易大小将更小(或者我们可以在交易中打包更多内容)!

你将做什么

在本指南中,你将:

  • 创建并执行版本 0(V0)交易,

  • 创建并填充地址查找表,

  • 比较两个几乎相同的交易的交易大小(一个使用查找表,一个不使用)。

如果你需要帮助确保你现有的客户端应用程序可以支持版本化交易,请查看我们的指南:如何更新你的 Solana 客户端以处理版本化交易。

你将需要什么

如果你是从我们之前的指南:如何在 Solana 上使用版本化交易过来的,你可以重用你的 app.ts 并跳到组装版本 0 交易部分。否则,请设置你的项目:

设置你的项目

在终端中创建一个新的项目目录,使用以下命令:

mkdir solana-versioned-tx
cd solana-versioned-tx

为你的应用程序创建一个文件,app.ts

echo > app.ts

使用“yes”标志初始化你的项目,以使用新包的默认值:

yarn init --yes
# 或
npm init --yes

创建一个启用了 .json 导入的 tsconfig.json

tsc -init --resolveJsonModule true

安装 Solana Web3 依赖项

我们需要为本次练习添加 Solana Web3 库。在终端中输入:

yarn add @solana/web3.js@1
# 或
npm install @solana/web3.js@1

让我们开始吧。

设置你的应用程序

导入必要的依赖项

打开 app.ts,并在第 1 行粘贴以下导入:

import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, TransactionSignature, TransactionConfirmationStatus, SignatureStatus } from '@solana/web3.js';

我们从 Solana Web3 库中导入了一些基本的方法和类。可能有一些导入是你以前没有见过的(例如,AddressLookupTableProgramVersionedTransaction)——我们将在本指南的后面部分介绍这些内容。

创建一个钱包并空投 SOL

我们需要完成的第一项任务是创建一个带有钱包的账户并为其提供资金。我们将使用下面的方便工具自动生成一个新钱包并向其空投 1 SOL。(如果你更喜欢手动操作,也可以使用 Keypair.generate()requestAirdrop() 函数来实现这一点)。

🔑生成一个带有 Devnet SOL 的新钱包

创建新钱包

一旦你成功生成了你的密钥对,你会注意到两个新的常量:secretSIGNER_WALLET,一个 Keypair。secret 是一个 32 字节的数组,用于生成公钥和私钥。SIGNER_WALLET 是一个 Keypair 实例,用于签署交易(我们已经空投了一些 devnet SOL 来支付 gas 费用)。如果还没有,请确保将其添加到你的代码中,放在其他常量下面。

在你的导入下面,粘贴你的新 secret,并添加:

const secret = [0,...,0]; // 👈 替换为你的 secret
const SIGNER_WALLET = Keypair.fromSecretKey(new Uint8Array(secret));
const DESTINATION_WALLET = Keypair.generate();
//const LOOKUP_TABLE_ADDRESS = new PublicKey(""); // 我们稍后会添加这个

我们定义了两个钱包:SIGNER_WALLET 将向我们的 DESTINATION_WALLET 发送 SOL。我们还添加了一个常量 LOOKUP_TABLE_ADDRESS,稍后我们将更新它以引用我们的查找表的链上账户 ID。

设置你的 QuickNode 端点

要在 Solana 上构建,你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理自己的基础设施;但是,如果你希望获得 8 倍更快的响应时间,可以将繁重的工作交给我们。

了解为什么超过 50% 的 Solana 项目选择 QuickNode 并在此注册一个免费账户。我们将使用 Solana Devnet 节点。

复制 HTTP Provider 链接:

app.ts 中,在你的导入语句下,声明你的 RPC 并建立与 Solana 的连接

const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);

你的环境应该看起来像这样:

好了,让我们开始构建吧!

组装版本 0 交易

要使用查找表,你必须使用版本 0 交易。如果你不熟悉如何构建和执行版本化交易,请查看我们的指南,这里

让我们从创建一个新函数 createAndSendV0Tx 开始,它接受一个 TransactionInstructions 数组,txInstructions

async function createAndSendV0Tx(txInstructions: TransactionInstruction[]) {
    // 步骤 1 - 获取最新的区块哈希
    let latestBlockhash = await SOLANA_CONNECTION.getLatestBlockhash('finalized');
    console.log("   ✅ - 获取了最新的区块哈希。最后有效高度:", latestBlockhash.lastValidBlockHeight);

    // 步骤 2 - 生成交易消息
    const messageV0 = new TransactionMessage({
        payerKey: SIGNER_WALLET.publicKey,
        recentBlockhash: latestBlockhash.blockhash,
        instructions: txInstructions
    }).compileToV0Message();
    console.log("   ✅ - 编译了交易消息");
    const transaction = new VersionedTransaction(messageV0);

    // 步骤 3 - 使用所需的 `Signers` 签署你的交易
    transaction.sign([SIGNER_WALLET]);
    console.log("   ✅ - 交易已签署");

    // 步骤 4 - 将我们的 v0 交易发送到集群
    const txid = await SOLANA_CONNECTION.sendTransaction(transaction, { maxRetries: 5 });
    console.log("   ✅ - 交易已发送到网络");

    // 步骤 5 - 确认交易
    const confirmation = await confirmTransaction(SOLANA_CONNECTION, txid);
    if (confirmation.value.err) { throw new Error("   ❌ - 交易未确认。") }
    console.log('🎉 交易成功确认!', '\n', `https://explorer.solana.com/tx/${txid}?cluster=devnet`);
}

让我们逐步了解我们的代码:

  • 步骤 1:我们从网络中获取最新的区块哈希。注意:我们传递参数 'finalized' 以确保该区块不属于已丢弃的分叉。
  • 步骤 2:使用我们的 txInstructions 参数和 latestBlockhash,我们通过构建一个 Message 并执行 .compileToV0Message() 方法来创建一个新的 MessageV0
  • 步骤 3:我们使用一个签名者数组签署交易。在这种情况下,它只是我们的 SIGNER_WALLET
  • 步骤 4:我们使用 sendTransaction 将交易发送到集群,它将返回一个交易签名/ID。
  • 步骤 5:我们等待集群确认交易是否成功。如果成功,我们记录我们的浏览器 URL;否则,我们抛出一个错误。

让我们定义我们在 createAndSendV0Tx 函数中调用的 confirmTransaction 函数。将以下代码添加到你的 app.ts 文件中:

async function confirmTransaction(
    connection: Connection,
    signature: TransactionSignature,
    desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed',
    timeout: number = 30000,
    pollInterval: number = 1000,
    searchTransactionHistory: boolean = false
): Promise<SignatureStatus> {
    const start = Date.now();

    while (Date.now() - start < timeout) {
        const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory });

        if (!statuses || statuses.length === 0) {
            throw new Error('Failed to get signature status');
        }

        const status = statuses[0];

        if (status === null) {
            // 如果状态为 null,交易尚未被知晓
            await new Promise(resolve => setTimeout(resolve, pollInterval));
            continue;
        }

        if (status.err) {
            throw new Error(`交易失败: ${JSON.stringify(status.err)}`);
        }

        if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) {
            return status;
        }

        if (status.confirmationStatus === 'finalized') {
            return status;
        }

        await new Promise(resolve => setTimeout(resolve, pollInterval));
    }

    throw new Error(`交易确认超时,等待了 ${timeout}ms`);
}

这将简单地轮询 Solana 网络以获取交易状态,直到它被确认或达到超时。我们为超时和轮询间隔包含了一些默认值,但你可以根据需要调整它们。

好了!我们都准备好了。让我们添加一些指令。

创建地址查找表

创建一个新的异步函数 createLookupTable,它将构建我们的交易指令并调用 createAndSendV0Tx

async function createLookupTable() {
    // 步骤 1 - 获取一个查找表地址并创建查找表指令
    const [lookupTableInst, lookupTableAddress] =
        AddressLookupTableProgram.createLookupTable({
            authority: SIGNER_WALLET.publicKey,
            payer: SIGNER_WALLET.publicKey,
            recentSlot: await SOLANA_CONNECTION.getSlot(),
        });

    // 步骤 2 - 记录查找表地址
    console.log("查找表地址:", lookupTableAddress.toBase58());

    // 步骤 3 - 生成交易并将其发送到网络
    createAndSendV0Tx([lookupTableInst]);
}

分解我们的代码:

  • 步骤 1:我们通过解构 createLookupTable 方法的结果创建两个变量,lookupTableInstlookupTableAddress。该方法返回创建后表的公钥和一个可以传递到我们的 createAndSendV0Tx 函数中的 TransactionInstruction

  • 步骤 2:我们记录表的地址(在本练习中稍后我们将需要它)。

  • 步骤 3:最后,我们通过将 lookupTableInst 传递到数组中调用 createAndSendV0Tx 以匹配我们的类型要求。

太棒了!此时,你应该能够运行你的代码并创建一个空的查找表。在你的函数之后,通过添加以下内容来调用它:

createLookupTable();

然后在你的终端中输入:

ts-node app.ts

你应该会在终端中看到你的交易进展,并最终收到一个指向 Solana Explorer 上交易页面的 URL

我们的查找表账户地址是:3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw

干得好!你已经创建了你的第一个查找表。

在我们继续之前,让我们做一些清理工作:

  1. 删除你对 createLookupTable() 的调用。我们不再需要它。

  2. 还记得我们之前创建的 LOOKUP_TABLE_ADDRESS 常量吗?删除注释反斜杠,并将你的控制台中的查找表地址添加到你的 PublicKey 声明中,如下所示(这是我们的第 6 行):

const LOOKUP_TABLE_ADDRESS = new PublicKey("YOUR_TABLE_ADDRESS_HERE");
// 例如,const LOOKUP_TABLE_ADDRESS = new PublicKey("3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw");

向你的查找表添加地址

由于我们已经创建了 createAndSendV0Tx,向查找表添加地址很容易!我们需要做的就是创建一个 TransactionInstruction。创建一个新的异步函数 addAddressesToTable,它使用 AddressLookupTableProgram.extendLookupTable() 方法:

async function addAddressesToTable() {
    // 步骤 1 - 创建交易指令
    const addAddressesInstruction = AddressLookupTableProgram.extendLookupTable({
        payer: SIGNER_WALLET.publicKey,
        authority: SIGNER_WALLET.publicKey,
        lookupTable: LOOKUP_TABLE_ADDRESS,
        addresses: [\
            Keypair.generate().publicKey,\
            Keypair.generate().publicKey,\
            Keypair.generate().publicKey,\
            Keypair.generate().publicKey,\
            Keypair.generate().publicKey\
        ],
    });
    // 步骤 2 - 生成交易并将其发送到网络
    await createAndSendV0Tx([addAddressesInstruction]);
    console.log(`查找表条目: `,`https://explorer.solana.com/address/${LOOKUP_TABLE_ADDRESS.toString()}/entries?cluster=devnet`)

}

让我们分解一下 extendLookupTable 方法的作用:

  1. 我们传递我们的 SIGNER_WALLET 来支付交易费用和任何额外的租金。

  2. 我们定义我们的更新权限——在我们的例子中,我们在上面的表创建步骤中将其设置为 SIGNER_WALLET

  3. 我们传递查找表账户地址(我们将其定义为 LOOKUP_TABLE_ADDRESS)。

  4. 我们将一个地址数组传递到我们的查找表中。我们将传递一些随机的公钥,但你可以传递任何你喜欢的公钥!该程序的“压缩”支持在一个查找表中存储多达 256 个地址

  5. 让我们还在交易完成后记录一个指向我们查找表条目的链接以便于访问。最后,我们将我们的 TransactionInstruction 传递到 createAndSendV0Tx 中以生成交易并将其发送到网络!

在你的函数之后,通过添加以下内容来调用你的新函数:

addAddressesToTable();

运行你的代码——在终端中,使用键盘上的上箭头键检索之前的命令,ts-node app.ts,然后按 Enter 运行你的代码。

你应该在终端中看到类似的交易流程,并成功获得指向你的交易和你的查找表条目在 Solana Explorer 上的链接。转到查找表条目。你应该会看到你的表存储的所有公钥列表:

干得好!你可以修改你的 addresses 数组并重新运行它以向你的查找表添加额外的公钥。

在继续之前,删除你对 addAddressesToTable();

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

0 条评论

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