Skip to content

Commit 323e68f

Browse files
authored
Jito Bell: Copy Directed Stake Targets (#143)
* fix: update * fix: test * fix: review
1 parent dd31c75 commit 323e68f

File tree

8 files changed

+212
-3
lines changed

8 files changed

+212
-3
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defillama-rs = "0.1.1"
1313
dotenvy = { version = "0.15.7" }
1414
env_logger = "0.11.3"
1515
futures = "0.3.24"
16+
hex = "0.4"
1617
hmac = "0.12"
1718
jito-vault-client = "0.0.5"
1819
jito-vault-sdk = "0.0.5"

jito-bell/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ tracing-subscriber = { workspace = true }
4848
twitterust = { workspace = true }
4949
yellowstone-grpc-client = { workspace = true }
5050
yellowstone-grpc-proto = { workspace = true }
51+
52+
[dev-dependencies]
53+
hex = { workspace = true }

jito-bell/src/ix_parser/jito_steward.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::str::FromStr;
22

33
use solana_pubkey::Pubkey;
4+
use solana_sdk::instruction::{AccountMeta, Instruction};
5+
6+
use crate::ix_parser::instruction::ParsableInstruction;
47

58
/// Jito Steward Instructions
69
#[derive(Debug, PartialEq)]
@@ -36,6 +39,12 @@ pub enum JitoStewardInstruction {
3639
IncreaseAdditionalValidatorStake,
3740
DecreaseAdditionalValidatorStake,
3841
UpdatePriorityFeeParameters,
42+
CopyDirectedStakeTargets {
43+
ix: Instruction,
44+
vote_pubkey: Pubkey,
45+
total_target_lamports: u64,
46+
validator_list_index: u32,
47+
},
3948
}
4049

4150
impl std::fmt::Display for JitoStewardInstruction {
@@ -88,6 +97,14 @@ impl std::fmt::Display for JitoStewardInstruction {
8897
JitoStewardInstruction::UpdatePriorityFeeParameters => {
8998
write!(f, "update_priority_fee_parameters")
9099
}
100+
JitoStewardInstruction::CopyDirectedStakeTargets {
101+
ix: _,
102+
vote_pubkey: _,
103+
total_target_lamports: _,
104+
validator_list_index: _,
105+
} => {
106+
write!(f, "copy_directed_stake_targets")
107+
}
91108
}
92109
}
93110
}
@@ -97,4 +114,120 @@ impl JitoStewardInstruction {
97114
pub fn program_id() -> Pubkey {
98115
Pubkey::from_str("Stewardf95sJbmtcZsyagb2dg4Mo8eVQho8gpECvLx8").unwrap()
99116
}
117+
118+
/// Parse Jito Steward instruction
119+
pub fn parse<T: ParsableInstruction>(
120+
instruction: &T,
121+
account_keys: &[Pubkey],
122+
) -> Option<JitoStewardInstruction> {
123+
let instruction_data = instruction.data();
124+
match instruction_data[0..8] {
125+
[135, 132, 9, 127, 189, 161, 14, 5] => {
126+
let vote_pubkey = {
127+
let mut pubkey_array = [0; 32];
128+
pubkey_array.copy_from_slice(&instruction_data[8..40]);
129+
Pubkey::new_from_array(pubkey_array)
130+
};
131+
132+
let total_target_lamports = {
133+
let mut slice = [0; 8];
134+
slice.copy_from_slice(&instruction_data[40..48]);
135+
u64::from_le_bytes(slice)
136+
};
137+
138+
let validator_list_index = {
139+
let mut slice = [0; 4];
140+
slice.copy_from_slice(&instruction_data[48..52]);
141+
u32::from_le_bytes(slice)
142+
};
143+
144+
Some(Self::parse_copy_directed_stake_targets_ix(
145+
instruction,
146+
account_keys,
147+
vote_pubkey,
148+
total_target_lamports,
149+
validator_list_index,
150+
))
151+
}
152+
_ => None,
153+
}
154+
}
155+
156+
/// #[account(0, name = "config")]
157+
/// #[account(1, writable, name = "directed_stake_meta")]
158+
/// #[account(2, name = "clock")]
159+
/// #[account(3, writable, name = "validator_list")]
160+
/// #[account(4, writable, signer, name = "authority")]
161+
pub fn parse_copy_directed_stake_targets_ix<T: ParsableInstruction>(
162+
instruction: &T,
163+
account_keys: &[Pubkey],
164+
vote_pubkey: Pubkey,
165+
total_target_lamports: u64,
166+
validator_list_index: u32,
167+
) -> Self {
168+
let mut account_metas = [
169+
AccountMeta::new(Pubkey::new_unique(), false),
170+
AccountMeta::new(Pubkey::new_unique(), false),
171+
AccountMeta::new(Pubkey::new_unique(), false),
172+
AccountMeta::new(Pubkey::new_unique(), false),
173+
AccountMeta::new_readonly(Pubkey::new_unique(), true),
174+
];
175+
176+
for (index, account) in instruction.accounts().iter().enumerate() {
177+
if let Some(account_meta) = account_metas.get_mut(index) {
178+
if let Some(account) = account_keys.get(*account as usize) {
179+
account_meta.pubkey = *account;
180+
}
181+
}
182+
}
183+
184+
let ix = Instruction {
185+
program_id: Self::program_id(),
186+
accounts: account_metas.to_vec(),
187+
data: instruction.data().to_vec(),
188+
};
189+
190+
Self::CopyDirectedStakeTargets {
191+
ix,
192+
vote_pubkey,
193+
total_target_lamports,
194+
validator_list_index,
195+
}
196+
}
197+
}
198+
199+
#[cfg(test)]
200+
mod tests {
201+
use yellowstone_grpc_proto::prelude::CompiledInstruction;
202+
203+
use crate::ix_parser::jito_steward::JitoStewardInstruction;
204+
205+
#[test]
206+
fn test_parse_copy_directed_stake_targets() {
207+
let instruction = {
208+
let data =
209+
hex::decode(
210+
"8784097fbda10e054613d432f5da8f600ac38dcd8f5c07df361f5b832400b012604624443b3f457000000000000000006e000000"
211+
).unwrap();
212+
CompiledInstruction {
213+
program_id_index: 0,
214+
accounts: vec![0],
215+
data,
216+
}
217+
};
218+
219+
let account_keys = vec![];
220+
let jito_steward_instruction =
221+
JitoStewardInstruction::parse(&instruction, &account_keys).unwrap();
222+
223+
match jito_steward_instruction {
224+
JitoStewardInstruction::CopyDirectedStakeTargets {
225+
ix: _,
226+
vote_pubkey: _,
227+
total_target_lamports: _,
228+
validator_list_index: _,
229+
} => {}
230+
_ => panic!("Wrong instruction"),
231+
}
232+
}
100233
}

