- Message → 用户想要做什么(他们签署的提案)
- Meta → 实际发生了什么(执行结果)
<Buffer 00 bf a0 e8...>,而不是可读的地址和签名。
本指南将向您展示如何: 将这些二进制数据解码为人类可读的格式,提取有意义的信息,并理解从提案到执行的完整交易过程。
实时流,无需解码
运行下面的最小客户端。过滤器标志去除投票和失败的交易,accountInclude数组限制结果为涉及Jupiter程序ID的活动。
filters, createdAt加上一个transaction分支,隐藏了两个子项:
transaction.transaction.transaction→ 签名的消息transaction.transaction.meta→ 执行的元数据
Uint8Array的东西目前仍然不透明。
当您运行带有解码功能的脚本时,您将看到实际的嵌套结构和可读的地址:
解码二进制数据
为什么要解码? 原始Laserstream数据包含签名、账户密钥和作为二进制Uint8Array对象的哈希,这些不可读。你需要将这些转换为base58字符串,以便理解交易。
解决方案: Laserstream使用Yellowstone gRPC,它提供了内置的解码工具。我们使用一个递归函数将所有二进制数据转换为人类可读的格式,而不是为每种字段类型编写单独的解码器。
理解交易结构
现在我们可以看到解码后的数据,让我们来探索每个 Laserstream 交易更新的两个主要部分。记住在我们最初的例子中,每个交易包含两个关键对象:- 消息(提案) →
transaction.transaction.transaction→ 签名的消息(用户的提案) - 元数据(执行) →
transaction.transaction.meta→ 执行元数据(验证者的响应)
提案:消息中的所有内容
用户创建一个消息,指定什么,谁以及何时为止。以下是如何解码每个部分:交易头
numRequiredSignatures 告诉验证者要验证多少签名,而另外两个 numReadonly* 值标记运行时可以视为只读的账户,从而实现并行执行。
账户密钥字典
accountKeys是一个公共密钥列表,作为查找表。事务中的每个后续整数 - programIdIndex,每个指令中的每个元素accounts数组 - 通过索引指向此列表,从而每条消息节省超过一千字节。
防重放保护
recentBlockhash 一旦超出最近 150 个区块哈希的范围就会过期,在主网上大约需要 30 秒。
指令:实际命令
- 程序ID (
programIdIndex): 指向accountKeys数组中的地址(例如,索引10 =ComputeBudget111111111111111111111111111111) - 账户 (
accounts): base58编码的字符串,表示该指令接触的账户索引 - 数据 (
data): 实际的指令数据,编码为base58
convertBuffers函数,账户显示为base58,但实际上包含账户索引(例如,"3vtmrQMafzDoG2CBz1iqgXPTnC"解码为索引[21, 19, 12, 17, 2, 6, 1, 22])
这种设计意味着每个指令只需引用查找表中的位置,而不是重复完整的 32 字节地址。
签名:授权证明
signatures包含加密签名,证明所需账户授权了此交易。签名数量必须与header.numRequiredSignatures匹配。
地址表查找
versioned是true,addressTableLookups会出现一个链上表和两个索引列表。查找表可以在保持数据包在1,232字节MTU以下的同时,将地址数量的硬性限制提升到几十个。
交易 v1:将计算预算放在头部
交易 v1(SIMD-0385,Agave 4.2)向消息中添加了一个字段:transactionConfig。
instructions 数组中从不包含 ComputeBudget111111111111111111111111111111 条目。priorityFee 是整个交易的 lamports 总费用,而不是每个计算单元的微 lamports。如果没有设置 null 字段,则表示发送者没有设置。传统消息和 v0 消息没有 transactionConfig,因此其存在标识了 v1 交易。
在解码器中需要检查两点:
- 优先费用提取。 可读取
transactionConfig.priorityFee如果存在,并且仅对传统和 v0 交易回退扫描 ComputeBudget 指令。仅扫描指令的代码会读取每个 v1 交易的优先费用为零。 - Proto 版本。
yellowstone-grpc-proto12.6.0 是第一个携带 v1 字段的版本,而helius-laserstream0.8.4(JavaScript)、0.6.3(Rust)和 0.2.0(Go)是基于它构建的第一个 SDK 版本。较旧版本会默默地丢弃transactionConfig。
如何连接:流程
根据基本原则,以下是会发生的情况:- 建立查找表:
accountKeys列出此交易将触及的所有地址 - 设置规则:
header指定需要多少签名以及哪些账户是只读的 - 创建命令:每个
instruction指向:- 一个程序(通过
programIdIndex→accountKeys[index]) - 所需的账户(通过
accounts→ 多个accountKeys[index]位置) - 指令数据(编码在
data中)
- 一个程序(通过
- 添加授权:
signatures证明所需账户批准了此交易 - 设置过期时间:
recentBlockhash确保此交易不能稍后重播
执行:所有内容在 meta 内
虽然消息显示了用户想要做的事情,但 meta 显示了验证者执行交易时实际发生的事情。基本执行信息
成功/失败err: null= 成功err: {...}= 失败并带有错误详情fee= 针对此交易收取的 lamports
accountKeys 数组:
- 账户 0:损失 15000 lamports(费用支付)
- 账户 1:获得 1461600 lamports(新账户创建)
- 账户 3:获得 2001231920 lamports(程序账户)
高级执行细节
内部指令实用解码模式
以下是从解码交易中提取有用信息的常见模式:完整示例:Jupiter 交换解码器
这是一个完整的示例,解码 Jupiter 交换交易并提取有意义的信息:关键要点
- 两部分结构:每个交易都有一个 message(请求的内容)和 meta(实际发生的事情)
- 二进制解码:使用
bs58.encode()将二进制字段转换为可读的 base58 字符串 - 账户键查找:指令按
accountKeys数组中的索引引用账户 - 余额跟踪:比较
preBalances和postBalances以查看变化 - 交易 v1:在存在
transactionConfig时读取计算预算和优先费用;v1 交易没有 ComputeBudget 指令