Skip to content

Commit 7075dbc

Browse files
authored
fix: light event (#2352)
* fix: handle mixed batch/non-batch inputs in create_nullifier_queue_indices When a transaction mixes batch (v2) and legacy/concurrent (v1) input accounts, the nullifier queue index assignment was using the raw position in input_compressed_accounts as the write index into nullifier_queue_indices. This caused an out-of-bounds panic when a non-batch account appeared between batch accounts (e.g. [batchA, legacy, batchB, batchA]). Fix by walking input_compressed_accounts in order and using a compact batch_idx counter that only advances for accounts with a matching sequence number entry. Non-batch accounts have no sequence number entry and are skipped without consuming a slot. * feat: add xtask fetch-block-events subcommand Fetches a configurable number of blocks starting at a given slot, parses every transaction using event_from_light_transaction, and prints a structured summary of all Light Protocol events found. Usage: cargo xtask fetch-block-events --start-slot <slot> --network mainnet cargo xtask fetch-block-events --start-slot <slot> --network devnet --num-blocks 5 * fix: format * chore: bump light-event 0.23.0 -> 0.23.1 * fix: extract ParsedInstruction struct to satisfy clippy::type_complexity * fix: rustfmt fetch_block_events * test(light-event): add regression tests for mixed batch/legacy nullifier OOB panic Transaction 3ybts1eFSC7QN6aU4ao6NJCgn7xTbtBVyzeLDZJf9eVN93vHZWupX4TXqHHgV18xf17eit7Uw5T135uabnpToKK4 at slot 407265372 panicked with "index out of bounds: len is 3 but index is 3" in create_nullifier_queue_indices when inputs mix batch and legacy trees. Adds two tests: - src/regression_test.rs: real mainnet instruction bytes decoded via bs58 - tests/parse_test.rs: synthetic test verifying exact nullifier_queue_indices [6, 3, 7] Also adds light-event to sdk-libs/justfile so it runs in CI.
1 parent 2644529 commit 7075dbc

11 files changed

Lines changed: 580 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ light-indexed-merkle-tree = { version = "5.0.0", path = "program-libs/indexed-me
201201
light-concurrent-merkle-tree = { version = "5.0.0", path = "program-libs/concurrent-merkle-tree" }
202202
light-sparse-merkle-tree = { version = "0.3.0", path = "sparse-merkle-tree" }
203203
light-client = { path = "sdk-libs/client", version = "0.23.0" }
204-
light-event = { path = "sdk-libs/event", version = "0.23.0" }
204+
light-event = { path = "sdk-libs/event", version = "0.23.1" }
205205
light-hasher = { path = "program-libs/hasher", version = "5.0.0", default-features = false }
206206
light-macros = { path = "program-libs/macros", version = "2.2.0" }
207207
light-merkle-tree-reference = { path = "program-tests/merkle-tree", version = "4.0.0" }

sdk-libs/event/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "light-event"
3-
version = "0.23.0"
3+
version = "0.23.1"
44
description = "Event types and utilities for Light Protocol"
55
repository = "https://github.com/Lightprotocol/light-protocol"
66
license = "Apache-2.0"
@@ -15,4 +15,5 @@ light-zero-copy = { workspace = true }
1515
thiserror = { workspace = true }
1616

1717
[dev-dependencies]
18+
bs58 = { workspace = true }
1819
rand = { workspace = true }

sdk-libs/event/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@
1111
pub mod error;
1212
pub mod event;
1313
pub mod parse;
14+
#[cfg(test)]
15+
mod regression_test;

sdk-libs/event/src/parse.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -738,18 +738,20 @@ fn create_nullifier_queue_indices(
738738
.insert_into_queues_instruction
739739
.input_sequence_numbers
740740
.to_vec();
741-
// For every sequence number:
742-
// 1. Find every input compressed account
743-
// 2. assign sequence number as nullifier queue index
744-
// 3. increment the sequence number
745-
internal_input_sequence_numbers.iter_mut().for_each(|seq| {
746-
for (i, merkle_tree_pubkey) in input_merkle_tree_pubkeys.iter().enumerate() {
747-
if *merkle_tree_pubkey == seq.tree_pubkey {
748-
nullifier_queue_indices[i] = seq.seq.into();
749-
seq.seq += 1;
750-
}
741+
// Walk input_compressed_accounts in order, assigning sequence numbers to batch
742+
// accounts using a compact write index. Non-batch (legacy/concurrent) accounts
743+
// have no matching sequence number entry and are skipped.
744+
let mut batch_idx = 0usize;
745+
for merkle_tree_pubkey in input_merkle_tree_pubkeys.iter() {
746+
if let Some(seq) = internal_input_sequence_numbers
747+
.iter_mut()
748+
.find(|s| s.tree_pubkey == *merkle_tree_pubkey)
749+
{
750+
nullifier_queue_indices[batch_idx] = seq.seq.into();
751+
seq.seq += 1;
752+
batch_idx += 1;
751753
}
752-
});
754+
}
753755
nullifier_queue_indices
754756
}
755757

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/// Regression test for index-out-of-bounds panic in create_nullifier_queue_indices.
2+
/// Transaction: 3ybts1eFSC7QN6aU4ao6NJCgn7xTbtBVyzeLDZJf9eVN93vHZWupX4TXqHHgV18xf17eit7Uw5T135uabnpToKK4
3+
/// Slot: 407265372 (mainnet)
4+
/// This transaction crashed photon's indexer because `len` passed to
5+
/// `create_nullifier_queue_indices` didn't match the number of input accounts.
6+
#[cfg(test)]
7+
mod tests {
8+
use light_compressed_account::Pubkey;
9+
10+
use crate::parse::event_from_light_transaction;
11+
12+
fn pubkey(s: &str) -> Pubkey {
13+
let bytes: [u8; 32] = bs58::decode(s).into_vec().unwrap().try_into().unwrap();
14+
Pubkey::from(bytes)
15+
}
16+
17+
fn ix_data(s: &str) -> Vec<u8> {
18+
bs58::decode(s).into_vec().unwrap()
19+
}
20+
21+
// Account addresses used in the transaction
22+
const USER: &str = "33X2Tg3gdxTwouaVSxpcNwVHJt2ZYxo3Hm7UjH2i8M3r";
23+
const REGISTERED_PDA: &str = "35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh";
24+
const NOOP: &str = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV";
25+
const CPI_CONTEXT: &str = "HwXnGK3tPkkVY6P439H2p68AxpeuWXd5PcrAxFpbmfbA";
26+
const ACCOUNT_COMPRESSION: &str = "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq";
27+
const SOL_POOL: &str = "CHK57ywWSDncAoRu1F8QgwYJeXuAJyyBYT4LixLXvMZ1";
28+
const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111";
29+
const BMT3_TREE: &str = "bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb";
30+
const OQ3_QUEUE: &str = "oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ";
31+
const SMT5_TREE: &str = "smt5uPaQT9n6b1qAkgyonmzRxtuazA53Rddwntqistc";
32+
const NFQ5_QUEUE: &str = "nfq5b5xEguPtdD6uPetZduyrB5EUqad7gcUE46rALau";
33+
const BMT2_TREE: &str = "bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi";
34+
const OQ2_QUEUE: &str = "oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg";
35+
36+
// Program IDs
37+
const LIGHT_SYSTEM: &str = "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7";
38+
const COMPUTE_BUDGET: &str = "ComputeBudget111111111111111111111111111111";
39+
40+
#[test]
41+
fn test_mainnet_tx_407265372_no_panic() {
42+
// Tx 3ybts1eFSC7QN...ToKK4, slot 407265372
43+
// Before fix: panicked with "index out of bounds: the len is 3 but the index is 3"
44+
let program_ids = vec![
45+
pubkey(COMPUTE_BUDGET), // SetComputeUnitLimit
46+
pubkey(COMPUTE_BUDGET), // SetComputeUnitPrice
47+
pubkey(LIGHT_SYSTEM), // Light system invoke
48+
pubkey(SYSTEM_PROGRAM), // SOL transfer (inner)
49+
pubkey(SYSTEM_PROGRAM), // SOL transfer (inner)
50+
pubkey(SYSTEM_PROGRAM), // SOL transfer (inner)
51+
pubkey(ACCOUNT_COMPRESSION), // InsertIntoQueues (inner)
52+
];
53+
54+
let instructions: Vec<Vec<u8>> = vec![
55+
ix_data("K1FDJ7"),
56+
ix_data("3cDeqiGMb6md"),
57+
ix_data("7Xu3JKNhcxBjvH52amHsaGu55uKzfsGvVjkBKAcEAAByDYGHt2TQQRq8aam17wkuH3Vtu2xuLyh8nZRxaqTEZPKM88CTs2e9MMiHW1ZA2NmFwbtgeHLFSRvW2DCayZMqHZWGEPjKwXnEJjFfKCTiJDXLeHbqirZeH4M3rYpeudpPnbNH9F9vLchjWs73hKJ9aSLVJKnJNXyr6ZW4hZd8YKVk3jaS11oW2ndPQT8CzYAF79wu8uishgqpLN42RwytWTDpMNUq7mKRFj1LkKKnpv8ya9WRxDCCKHfp1zn8sc1YviTcMyFsDRBvnE7kibyhcvd6hY9PvojPZNWABNDHxMGZoUL8xoUNRiD8Fxk7DyWQBvtqwyoPSjgFmKA97yEp4Kvj8btYDP24t51GYYXZyKjfFHnShcmdoKxuGohShW1UdjAhSWMVySZ92KRXjVJm6uv7CD5uXRy5Kuqco9ZHwASTv6HE1fQCEWKDdvq8Nx8SBMZF9jPM8JKJEarj"),
58+
ix_data("3Bxs4HHMpGEM2775"),
59+
ix_data("3Bxs4PckVVt51W8w"),
60+
ix_data("3Bxs4PckVVt51W8w"),
61+
ix_data("42NS6uhgPkAU4qDJGz54pXVoPKYL4VENq5jdLryg8pPRKsdthWiNYkaBQEimb4SSscjPZ2uYSXD7TjANLcaUdRMjh7Hid94o5GpGTxM3Pg2ALYdg8Qps6w2Sn6FXc1cp2vWVaXFQicExxLSTUNSSZwKH2M2XiqDxZBSekyELNcXkJCji9heVWqiB48zJX1YDBMYKLgXu3MoFvUgGjpYRteuuw44rBYUSfrs5tNh5CdfMtNkUJVCEvr5LSWeRUYwwXT8shx53iYb186vE3Gm2qY1Up7PfHdqGH1KZmzNz6ZjU2oC2r6zUHxoAA4v7HhMiC2cgwFXMrVGnw2nfKunjEP7Xm2Q62G4uJHGH3aMucTrSKCiwc55czqV9RaUDZUrvtfbLUjwG7XcPwwaY9JusFs21sZNveGE9xm1groM6uGn8ERCc6oBtFhouRKpfQiGoWxKeSrS6K5KWEq5aJ7XsZcXkNSdNGsGtgGu4nDXDtGhbamhXUtVmXcEfMMsMfoSzm1Cj1HCP89thHHC6P52Wert8XAfeei8X8bfwRHw6SzVFTBKkP7W8vjE2PgjwD5rVprBxS5owL4HPEnuTdSoawLA5JEqucpqgvXv7qihuJZ5aEQ8q2JhayJx3hqDriN6g1Vc2br8MtGRPXuwQYAd84jJoS6puMoanPnyFccv35jaxkEwUi5vY8J88ejut9W4uP7JVBivLBXYgDyLteffxA5a6rhJtFZ"),
62+
];
63+
64+
let accounts: Vec<Vec<Pubkey>> = vec![
65+
vec![], // ComputeBudget
66+
vec![], // ComputeBudget
67+
// Light system invoke: user, registered PDA, noop, CPI context, account compression,
68+
// SOL pool, system program, V2 trees (bmt3, oq3), V1 trees (smt5, nfq5), V2 trees (bmt2, oq2)
69+
vec![
70+
pubkey(USER),
71+
pubkey(USER),
72+
pubkey(REGISTERED_PDA),
73+
pubkey(NOOP),
74+
pubkey(CPI_CONTEXT),
75+
pubkey(ACCOUNT_COMPRESSION),
76+
pubkey(SOL_POOL),
77+
pubkey(USER),
78+
pubkey(SYSTEM_PROGRAM),
79+
pubkey(BMT3_TREE),
80+
pubkey(OQ3_QUEUE),
81+
pubkey(SMT5_TREE),
82+
pubkey(NFQ5_QUEUE),
83+
pubkey(BMT2_TREE),
84+
pubkey(OQ2_QUEUE),
85+
],
86+
// SOL transfers (inner)
87+
vec![pubkey(SOL_POOL), pubkey(USER)],
88+
vec![pubkey(USER), pubkey(BMT3_TREE)],
89+
vec![pubkey(USER), pubkey(SMT5_TREE)],
90+
// InsertIntoQueues (inner): CPI context, registered PDA, queues and trees
91+
vec![
92+
pubkey(CPI_CONTEXT),
93+
pubkey(REGISTERED_PDA),
94+
pubkey(OQ3_QUEUE),
95+
pubkey(BMT3_TREE),
96+
pubkey(NFQ5_QUEUE),
97+
pubkey(SMT5_TREE),
98+
pubkey(OQ2_QUEUE),
99+
pubkey(BMT2_TREE),
100+
],
101+
];
102+
103+
let result = event_from_light_transaction(&program_ids, &instructions, accounts);
104+
assert!(
105+
result.is_ok(),
106+
"event_from_light_transaction failed: {:?}",
107+
result.err()
108+
);
109+
}
110+
}

0 commit comments

Comments
 (0)