jito-bell/src/lib.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use yellowstone_grpc_proto::{
3636
use crate::{
3737
config::JitoBellConfig,
3838
event_parser::{jito_steward::JitoStewardEvent, EventParser},
39-
ix_parser::InstructionParser,
39+
ix_parser::{jito_steward::JitoStewardInstruction, InstructionParser},
4040
notification_info::Destination,
4141
program::{EventConfig, Instruction, ProgramName},
4242
tx_parser::JitoTransactionParser,
@@ -274,7 +274,31 @@ impl JitoBellHandler {
274274
.await?;
275275
}
276276
}
277-
InstructionParser::JitoSteward(_) => {}
277+
InstructionParser::JitoSteward(jito_steward_instruction) => {
278+
debug!("Jito Steward");
279+
280+
let jito_steward_program_str = jito_steward_instruction.to_string();
281+
282+
let instruction_opt = self
283+
.config
284+
.programs
285+
.get(&ProgramName::JitoSteward)
286+
.and_then(|program_config| {
287+
program_config
288+
.instructions
289+
.get(&jito_steward_program_str)
290+
.cloned()
291+
});
292+
293+
if let Some(instruction) = instruction_opt {
294+
self.handle_jito_steward_program(
295+
parser,
296+
jito_steward_instruction,
297+
&instruction,
298+
)
299+
.await?;
300+
}
301+
}
278302
}
279303
}
280304

@@ -856,6 +880,38 @@ impl JitoBellHandler {
856880
Ok(())
857881
}
858882

883+
/// Sends a notification for each matching `CopyDirectedStakeTargets` instruction
884+
/// that includes notification metadata.
885+
async fn handle_jito_steward_program(
886+
&mut self,
887+
parser: &JitoTransactionParser,
888+
jito_steward_instruction: &JitoStewardInstruction,
889+
instruction: &Instruction,
890+
) -> Result<(), JitoBellError> {
891+
debug!("Jito Steward Instruction: {jito_steward_instruction}");
892+
893+
if let JitoStewardInstruction::CopyDirectedStakeTargets {
894+
ix: _,
895+
vote_pubkey: _,
896+
total_target_lamports,
897+
validator_list_index: _,
898+
} = jito_steward_instruction
899+
{
900+
if let Some(ref notification_info) = instruction.notification_info {
901+
self.dispatch_platform_notifications(
902+
&notification_info.destinations,
903+
&notification_info.description,
904+
Some(*total_target_lamports as f64),
905+
Some("lamports"),
906+
&parser.transaction_signature,
907+
)
908+
.await?;
909+
}
910+
}
911+
912+
Ok(())
913+
}
914+
859915
/// Dispatch platform notifications
860916
///
861917
/// - Return error only if ALL platforms failed, or handle as needed

jito-bell/src/program.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::HashMap;
33
use serde::Deserialize;
44

55
use crate::{
6-
notification_info::Destination,
6+
notification_info::{Destination, NotificationInfo},
77
threshold_config::{ThresholdConfig, UsdThresholdConfig},
88
};
99

@@ -51,6 +51,9 @@ pub struct Instruction {
5151

5252
/// Vault receipt token (VRT)
5353
pub vrts: Option<HashMap<String, AlertConfig>>,
54+
55+
/// Notification info
56+
pub notification_info: Option<NotificationInfo>,
5457
}
5558

5659
#[derive(Debug, Clone, Deserialize)]

jito-bell/src/tx_parser.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ impl JitoTransactionParser {
102102
if program_id
103103
.eq(&JitoStewardInstruction::program_id()) =>
104104
{
105+
if let Some(ix_info) =
106+
JitoStewardInstruction::parse(instruction, &pubkeys)
107+
{
108+
parsed_instructions
109+
.push(InstructionParser::JitoSteward(ix_info));
110+
}
111+
105112
for log in &meta.log_messages {
106113
if let Some(event) =
107114
JitoStewardEvent::parse_log(log)

jito_bell_config_sample.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
programs:
22
jito_steward:
33
program_id: "Stewardf95sJbmtcZsyagb2dg4Mo8eVQho8gpECvLx8"
4+
instructions:
5+
notification_info:
6+
copy_directed_stake_targets:
7+
description: "Copy directed stake targets detected"
8+
destinations: ["stakenet_event_alerts_slack"]
49
events:
510
auto_remove_validator:
611
destinations: ["stake_pool_alerts_slack"]

0 commit comments

Comments
 (0)