Skip to content

Commit 18539eb

Browse files
authored
fix: stabilize bundle, lighthouse header, and transfer-hook tests (#392)
* fix(bundle): send full merged bundle to Jito in signAndSendBundle * fix(lighthouse): maintain readonly header when appending assertion * fix(tests): make transfer-hook mint setup idempotent --------- Co-authored-by: Jo D <dev-jodee@users.noreply.github.com>
1 parent 39747af commit 18539eb

File tree

4 files changed

+270
-95
lines changed

4 files changed

+270
-95
lines changed

crates/lib/src/bundle/jito/client.rs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ use crate::{
44
BundleError,
55
},
66
sanitize_error,
7-
transaction::VersionedTransactionResolved,
87
};
9-
use base64::{prelude::BASE64_STANDARD, Engine};
10-
use bincode::serialize;
118
use reqwest::Client;
129
use serde::{Deserialize, Serialize};
1310
use serde_json::{json, Value};
@@ -28,11 +25,11 @@ impl JitoBundleClient {
2825

2926
pub async fn send_bundle(
3027
&self,
31-
transactions: &[VersionedTransactionResolved],
28+
encoded_transactions: &[String],
3229
) -> Result<String, BundleError> {
3330
match self {
34-
Self::Live(client) => client.send_bundle(transactions).await,
35-
Self::Mock(client) => client.send_bundle(transactions).await,
31+
Self::Live(client) => client.send_bundle(encoded_transactions).await,
32+
Self::Mock(client) => client.send_bundle(encoded_transactions).await,
3633
}
3734
}
3835

@@ -117,16 +114,9 @@ impl JitoClient {
117114

118115
pub async fn send_bundle(
119116
&self,
120-
transactions: &[VersionedTransactionResolved],
117+
encoded_transactions: &[String],
121118
) -> Result<String, BundleError> {
122-
let mut encoded_txs = Vec::with_capacity(transactions.len());
123-
for resolved in transactions {
124-
let serialized = serialize(&resolved.transaction)
125-
.map_err(|e| BundleError::SerializationError(e.to_string()))?;
126-
encoded_txs.push(BASE64_STANDARD.encode(&serialized));
127-
}
128-
129-
let params = json!([encoded_txs, {"encoding": "base64"}]);
119+
let params = json!([encoded_transactions, {"encoding": "base64"}]);
130120
let result = self.send_request("sendBundle", params).await?;
131121

132122
result.as_str().map(|s| s.to_string()).ok_or_else(|| {
@@ -152,7 +142,7 @@ impl JitoMockClient {
152142

153143
pub async fn send_bundle(
154144
&self,
155-
_transactions: &[VersionedTransactionResolved],
145+
_encoded_transactions: &[String],
156146
) -> Result<String, BundleError> {
157147
let random_id: u64 = rand::random();
158148
Ok(format!("mock-bundle-{random_id}"))
@@ -185,7 +175,7 @@ impl Default for JitoMockClient {
185175
#[cfg(test)]
186176
mod tests {
187177
use super::*;
188-
use crate::tests::transaction_mock::create_mock_resolved_transaction;
178+
use crate::tests::transaction_mock::create_mock_encoded_transaction;
189179
use mockito::{Matcher, Server};
190180

191181
#[tokio::test]
@@ -204,7 +194,7 @@ mod tests {
204194
let config = JitoConfig { block_engine_url: server.url() };
205195
let client = JitoClient::new(&config);
206196

207-
let tx = create_mock_resolved_transaction();
197+
let tx = create_mock_encoded_transaction();
208198

209199
let result = client.send_bundle(&[tx]).await;
210200
mock.assert();
@@ -225,7 +215,7 @@ mod tests {
225215
let config = JitoConfig { block_engine_url: server.url() };
226216
let client = JitoClient::new(&config);
227217

228-
let tx = create_mock_resolved_transaction();
218+
let tx = create_mock_encoded_transaction();
229219

230220
let result = client.send_bundle(&[tx]).await;
231221
mock.assert();
@@ -246,7 +236,7 @@ mod tests {
246236
let config = JitoConfig { block_engine_url: server.url() };
247237
let client = JitoClient::new(&config);
248238

249-
let tx = create_mock_resolved_transaction();
239+
let tx = create_mock_encoded_transaction();
250240

251241
let result = client.send_bundle(&[tx]).await;
252242
mock.assert();
@@ -287,7 +277,7 @@ mod tests {
287277
let config = JitoConfig { block_engine_url: server.url() };
288278
let client = JitoClient::new(&config);
289279

290-
let tx = create_mock_resolved_transaction();
280+
let tx = create_mock_encoded_transaction();
291281

292282
let result = client.send_bundle(&[tx]).await;
293283
mock.assert();
@@ -308,7 +298,7 @@ mod tests {
308298
let config = JitoConfig { block_engine_url: server.url() };
309299
let client = JitoClient::new(&config);
310300

311-
let tx = create_mock_resolved_transaction();
301+
let tx = create_mock_encoded_transaction();
312302

313303
let result = client.send_bundle(&[tx]).await;
314304
mock.assert();
@@ -330,9 +320,9 @@ mod tests {
330320
let client = JitoClient::new(&config);
331321

332322
let txs = vec![
333-
create_mock_resolved_transaction(),
334-
create_mock_resolved_transaction(),
335-
create_mock_resolved_transaction(),
323+
create_mock_encoded_transaction(),
324+
create_mock_encoded_transaction(),
325+
create_mock_encoded_transaction(),
336326
];
337327

338328
let result = client.send_bundle(&txs).await;
@@ -355,7 +345,7 @@ mod tests {
355345
let config = JitoConfig { block_engine_url: server.url() };
356346
let client = JitoClient::new(&config);
357347

358-
let tx = create_mock_resolved_transaction();
348+
let tx = create_mock_encoded_transaction();
359349

360350
let result = client.send_bundle(&[tx]).await;
361351
mock.assert();
@@ -420,7 +410,7 @@ mod tests {
420410

421411
assert!(matches!(client, JitoBundleClient::Mock(_)));
422412

423-
let tx = create_mock_resolved_transaction();
413+
let tx = create_mock_encoded_transaction();
424414
let result = client.send_bundle(&[tx]).await;
425415

426416
assert!(result.is_ok());

crates/lib/src/lighthouse/assertion.rs

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use solana_client::nonblocking::rpc_client::RpcClient;
2-
use solana_message::{compiled_instruction::CompiledInstruction, VersionedMessage};
2+
use solana_message::{compiled_instruction::CompiledInstruction, MessageHeader, VersionedMessage};
33
use solana_sdk::{
44
instruction::{AccountMeta, Instruction},
55
pubkey::Pubkey,
@@ -105,9 +105,9 @@ impl LighthouseUtil {
105105
fn find_or_add_account(
106106
account_keys: &mut Vec<Pubkey>,
107107
pubkey: &Pubkey,
108-
) -> Result<u8, KoraError> {
108+
) -> Result<(u8, bool), KoraError> {
109109
if let Some(index) = account_keys.iter().position(|k| k == pubkey) {
110-
Ok(index as u8)
110+
Ok((index as u8, false))
111111
} else {
112112
if account_keys.len() >= 256 {
113113
return Err(KoraError::ValidationError(
@@ -116,25 +116,49 @@ impl LighthouseUtil {
116116
}
117117
let index = account_keys.len() as u8;
118118
account_keys.push(*pubkey);
119-
Ok(index)
119+
Ok((index, true))
120120
}
121121
}
122122

123+
fn increment_readonly_unsigned_accounts(header: &mut MessageHeader) -> Result<(), KoraError> {
124+
header.num_readonly_unsigned_accounts =
125+
header.num_readonly_unsigned_accounts.checked_add(1).ok_or_else(|| {
126+
KoraError::ValidationError(
127+
"num_readonly_unsigned_accounts overflow when appending instruction"
128+
.to_string(),
129+
)
130+
})?;
131+
Ok(())
132+
}
133+
123134
/// Append an instruction to a versioned transaction
124135
fn append_instruction_to_transaction(
125136
transaction: &mut VersionedTransaction,
126137
instruction: Instruction,
127138
) -> Result<(), KoraError> {
128139
match &mut transaction.message {
129140
VersionedMessage::Legacy(message) => {
130-
let program_id_index =
141+
let (program_id_index, program_added) =
131142
Self::find_or_add_account(&mut message.account_keys, &instruction.program_id)?;
132-
133-
let account_indices: Vec<u8> = instruction
134-
.accounts
135-
.iter()
136-
.map(|meta| Self::find_or_add_account(&mut message.account_keys, &meta.pubkey))
137-
.collect::<Result<Vec<_>, _>>()?;
143+
if program_added {
144+
Self::increment_readonly_unsigned_accounts(&mut message.header)?;
145+
}
146+
147+
let mut account_indices: Vec<u8> = Vec::with_capacity(instruction.accounts.len());
148+
for meta in &instruction.accounts {
149+
let (index, added) =
150+
Self::find_or_add_account(&mut message.account_keys, &meta.pubkey)?;
151+
if added {
152+
if meta.is_signer || meta.is_writable {
153+
return Err(KoraError::ValidationError(
154+
"Appending new signer/writable accounts is not supported"
155+
.to_string(),
156+
));
157+
}
158+
Self::increment_readonly_unsigned_accounts(&mut message.header)?;
159+
}
160+
account_indices.push(index);
161+
}
138162

139163
message.instructions.push(CompiledInstruction {
140164
program_id_index,
@@ -145,14 +169,27 @@ impl LighthouseUtil {
145169
Ok(())
146170
}
147171
VersionedMessage::V0(message) => {
148-
let program_id_index =
172+
let (program_id_index, program_added) =
149173
Self::find_or_add_account(&mut message.account_keys, &instruction.program_id)?;
150-
151-
let account_indices: Vec<u8> = instruction
152-
.accounts
153-
.iter()
154-
.map(|meta| Self::find_or_add_account(&mut message.account_keys, &meta.pubkey))
155-
.collect::<Result<Vec<_>, _>>()?;
174+
if program_added {
175+
Self::increment_readonly_unsigned_accounts(&mut message.header)?;
176+
}
177+
178+
let mut account_indices: Vec<u8> = Vec::with_capacity(instruction.accounts.len());
179+
for meta in &instruction.accounts {
180+
let (index, added) =
181+
Self::find_or_add_account(&mut message.account_keys, &meta.pubkey)?;
182+
if added {
183+
if meta.is_signer || meta.is_writable {
184+
return Err(KoraError::ValidationError(
185+
"Appending new signer/writable accounts is not supported"
186+
.to_string(),
187+
));
188+
}
189+
Self::increment_readonly_unsigned_accounts(&mut message.header)?;
190+
}
191+
account_indices.push(index);
192+
}
156193

157194
message.instructions.push(CompiledInstruction {
158195
program_id_index,
@@ -256,6 +293,8 @@ mod tests {
256293
let mut transaction = VersionedTransaction::try_new(message, &[&keypair]).unwrap();
257294

258295
let original_ix_count = transaction.message.instructions().len();
296+
let original_readonly_unsigned =
297+
transaction.message.header().num_readonly_unsigned_accounts;
259298

260299
let assertion_ix = LighthouseUtil::build_fee_payer_assertion(&keypair.pubkey(), 1_000_000);
261300
let config = LighthouseConfig { enabled: true, fail_if_transaction_size_overflow: true };
@@ -265,6 +304,11 @@ mod tests {
265304
assert!(result.is_ok());
266305

267306
assert_eq!(transaction.message.instructions().len(), original_ix_count + 1);
307+
assert_eq!(
308+
transaction.message.header().num_readonly_unsigned_accounts,
309+
original_readonly_unsigned + 1
310+
);
311+
assert!(transaction.message.static_account_keys().contains(&LIGHTHOUSE_PROGRAM_ID));
268312
}
269313

270314
#[test]
@@ -292,6 +336,8 @@ mod tests {
292336
let mut transaction = VersionedTransaction::try_new(message, &[&keypair]).unwrap();
293337

294338
let original_ix_count = transaction.message.instructions().len();
339+
let original_readonly_unsigned =
340+
transaction.message.header().num_readonly_unsigned_accounts;
295341

296342
let assertion_ix = LighthouseUtil::build_fee_payer_assertion(&keypair.pubkey(), 1_000_000);
297343
let config = LighthouseConfig { enabled: true, fail_if_transaction_size_overflow: true };
@@ -301,6 +347,39 @@ mod tests {
301347
assert!(result.is_ok());
302348

303349
assert_eq!(transaction.message.instructions().len(), original_ix_count + 1);
350+
assert_eq!(
351+
transaction.message.header().num_readonly_unsigned_accounts,
352+
original_readonly_unsigned + 1
353+
);
354+
assert!(transaction.message.static_account_keys().contains(&LIGHTHOUSE_PROGRAM_ID));
355+
}
356+
357+
#[test]
358+
fn test_append_lighthouse_assertion_header_unchanged_when_lighthouse_program_exists() {
359+
let keypair = Keypair::new();
360+
361+
let instruction = Instruction::new_with_bytes(
362+
LIGHTHOUSE_PROGRAM_ID,
363+
&[1, 2, 3],
364+
vec![AccountMeta::new(keypair.pubkey(), true)],
365+
);
366+
367+
let message =
368+
VersionedMessage::Legacy(Message::new(&[instruction], Some(&keypair.pubkey())));
369+
let mut transaction = VersionedTransaction::try_new(message, &[&keypair]).unwrap();
370+
let original_readonly_unsigned =
371+
transaction.message.header().num_readonly_unsigned_accounts;
372+
373+
let assertion_ix = LighthouseUtil::build_fee_payer_assertion(&keypair.pubkey(), 1_000_000);
374+
let config = LighthouseConfig { enabled: true, fail_if_transaction_size_overflow: true };
375+
376+
let result =
377+
LighthouseUtil::append_lighthouse_assertion(&mut transaction, assertion_ix, &config);
378+
assert!(result.is_ok());
379+
assert_eq!(
380+
transaction.message.header().num_readonly_unsigned_accounts,
381+
original_readonly_unsigned
382+
);
304383
}
305384

306385
#[test]

0 commit comments

Comments
 (0)