本节将探讨 Solana 中的日志和事件日志,以及如何查询历史交易。
Solana 程序可以发出类似 Ethereum 事件的事件日志,但其设计和用途有所不同。本节将探讨 Solana 中的日志和事件日志,以及如何查询历史交易。
以下程序定义了两个事件日志:MyEvent 和 MySecondEvent,类似于 Ethereum 事件通过参数传递数据,Solana 使用结构体字段:
use anchor_lang::prelude::*;
declare_id!("8kSqq6R5YL4ZudFtnRH4xeRSP8xQNFdS4naWxunR5RKh");
#[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 中:
"events": [
{
"name": "MyEvent",
"fields": [
{
"name": "value",
"type": "u64",
"index": false
}
]
},
{
"name": "MySecondEvent",
"fields": [
{
"name": "value",
"type": "u64",
"index": false
},
{
"name": "message",
"type": "string",
"index": false
}
]
}
]
与 Ethereum 不同,Solana 没有“索引”或“非索引”字段的概念。
Solana 事件日志无法像 Ethereum 那样查询历史记录,只能实时监听。以下是监听示例:
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("Is initialized!", async () => {
const listenerMyEvent = program.addEventListener('MyEvent', (event, slot) => {
console.log(`slot ${slot} event value ${event.value}`);
});
const listenerMySecondEvent = program.addEventListener('MySecondEvent', (event, slot) => {
console.log(`slot ${slot} event value ${event.value} event message ${event.message}`);
});
await program.methods.initialize().rpc();
await new Promise((resolve) => setTimeout(resolve, 5000));
program.removeEventListener(listenerMyEvent);
program.removeEventListener(listenerMySecondEvent);
});
});
输出示例:
slot 4 event value 42
slot 4 event value 3 event message hello world
✔ Is initialized! (5264ms)
Solana 不支持扫描历史日志,必须在交易发生时监听。
Ethereum 通过 log0、log1 等操作码发出日志,而 Solana 使用系统调用 sol_log_data,接受字节序列作为参数(详见 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 在幕后将其序列化为字节传递给 sol_log_data。
Ethereum 的日志常用于审计,而 Solana 的日志仅限实时查询,更适合向前端传递信息。Solana 函数无法像 Solidity 的 view 函数那样返回数据,日志因此成为轻量级替代方案。事件日志会在区块浏览器中保留,例如 此交易。
Ethereum 无法直接按地址查询交易,需通过 eth_getTransactionCount、eth_getTransactionByHash 或 eth_getBlockByNumber 间接获取,审计依赖事件日志(见 Ethereum 事件)。
Solana 则提供 getSignaturesForAddress RPC 方法,可查询地址(程序或钱包)的所有交易。示例脚本:
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 = "enter and address here"; // 替换为实际地址
getTransactions(myAddress, 3);
说明:getParsedTransaction 用于获取交易详情。
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!