Skip to content

Commit c33467e

Browse files
authored
Merge pull request #15 from holaplex/dev
Release v0.5
2 parents 33be344 + e9ba920 commit c33467e

File tree

10 files changed

+165
-56
lines changed

10 files changed

+165
-56
lines changed

Cargo.lock

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

crates/plugin/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "holaplex-indexer-rabbitmq-geyser"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
authors = [
55
"ryans <[email protected]>",
66
]
@@ -51,7 +51,7 @@ features = [
5151

5252
[dependencies.indexer-rabbitmq]
5353
package = "holaplex-indexer-rabbitmq"
54-
version = "=0.1.0"
54+
version = "=0.2.0"
5555
path = "../rabbitmq"
5656
default-features = false
5757
features = ["producer", "geyser"]

crates/plugin/sample_config.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,20 @@
2626
"useZ65tbyvWpdYCLDJaegGK34Lnsi8S3jZdwx8122qp",
2727
"LocktDzaV1W2Bm9DeZeiyz4J9zs4fRqNiYqQyracRXw",
2828
"Govz1VyoyLD5BL6CSCxUJLVLsQHRwjfFj1prNsdNg5Jw",
29-
"GokivDYuQXPZCWRkwMhdH2h91KpDQXBEmpgBgs55bnpH"
29+
"GokivDYuQXPZCWRkwMhdH2h91KpDQXBEmpgBgs55bnpH",
30+
"TBondmkCYxaPCKG4CHYfVTcwQ8on31xnJrPzk8F8WsS",
31+
"nameXpT2PwZ2iA6DTNYTotTmiMYusBCYqwBLN2QgF4w",
32+
"HAbiTatJVqoCJd9asyr6RxMEdwtfrQugwp7VAFyKWb1g",
33+
"GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw"
3034
],
3135
"startup": false
3236
},
33-
"instructionPrograms": []
37+
"instructions": {
38+
"programs": [
39+
"M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K",
40+
"MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8",
41+
"hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk",
42+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
43+
]
44+
}
3445
}

crates/plugin/src/config.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ pub struct Config {
1616
metrics: Metrics,
1717

1818
accounts: Accounts,
19-
20-
instruction_programs: HashSet<String>,
19+
instructions: Instructions,
2120
}
2221

2322
#[serde_with::serde_as]
@@ -46,7 +45,7 @@ pub struct Metrics {
4645
}
4746

