本文介绍了 Solana 中的日志和事件机制,解释了如何通过事件在前端传递信息,以及如何查询 Solana 的交易历史。与以太坊不同,Solana 的事件不能用于历史查询,而是更适合实时监听。
Solana 程序可以发出事件,类似于 Ethereum 发出事件,不过我们会讨论其中的一些差异。
具体而言,Solana 中的事件旨在将信息传递给前端,而不是记录过去的交易。如需获取历史记录,可以通过地址查询 Solana 交易。
下面的程序有两个事件:MyEvent
和 MySecondEvent
。与 Ethereum 事件有“参数”类似,Solana 事件在结构体中有字段:
use anchor_lang::prelude::*;
declare_id!("FmyZrMmPvRzmJCG3p5R1AnbkPqSmzdJrcYzgnQiGKuBq");
#[program]
pub mod emit {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent { value: 42 });
emit!(MySecondEvent { value: 3, message: "hello world".to_string() });
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
#[event]
pub struct MyEvent {
pub value: u64,
}
#[event]
pub struct MySecondEvent {
pub value: u64,
pub message: String,
}
事件成为 Solana 程序的 IDL 的一部分,类似于事件是 Solidity 智能合约 ABI 的一部分。下面我们截图了上面程序的 IDL,并高亮显示了相关部分:
在 Solana 中没有类似于 Ethereum 的“索引”和“非索引”信息(即使在上面的截图中有一个“索引”字段,它也没有用)。
与 Ethereum 不同,我们不能直接根据区块编号的范围查询过去的事件。我们只能在事件发生时进行监听。(稍后我们将看到 Solana 的审计过去交易的方法)。下面的代码展示了如何在 Solana 中监听事件:
import * as anchor from "@coral-xyz/anchor";
import { BorshCoder, EventParser, Program } from "@coral-xyz/anchor";
import { Emit } from "../target/types/emit";
describe("emit", () => {
// 配置客户端以使用本地集群。
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Emit as Program<Emit>;
it("已初始化!", async () => {
const listenerMyEvent = program.addEventListener('MyEvent', (event, slot) => {
console.log(`槽 ${slot} 事件值 ${event.value}`);
});
const listenerMySecondEvent = program.addEventListener('MySecondEvent', (event, slot) => {
console.log(`槽 ${slot} 事件值 ${event.value} 事件消息 ${event.message}`);
});
await program.methods.initialize().rpc();
// 这行仅用于测试,确保事件监听器有时间来监听事件。
await new Promise((resolve) => setTimeout(resolve, 5000));
program.removeEventListener(listenerMyEvent);
program.removeEventListener(listenerMySecondEvent);
});
});
在 Solana 中不能像在 Ethereum 中那样扫描过去的日志,它们必须在交易发生时进行监听。
在 EVM 中,日志通过运行 log0
、log1
、log2
等操作码发出。在 Solana 中,日志是通过调用系统调用 sol_log_data
来运行的。作为参数,简单地是一个字节序列:
<https://docs.rs/solana-program/latest/src/solana_program/log.rs.html#116-124>
以下是 Solana 客户端中系统调用的函数:
/// 将某些切片打印为 base64。
pub fn sol_log_data(data: &[&[u8]]) {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64)
};
#[cfg(not(target_os = "solana"))]
crate::program_stubs::sol_log_data(data);
}
我们用来创建事件的“结构体”结构是对字节序列的抽象。在后台,Anchor 将结构体转换为字节序列传递给此函数。Solana 系统调用仅接受字节序列,而不接受结构体。
在 Ethereum 中,日志用于审计目的,但在 Solana 中,日志不能以这种方式使用,因为它们只能在发生时被查询。因此,它们更适合将信息传递给前端应用程序。Solana 函数不能像 Solidity 查看函数那样返回数据给前端,所以 Solana 日志是一种轻量级的实现方式。
然而,事件会保存在区块浏览器中。请查看这个交易的底部作为例子:
<https://explorer.solana.com/tx/JgyHQPxL3cPLFtV4cx5i842ZgBx57R2fkNn2TZn1wsQZqVXKfijd43CEHo88C3ridK27Kw8KkMzfvDdqaS398SX>
在 Ethereum 中,没有直接的方法查询发往智能合约或特定钱包的交易。
我们可以通过 eth_getTransactionCount 计算从一个地址发送的交易数量。我们可以使用交易哈希通过 eth_getTransactionByHash 获取特定交易。我们可以使用 eth_getBlockByNumber 或 eth_getBlockByHash 获取特定区块中的交易。
但是,不可能按地址获取所有交易。这必须通过解析自钱包激活或智能合约部署以来的每个区块来间接完成。
为了审计智能合约中的交易,开发人员添加 智能合约事件 以查询感兴趣的交易。
另一方面,Solana 有一个 RPC 函数 getSignaturesForAddress,用于列出某个地址所做的所有交易。该地址可以是一个程序或一个钱包。
以下是一个列出某个地址的交易的脚本:
let web3 = require('@solana/web3.js');
const solanaConnection = new web3.Connection(web3.clusterApiUrl("mainnet-beta"));
const getTransactions = async(address,limit) => {
const pubKey = new web3.PublicKey(address);
let transactionList = await solanaConnection.getSignaturesForAddress(pubKey, {limit: limit});
let signatureList = transactionList.map(transaction => transaction.signature);
console.log(signatureList);
for await (const sig of signatureList) {
console.log(await solanaConnection.getParsedTransaction(sig, {maxSupportedTransactionVersion: 0}));
}
}
let myAddress = "请在此输入地址";
getTransactions(myAddress, 3);
请注意,交易的实际内容是通过 getParsedTransaction
RPC 方法获取的。
最初发布于 2024 年 2 月 20 日
- 原文链接: rareskills.io/post/solan...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!