11use solana_client:: nonblocking:: rpc_client:: RpcClient ;
2- use solana_message:: { compiled_instruction:: CompiledInstruction , VersionedMessage } ;
2+ use solana_message:: { compiled_instruction:: CompiledInstruction , MessageHeader , VersionedMessage } ;
33use 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