4847
#[derive(Debug, Deserialize)]
49-
#[serde(rename_all = "camelCase")]
48+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
5049
pub struct Accounts {
5150
pub owners: HashSet<String>,
5251

@@ -57,6 +56,26 @@ pub struct Accounts {
5756
/// - `Some(true)`: Only send updates when `is_startup` is `true`.
5857
/// - `Some(false)`: Only send updates when `is_startup` is `false`.
5958
pub startup: Option<bool>,
59+
60+
/// Set to true to disable heuristics to reduce the number of incoming
61+
/// token account updates. Has no effect if the spl-token pubkey is not in
62+
/// the owners list.
63+
#[serde(default)]
64+
pub all_tokens: bool,
65+
}
66+
67+
#[derive(Debug, Deserialize)]
68+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
69+
pub struct Instructions {
70+
pub programs: HashSet<String>,
71+
72+
/// Set to true to disable heuristics to reduce the number of incoming
73+
/// token instructions. Has no effect if the spl-token pubkey is not in the
74+
/// programs list. Currently the heuristics are tailored towards NFT burns,
75+
/// only passing through instructions whose data indicates a burn of amount
76+
/// 1.
77+
#[serde(default)]
78+
pub all_token_calls: bool,
6079
}
6180

6281
impl Config {
@@ -73,12 +92,12 @@ impl Config {
7392
jobs,
7493
metrics,
7594
accounts,
76-
instruction_programs,
95+
instructions,
7796
} = self;
7897

7998
let acct =
8099
AccountSelector::from_config(accounts).context("Failed to create account selector")?;
81-
let ins = InstructionSelector::from_config(instruction_programs)
100+
let ins = InstructionSelector::from_config(instructions)
82101
.context("Failed to create instruction selector")?;
83102

84103
Ok((amqp, jobs, metrics, acct, ins))

crates/plugin/src/plugin.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use std::{env, sync::Arc};
33
use anyhow::Context;
44
use hashbrown::HashSet;
55
use indexer_rabbitmq::geyser::{AccountUpdate, InstructionNotify, Message};
6-
use solana_program::{instruction::CompiledInstruction, message::AccountKeys, program_pack::Pack};
7-
use spl_token::state::Account as TokenAccount;
6+
use solana_program::{instruction::CompiledInstruction, message::AccountKeys};
87

9-
static TOKEN_KEY: Pubkey = solana_program::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
8+
pub(crate) static TOKEN_KEY: Pubkey =
9+
solana_program::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
1010

1111
use serde::Deserialize;
1212

@@ -41,7 +41,6 @@ pub(crate) struct Inner {
4141
acct_sel: AccountSelector,
4242
ins_sel: InstructionSelector,
4343
metrics: Arc<Metrics>,
44-
token_addresses: HashSet<Pubkey>,
4544
}
4645

4746
impl Inner {
@@ -148,7 +147,7 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
148147
.map_err(custom_err(&metrics.errs))?;
149148
}
150149

151-
let (amqp, jobs, metrics_conf, acct_sel, ins_sel) = Config::read(cfg)
150+
let (amqp, jobs, metrics_conf, mut acct_sel, ins_sel) = Config::read(cfg)
152151
.and_then(Config::into_parts)
153152
.map_err(custom_err(&metrics.errs))?;
154153

@@ -172,7 +171,7 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
172171
.build()
173172
.map_err(custom_err(&metrics.errs))?;
174173

175-
let (producer, token_addresses) = rt.block_on(async {
174+
let producer = rt.block_on(async {
176175
let producer = Sender::new(
177176
amqp,
178177
format!("geyser-rabbitmq-{}@{}", version, host),
@@ -182,11 +181,15 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
182181
.await
183182
.map_err(custom_err(&metrics.errs))?;
184183

185-
let tokens = Self::load_token_reg()
186-
.await
187-
.map_err(custom_err(&metrics.errs))?;
184+
if acct_sel.screen_tokens() {
185+
acct_sel.init_tokens(
186+
Self::load_token_reg()
187+
.await
188+
.map_err(custom_err(&metrics.errs))?,
189+
);
190+
}
188191

189-
Result::<_>::Ok((producer, tokens))
192+
Result::<_>::Ok(producer)
190193
})?;
191194

192195
self.0 = Some(Arc::new(Inner {
@@ -195,7 +198,6 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
195198
acct_sel,
196199
ins_sel,
197200
metrics,
198-
token_addresses,
199201
}));
200202

201203
Ok(())
@@ -228,20 +230,6 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
228230
write_version,
229231
} = *acct;
230232

231-
if owner == TOKEN_KEY.as_ref()
232-
&& data.len() == TokenAccount::get_packed_len()
233-
{
234-
let token_account = TokenAccount::unpack_from_slice(data);
235-
236-
if let Ok(token_account) = token_account {
237-
if token_account.amount > 1
238-
|| this.token_addresses.contains(&token_account.mint)
239-
{
240-
return Ok(());
241-
}
242-
}
243-
}
244-
245233
let key = Pubkey::new_from_array(pubkey.try_into()?);
246234
let owner = Pubkey::new_from_array(owner.try_into()?);
247235
let data = data.to_owned();
@@ -288,7 +276,7 @@ impl GeyserPlugin for GeyserPluginRabbitMq {
288276
.get(ins.program_id_index as usize)
289277
.ok_or_else(|| anyhow!("Couldn't get program ID for instruction"))?;
290278

291-
if !sel.is_selected(&program) {
279+
if !sel.is_selected(&program, ins) {
292280
return Ok(None);
293281
}
294282

crates/plugin/src/selectors.rs

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,111 @@
11
use hashbrown::HashSet;
22
use indexer_rabbitmq::geyser::StartupType;
3+
use solana_program::{instruction::CompiledInstruction, program_pack::Pack};
4+
use spl_token::state::Account as TokenAccount;
35

4-
use super::config::Accounts;
5-
use crate::{interface::ReplicaAccountInfo, prelude::*};
6+
use crate::{
7+
config::{Accounts, Instructions},
8+
interface::ReplicaAccountInfo,
9+
plugin::TOKEN_KEY,
10+
prelude::*,
11+
};
612

713
#[derive(Debug)]
814
pub struct AccountSelector {
915
owners: HashSet<[u8; 32]>,
1016
startup: Option<bool>,
17+
token_addresses: Option<HashSet<Pubkey>>,
1118
}
1219

1320
impl AccountSelector {
1421
pub fn from_config(config: Accounts) -> Result<Self> {
15-
let Accounts { owners, startup } = config;
22+
let Accounts {
23+
owners,
24+
all_tokens,
25+
startup,
26+
} = config;
1627

1728
let owners = owners
1829
.into_iter()
1930
.map(|s| s.parse().map(Pubkey::to_bytes))
2031
.collect::<Result<_, _>>()
2132
.context("Failed to parse account owner keys")?;
2233

23-
Ok(Self { owners, startup })
34+
Ok(Self {
35+
owners,
36+
startup,
37+
token_addresses: if all_tokens {
38+
None
39+
} else {
40+
Some(HashSet::new())
41+
},
42+
})
43+
}
44+
45+
/// Lazy-load the token addresses. Fails if token addresses are not wanted
46+
/// or if they have already been loaded.
47+
pub fn init_tokens(&mut self, addrs: HashSet<Pubkey>) {
48+
assert!(self.token_addresses.as_ref().unwrap().is_empty());
49+
self.token_addresses = Some(addrs);
2450
}
2551

2652
#[inline]
2753
pub fn startup(&self) -> StartupType {
2854
StartupType::new(self.startup)
2955
}
3056

57+
#[inline]
58+
pub fn screen_tokens(&self) -> bool {
59+
self.token_addresses.is_some()
60+
}
61+
3162
#[inline]
3263
pub fn is_selected(&self, acct: &ReplicaAccountInfo, is_startup: bool) -> bool {
33-
self.startup.map_or(true, |s| is_startup == s) && self.owners.contains(acct.owner)
64+
let ReplicaAccountInfo { owner, data, .. } = *acct;
65+
66+
if self.startup.map_or(false, |s| is_startup != s) || !self.owners.contains(owner) {
67+
return false;
68+
}
69+
70+
if owner == TOKEN_KEY.as_ref() && data.len() == TokenAccount::get_packed_len() {
71+
if let Some(ref addrs) = self.token_addresses {
72+
let token_account = TokenAccount::unpack_from_slice(data);
73+
74+
if let Ok(token_account) = token_account {
75+
if token_account.amount > 1 || addrs.contains(&token_account.mint) {
76+
return false;
77+
}
78+
}
79+
}
80+
}
81+
82+
true
3483
}
3584
}
3685

3786
#[derive(Debug)]
3887
pub struct InstructionSelector {
3988
programs: HashSet<Pubkey>,
89+
screen_tokens: bool,
4090
}
4191

4292
impl InstructionSelector {
43-
pub fn from_config(programs: HashSet<String>) -> Result<Self> {
93+
pub fn from_config(config: Instructions) -> Result<Self> {
94+
let Instructions {
95+
programs,
96+
all_token_calls,
97+
} = config;
98+
4499
let programs = programs
45100
.into_iter()
46101
.map(|s| s.parse())
47102
.collect::<Result<_, _>>()
48103
.context("Failed to parse instruction program keys")?;
49104

50-
Ok(Self { programs })
105+
Ok(Self {
106+
programs,
107+
screen_tokens: !all_token_calls,
108+
})
51109
}
52110

53111
#[inline]
@@ -56,7 +114,28 @@ impl InstructionSelector {
56114
}
57115

58116
#[inline]
59-
pub fn is_selected(&self, pgm: &Pubkey) -> bool {
60-
self.programs.contains(pgm)
117+
pub fn is_selected(&self, pgm: &Pubkey, ins: &CompiledInstruction) -> bool {
118+
if !self.programs.contains(pgm) {
119+
return false;
120+
}
121+
122+
if self.screen_tokens && *pgm == TOKEN_KEY {
123+
if let [8, rest @ ..] = ins.data.as_slice() {
124+
let amt = rest.try_into().map(u64::from_le_bytes);
125+
126+
if !matches!(amt, Ok(1)) {
127+
return false;
128+
}
129+
130+
debug_assert_eq!(
131+
ins.data,
132+
spl_token::instruction::TokenInstruction::Burn { amount: 1_u64 }.pack(),
133+
);
134+
} else {
135+
return false;
136+
}
137+
}
138+
139+
true
61140
}
62141
}

crates/plugin/src/sender.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl Sender {
5555

5656
Producer::new(
5757
&conn,
58-
QueueType::new(amqp.network, startup_type, &Suffix::Production)?,
58+
QueueType::new(amqp.network, startup_type, &Suffix::ProductionUnchecked)?,
5959
)
6060
.await
6161
}

crates/rabbitmq/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "holaplex-indexer-rabbitmq"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = [
55
"ryans <[email protected]>",
66
]
@@ -19,7 +19,7 @@ default = ["consumer"]
1919
geyser = ["solana-program", "suffix"]
2020
http-indexer = ["solana-program", "suffix"]
2121
producer = ["suffix"]
22-
search-indexer = ["serde_json", "suffix"]
22+
search-indexer = ["serde_json", "solana-program", "suffix"]
2323
suffix = ["clap"]
2424

2525
[dependencies]

0 commit comments

Comments
 (0)