1pub type TnPubkey = [u8; 32];
5pub type TnHash = [u8; 32];
6pub type TnSignature = [u8; 64];
7
8use crate::{
9 tn_signature::{sign_transaction, verify_transaction},
10 tn_state_proof::StateProof,
11 StateProofType,
12};
13use bytemuck::{Pod, Zeroable, bytes_of, from_bytes};
14
15pub const TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT: u8 = 0; pub const TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT: u8 = 1; pub const TN_STATE_PROOF_TYPE_EXISTING: u64 = 0x0;
20pub const TN_STATE_PROOF_TYPE_UPDATING: u64 = 0x1;
21pub const TN_STATE_PROOF_TYPE_CREATION: u64 = 0x2;
22
23pub const TN_STATE_PROOF_HDR_SIZE: usize = 40; pub const TN_ACCOUNT_META_FOOTPRINT: usize = 64; #[derive(Debug, PartialEq)]
29pub enum RpcError {
30 InvalidTransactionSize { size: usize, max_size: usize },
31 TrailingBytes { expected: usize, found: usize },
32 TooManyAccounts { count: usize, max_count: usize },
33 InvalidTransactionSignature,
34 InvalidParams(&'static str),
35 InvalidFormat,
36 InvalidVersion,
37 InvalidFlags,
38 InvalidFeePayerStateProofType,
39}
40
41impl std::fmt::Display for RpcError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 RpcError::InvalidTransactionSize { size, max_size } => {
45 write!(
46 f,
47 "Transaction size {} exceeds maximum allowed size {}",
48 size, max_size
49 )
50 }
51 RpcError::TrailingBytes { expected, found } => {
52 write!(
53 f,
54 "Transaction has trailing bytes: expected {} bytes, found {} bytes",
55 expected, found
56 )
57 }
58 RpcError::TooManyAccounts { count, max_count } => {
59 write!(
60 f,
61 "Too many accounts: {} exceeds maximum {}",
62 count, max_count
63 )
64 }
65 RpcError::InvalidTransactionSignature => {
66 write!(f, "Invalid transaction signature")
67 }
68 RpcError::InvalidParams(msg) => {
69 write!(f, "Invalid parameters: {}", msg)
70 }
71 RpcError::InvalidFormat => {
72 write!(f, "Invalid transaction format")
73 }
74 RpcError::InvalidVersion => {
75 write!(f, "Invalid transaction version")
76 }
77 RpcError::InvalidFlags => {
78 write!(f, "Invalid transaction flags")
79 }
80 RpcError::InvalidFeePayerStateProofType => {
81 write!(f, "Invalid fee payer state proof type")
82 }
83 }
84 }
85}
86
87impl RpcError {
88 pub fn invalid_transaction_size(size: usize, max_size: usize) -> Self {
89 Self::InvalidTransactionSize { size, max_size }
90 }
91 pub fn trailing_bytes(expected: usize, found: usize) -> Self {
92 Self::TrailingBytes { expected, found }
93 }
94 pub fn too_many_accounts(count: usize, max_count: usize) -> Self {
95 Self::TooManyAccounts { count, max_count }
96 }
97 pub fn invalid_transaction_signature() -> Self {
98 Self::InvalidTransactionSignature
99 }
100 pub fn invalid_params(msg: &'static str) -> Self {
101 Self::InvalidParams(msg)
102 }
103 pub fn invalid_format() -> Self {
104 Self::InvalidFormat
105 }
106 pub fn invalid_version() -> Self {
107 Self::InvalidVersion
108 }
109 pub fn invalid_flags() -> Self {
110 Self::InvalidFlags
111 }
112 pub fn invalid_fee_payer_state_proof_type() -> Self {
113 Self::InvalidFeePayerStateProofType
114 }
115}
116
117#[repr(C)]
119#[derive(Clone, Copy, Debug)]
120pub struct WireTxnHdrV1 {
121 pub fee_payer_signature: [u8; 64],
122 pub transaction_version: u8,
123 pub flags: u8,
124 pub readwrite_accounts_cnt: u16,
125 pub readonly_accounts_cnt: u16,
126 pub instr_data_sz: u16,
127 pub req_compute_units: u32,
128 pub req_state_units: u16,
129 pub req_memory_units: u16,
130 pub fee: u64,
131 pub nonce: u64,
132 pub start_slot: u64,
133 pub expiry_after: u32,
134 pub padding_0: [u8; 4],
135 pub fee_payer_pubkey: [u8; 32],
136 pub program_pubkey: [u8; 32],
137}
138
139impl Default for WireTxnHdrV1 {
140 fn default() -> Self {
141 Self {
142 fee_payer_signature: [0u8; 64],
143 transaction_version: 0,
144 flags: 0,
145 readwrite_accounts_cnt: 0,
146 readonly_accounts_cnt: 0,
147 instr_data_sz: 0,
148 req_compute_units: 0,
149 req_state_units: 0,
150 req_memory_units: 0,
151 fee: 0,
152 nonce: 0,
153 start_slot: 0,
154 expiry_after: 0,
155 padding_0: [0u8; 4],
156 fee_payer_pubkey: [0u8; 32],
157 program_pubkey: [0u8; 32],
158 }
159 }
160}
161
162unsafe impl Pod for WireTxnHdrV1 {}
164unsafe impl Zeroable for WireTxnHdrV1 {}
165
166#[derive(Clone, Debug, Default)]
168pub struct Transaction {
169 pub fee_payer: TnPubkey, pub program: TnPubkey, pub rw_accs: Option<Vec<TnPubkey>>, pub r_accs: Option<Vec<TnPubkey>>, pub instructions: Option<Vec<u8>>, pub fee: u64, pub req_compute_units: u32, pub req_state_units: u16, pub req_memory_units: u16, pub expiry_after: u32, pub start_slot: u64, pub nonce: u64, pub flags: u8, pub signature: Option<TnSignature>, pub fee_payer_state_proof: Option<StateProof>, pub fee_payer_account_meta_raw: Option<Vec<u8>>,
197}
198
199impl Transaction {
200 pub fn new(fee_payer: TnPubkey, program: TnPubkey, fee: u64, nonce: u64) -> Self {
202 Self {
203 fee_payer,
204 program,
205 rw_accs: None,
206 r_accs: None,
207 instructions: None,
208 fee,
209 req_compute_units: 0,
210 req_state_units: 0,
211 req_memory_units: 0,
212 expiry_after: 0,
213 start_slot: 0,
214 nonce,
215 flags: 0,
216 signature: None,
217 fee_payer_state_proof: None,
218 fee_payer_account_meta_raw: None,
219 }
220 }
221
222 pub fn has_fee_payer_state_proof(&self) -> bool {
223 (self.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT)) != 0
224 }
225 pub fn may_compress_account(&self) -> bool {
226 (self.flags & (1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT)) != 0
227 }
228
229 pub fn get_signature(&self) -> Option<crate::Signature> {
230 if let Some(sig) = &self.signature {
231 return Some(crate::Signature::from_bytes(&sig));
232 }
233 None
234 }
235
236 pub fn with_may_compress_account(mut self) -> Self {
237 self.flags |= 1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT;
238 self
239 }
240
241 pub fn with_fee_payer_state_proof(mut self, state_proof: &StateProof) -> Self {
243 self.fee_payer_state_proof = Some(state_proof.clone());
244 self.flags |= 1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT;
246 self
247 }
248
249 pub fn with_fee_payer_account_meta_raw(mut self, account_meta_raw: Vec<u8>) -> Self {
251 self.fee_payer_account_meta_raw = Some(account_meta_raw);
252 self
253 }
254
255 pub fn without_fee_payer_state_proof(mut self) -> Self {
257 self.fee_payer_state_proof = None;
258 self.flags &= !(1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT);
260 self
261 }
262
263 pub fn with_rw_accounts(mut self, accounts: Vec<TnPubkey>) -> Self {
265 self.rw_accs = Some(accounts);
266 self
267 }
268
269 pub fn with_r_accounts(mut self, accounts: Vec<TnPubkey>) -> Self {
271 self.r_accs = Some(accounts);
272 self
273 }
274
275 pub fn add_rw_account(mut self, account: TnPubkey) -> Self {
277 match self.rw_accs {
278 Some(ref mut accounts) => accounts.push(account),
279 None => self.rw_accs = Some(vec![account]),
280 }
281 self
282 }
283
284 pub fn add_r_account(mut self, account: TnPubkey) -> Self {
286 match self.r_accs {
287 Some(ref mut accounts) => accounts.push(account),
288 None => self.r_accs = Some(vec![account]),
289 }
290 self
291 }
292
293 pub fn with_instructions(mut self, instructions: Vec<u8>) -> Self {
295 self.instructions = Some(instructions);
296 self
297 }
298
299 pub fn with_compute_units(mut self, units: u32) -> Self {
301 self.req_compute_units = units;
302 self
303 }
304
305 pub fn with_state_units(mut self, units: u16) -> Self {
307 self.req_state_units = units;
308 self
309 }
310
311 pub fn with_memory_units(mut self, units: u16) -> Self {
313 self.req_memory_units = units;
314 self
315 }
316
317 pub fn with_expiry_after(mut self, expiry: u32) -> Self {
319 self.expiry_after = expiry;
320 self
321 }
322
323 pub fn with_nonce(mut self, nonce: u64) -> Self {
325 self.nonce = nonce;
326 self
327 }
328
329 pub fn with_start_slot(mut self, slot: u64) -> Self {
331 self.start_slot = slot;
332 self
333 }
334
335 pub fn sign(&mut self, private_key: &[u8; 32]) -> Result<(), Box<dyn std::error::Error>> {
337 let wire_bytes = self.to_wire_for_signing();
338 let sig = sign_transaction(&wire_bytes, &self.fee_payer, private_key)
339 .map_err(|e| Box::<dyn std::error::Error>::from(e))?;
340 self.signature = Some(sig);
341 Ok(())
342 }
343
344 pub fn verify(&self) -> bool {
346 if let Some(sig_bytes) = &self.signature {
347 let wire_bytes = self.to_wire_for_signing();
348 return verify_transaction(&wire_bytes, sig_bytes, &self.fee_payer).is_ok();
349 }
350 false
351 }
352
353 fn to_wire_for_signing(&self) -> Vec<u8> {
355 let mut wire: WireTxnHdrV1 = unsafe { core::mem::zeroed() };
357 wire.transaction_version = 1;
359 wire.flags = self.flags;
360 wire.readwrite_accounts_cnt = self.rw_accs.as_ref().map_or(0, |v| v.len() as u16);
361 wire.readonly_accounts_cnt = self.r_accs.as_ref().map_or(0, |v| v.len() as u16);
362 wire.instr_data_sz = self.instructions.as_ref().map_or(0, |v| v.len() as u16);
363 wire.req_compute_units = self.req_compute_units;
364 wire.req_state_units = self.req_state_units;
365 wire.req_memory_units = self.req_memory_units;
366 wire.expiry_after = self.expiry_after;
367 wire.fee = self.fee;
368 wire.nonce = self.nonce;
369 wire.start_slot = self.start_slot;
370 wire.fee_payer_pubkey = self.fee_payer;
371 wire.program_pubkey = self.program;
372
373 let wire_bytes = bytes_of(&wire);
374 let mut result = wire_bytes[64..].to_vec();
376
377 if let Some(ref rw_accs) = self.rw_accs {
379 for acc in rw_accs {
380 result.extend_from_slice(acc);
381 }
382 }
383
384 if let Some(ref r_accs) = self.r_accs {
385 for acc in r_accs {
386 result.extend_from_slice(acc);
387 }
388 }
389
390 if let Some(ref instructions) = self.instructions {
391 result.extend_from_slice(instructions);
392 }
393
394 if let Some(ref state_proof) = self.fee_payer_state_proof {
396 result.extend_from_slice(&state_proof.to_wire());
397 }
398
399 if let Some(ref fee_payer_account_meta_raw) = self.fee_payer_account_meta_raw {
401 result.extend_from_slice(fee_payer_account_meta_raw);
402 }
403
404 result
405 }
406
407 pub fn to_wire(&self) -> Vec<u8> {
409 let mut wire = WireTxnHdrV1::default();
410 if let Some(sig) = &self.signature {
411 wire.fee_payer_signature = *sig;
412 }
413 wire.transaction_version = 1;
414 wire.flags = self.flags;
415 wire.readwrite_accounts_cnt = self.rw_accs.as_ref().map_or(0, |v| v.len() as u16);
416 wire.readonly_accounts_cnt = self.r_accs.as_ref().map_or(0, |v| v.len() as u16);
417 wire.instr_data_sz = self.instructions.as_ref().map_or(0, |v| v.len() as u16);
418 wire.req_compute_units = self.req_compute_units;
419 wire.req_state_units = self.req_state_units;
420 wire.req_memory_units = self.req_memory_units;
421 wire.expiry_after = self.expiry_after;
422 wire.fee = self.fee;
423 wire.nonce = self.nonce;
424 wire.start_slot = self.start_slot;
425 wire.fee_payer_pubkey = self.fee_payer;
426 wire.program_pubkey = self.program;
427
428 let mut result = bytes_of(&wire).to_vec();
429
430 if let Some(ref rw_accs) = self.rw_accs {
432 for acc in rw_accs {
433 result.extend_from_slice(acc);
434 }
435 }
436
437 if let Some(ref r_accs) = self.r_accs {
438 for acc in r_accs {
439 result.extend_from_slice(acc);
440 }
441 }
442
443 if let Some(ref instructions) = self.instructions {
444 result.extend_from_slice(instructions);
445 }
446
447 if let Some(ref state_proof) = self.fee_payer_state_proof {
449 result.extend_from_slice(&state_proof.to_wire());
450 }
451
452 if let Some(ref fee_payer_account_meta_raw) = self.fee_payer_account_meta_raw {
454 result.extend_from_slice(fee_payer_account_meta_raw);
455 }
456
457 result
458 }
459
460 pub fn from_wire(bytes: &[u8]) -> Option<Self> {
462 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() {
463 return None;
464 }
465
466 let wire: &WireTxnHdrV1 = from_bytes(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
467 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
468
469 let rw_accs = if wire.readwrite_accounts_cnt > 0 {
471 let mut accounts = Vec::new();
472 for _ in 0..wire.readwrite_accounts_cnt {
473 if offset + 32 > bytes.len() {
474 return None;
475 }
476 let mut acc = [0u8; 32];
477 acc.copy_from_slice(&bytes[offset..offset + 32]);
478 accounts.push(acc);
479 offset += 32;
480 }
481 Some(accounts)
482 } else {
483 None
484 };
485
486 let r_accs = if wire.readonly_accounts_cnt > 0 {
488 let mut accounts = Vec::new();
489 for _ in 0..wire.readonly_accounts_cnt {
490 if offset + 32 > bytes.len() {
491 return None;
492 }
493 let mut acc = [0u8; 32];
494 acc.copy_from_slice(&bytes[offset..offset + 32]);
495 accounts.push(acc);
496 offset += 32;
497 }
498 Some(accounts)
499 } else {
500 None
501 };
502
503 let instructions = if wire.instr_data_sz > 0 {
505 if offset + wire.instr_data_sz as usize > bytes.len() {
506 return None;
507 }
508 let instr = bytes[offset..offset + wire.instr_data_sz as usize].to_vec();
509 offset += wire.instr_data_sz as usize;
510 Some(instr)
511 } else {
512 None
513 };
514
515 let mut fee_payer_account_meta_raw: Option<Vec<u8>> = None;
516 let fee_payer_state_proof = if has_fee_payer_state_proof(wire.flags) {
518 if offset >= bytes.len() {
519 return None;
520 }
521 let state_proof_bytes = &bytes[offset..];
522 if let Some(state_proof) = StateProof::from_wire(state_proof_bytes) {
523 offset += state_proof.footprint();
524 if state_proof.header.proof_type == StateProofType::Existing {
525 if offset + TN_ACCOUNT_META_FOOTPRINT > bytes.len() {
526 return None;
527 }
528 let account_meta_bytes = &bytes[offset..offset + TN_ACCOUNT_META_FOOTPRINT];
529 fee_payer_account_meta_raw = Some(account_meta_bytes.to_vec());
530 offset += TN_ACCOUNT_META_FOOTPRINT;
531 }
532 Some(state_proof)
533 } else {
534 return None;
535 }
536 } else {
537 None
538 };
539
540 if offset != bytes.len() {
542 log::warn!(
543 "Transaction::from_wire: offset != bytes.len() ({} != {})",
544 offset,
545 bytes.len()
546 );
547 return None;
548 }
549
550 Some(Transaction {
551 fee_payer: wire.fee_payer_pubkey,
552 program: wire.program_pubkey,
553 rw_accs,
554 r_accs,
555 instructions,
556 flags: wire.flags,
557 fee: wire.fee,
558 req_compute_units: wire.req_compute_units,
559 req_state_units: wire.req_state_units,
560 req_memory_units: wire.req_memory_units,
561 expiry_after: wire.expiry_after,
562 start_slot: wire.start_slot,
563 nonce: wire.nonce,
564 signature: Some(wire.fee_payer_signature),
565 fee_payer_state_proof,
566 fee_payer_account_meta_raw,
567 })
568 }
569
570 pub fn get_field_from_wire(bytes: &[u8], field: &str) -> Option<Vec<u8>> {
572 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() {
573 return None;
574 }
575 let wire: &WireTxnHdrV1 = from_bytes(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
576 match field {
577 "fee_payer_signature" => Some(wire.fee_payer_signature.to_vec()),
578 "transaction_version" => Some(vec![wire.transaction_version]),
579 "flags" => Some(vec![wire.flags]),
580 "readwrite_accounts_cnt" => Some(wire.readwrite_accounts_cnt.to_le_bytes().to_vec()),
581 "readonly_accounts_cnt" => Some(wire.readonly_accounts_cnt.to_le_bytes().to_vec()),
582 "instr_data_sz" => Some(wire.instr_data_sz.to_le_bytes().to_vec()),
583 "req_compute_units" => Some(wire.req_compute_units.to_le_bytes().to_vec()),
584 "req_state_units" => Some(wire.req_state_units.to_le_bytes().to_vec()),
585 "req_memory_units" => Some(wire.req_memory_units.to_le_bytes().to_vec()),
586 "expiry_after" => Some(wire.expiry_after.to_le_bytes().to_vec()),
587 "fee" => Some(wire.fee.to_le_bytes().to_vec()),
588 "nonce" => Some(wire.nonce.to_le_bytes().to_vec()),
589 "start_slot" => Some(wire.start_slot.to_le_bytes().to_vec()),
590 "fee_payer_pubkey" => Some(wire.fee_payer_pubkey.to_vec()),
591 "program_pubkey" => Some(wire.program_pubkey.to_vec()),
592 _ => None,
593 }
594 }
595}
596
597fn has_fee_payer_state_proof(flags: u8) -> bool {
599 (flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT)) != 0
600}
601
602fn extract_state_proof_type(type_slot: u64) -> u64 {
604 (type_slot >> 62) & 0x3 }
606
607fn calculate_state_proof_footprint(state_proof_data: &[u8]) -> Result<usize, RpcError> {
609 if state_proof_data.len() < TN_STATE_PROOF_HDR_SIZE {
610 return Err(RpcError::invalid_format());
611 }
612
613 let type_slot = u64::from_le_bytes([
615 state_proof_data[0],
616 state_proof_data[1],
617 state_proof_data[2],
618 state_proof_data[3],
619 state_proof_data[4],
620 state_proof_data[5],
621 state_proof_data[6],
622 state_proof_data[7],
623 ]);
624
625 let mut sibling_hash_cnt = 0u32;
627 for i in 0..4 {
628 let start = 8 + i * 8;
629 let word = u64::from_le_bytes([
630 state_proof_data[start],
631 state_proof_data[start + 1],
632 state_proof_data[start + 2],
633 state_proof_data[start + 3],
634 state_proof_data[start + 4],
635 state_proof_data[start + 5],
636 state_proof_data[start + 6],
637 state_proof_data[start + 7],
638 ]);
639 sibling_hash_cnt += word.count_ones();
640 }
641
642 let proof_type = extract_state_proof_type(type_slot);
643 let body_sz = (proof_type + sibling_hash_cnt as u64) * 32; Ok(TN_STATE_PROOF_HDR_SIZE + body_sz as usize)
646}
647
648pub fn tn_txn_size(bytes: &[u8]) -> Result<usize, RpcError> {
649 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() {
651 return Err(RpcError::invalid_format());
652 }
653
654 let hdr: WireTxnHdrV1 =
657 unsafe { std::ptr::read_unaligned(bytes.as_ptr() as *const WireTxnHdrV1) };
658 let hdr = &hdr;
659 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
660
661 let accs_sz = (hdr.readwrite_accounts_cnt as usize + hdr.readonly_accounts_cnt as usize) * 32;
663 if offset + accs_sz > bytes.len() {
664 return Err(RpcError::invalid_format());
665 }
666 offset += accs_sz;
667
668 let instr_sz = hdr.instr_data_sz as usize;
670 if offset + instr_sz > bytes.len() {
671 return Err(RpcError::invalid_format());
672 }
673 offset += instr_sz;
674
675 if has_fee_payer_state_proof(hdr.flags) {
677 if offset + TN_STATE_PROOF_HDR_SIZE > bytes.len() {
679 return Err(RpcError::invalid_format());
680 }
681
682 let state_proof_data = &bytes[offset..];
684 let state_proof_sz = calculate_state_proof_footprint(state_proof_data)?;
685
686 if offset + state_proof_sz > bytes.len() {
687 return Err(RpcError::invalid_format());
688 }
689 offset += state_proof_sz;
690
691 let type_slot = u64::from_le_bytes([
693 state_proof_data[0],
694 state_proof_data[1],
695 state_proof_data[2],
696 state_proof_data[3],
697 state_proof_data[4],
698 state_proof_data[5],
699 state_proof_data[6],
700 state_proof_data[7],
701 ]);
702 let proof_type = extract_state_proof_type(type_slot);
703
704 if proof_type == TN_STATE_PROOF_TYPE_EXISTING {
706 if offset + TN_ACCOUNT_META_FOOTPRINT > bytes.len() {
707 return Err(RpcError::invalid_format());
708 }
709 offset += TN_ACCOUNT_META_FOOTPRINT;
710 }
711 }
712
713 if offset > bytes.len() {
715 return Err(RpcError::invalid_format());
716 }
717
718 Ok(offset)
719}
720
721pub fn validate_wire_transaction(bytes: &[u8]) -> Result<(), RpcError> {
723 const TN_TXN_MTU: usize = 32_768;
724 const TN_TXN_VERSION_OFFSET: usize = 64;
725 const TN_TXN_FLAGS_OFFSET: usize = 65;
726
727 use bytemuck::from_bytes;
728
729 if bytes.len() > TN_TXN_MTU {
731 return Err(RpcError::invalid_transaction_size(bytes.len(), TN_TXN_MTU));
732 }
733
734 if bytes.len() <= TN_TXN_VERSION_OFFSET {
736 return Err(RpcError::invalid_format());
737 }
738 let transaction_version = bytes[TN_TXN_VERSION_OFFSET];
739 if transaction_version != 0x01 {
740 return Err(RpcError::invalid_version());
741 }
742
743 if bytes.len() <= TN_TXN_FLAGS_OFFSET {
745 return Err(RpcError::invalid_format());
746 }
747 let flags = bytes[TN_TXN_FLAGS_OFFSET];
748 let flags_without_proof_bit = flags & !(1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT);
750 let flags_cleared = flags_without_proof_bit & !(1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT);
751 if flags_cleared != 0 {
752 return Err(RpcError::invalid_flags());
753 }
754
755 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() {
757 return Err(RpcError::invalid_format());
758 }
759 let hdr: &WireTxnHdrV1 = from_bytes(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
760 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
761
762 let accs_sz = (hdr.readwrite_accounts_cnt as usize + hdr.readonly_accounts_cnt as usize) * 32;
764 if offset + accs_sz > bytes.len() {
765 return Err(RpcError::invalid_format());
766 }
767 offset += accs_sz;
768
769 let instr_sz = hdr.instr_data_sz as usize;
771 if offset + instr_sz > bytes.len() {
772 return Err(RpcError::invalid_format());
773 }
774 offset += instr_sz;
775
776 if has_fee_payer_state_proof(flags) {
778 if offset + TN_STATE_PROOF_HDR_SIZE > bytes.len() {
780 return Err(RpcError::invalid_format());
781 }
782
783 let state_proof_data = &bytes[offset..];
785 let state_proof_sz = calculate_state_proof_footprint(state_proof_data)?;
786
787 if offset + state_proof_sz > bytes.len() {
788 return Err(RpcError::invalid_format());
789 }
790
791 let type_slot = u64::from_le_bytes([
793 state_proof_data[0],
794 state_proof_data[1],
795 state_proof_data[2],
796 state_proof_data[3],
797 state_proof_data[4],
798 state_proof_data[5],
799 state_proof_data[6],
800 state_proof_data[7],
801 ]);
802 let proof_type = extract_state_proof_type(type_slot);
803
804 if proof_type == TN_STATE_PROOF_TYPE_UPDATING {
806 return Err(RpcError::invalid_fee_payer_state_proof_type());
807 }
808
809 offset += state_proof_sz;
810
811 if proof_type == TN_STATE_PROOF_TYPE_EXISTING {
813 if offset + TN_ACCOUNT_META_FOOTPRINT > bytes.len() {
814 return Err(RpcError::invalid_format());
815 }
816 offset += TN_ACCOUNT_META_FOOTPRINT;
817 }
818 }
819
820 if offset != bytes.len() {
822 return Err(RpcError::trailing_bytes(offset, bytes.len()));
824 }
825 let wire_for_signing = &bytes[64..]; if verify_transaction(
828 wire_for_signing,
829 &hdr.fee_payer_signature,
830 &hdr.fee_payer_pubkey,
831 )
832 .is_err()
833 {
834 return Err(RpcError::invalid_transaction_signature());
835 }
836
837 Ok(())
838}
839
840#[cfg(test)]
841mod tests {
842 use super::*;
843 use ed25519_dalek::SigningKey;
844
845 fn make_valid_txn_bytes_with_flags(flags: u8) -> Vec<u8> {
846 let signing_key = SigningKey::from(&[1u8; 32]);
847 let verifying_key = signing_key.verifying_key();
848 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
849 tx.rw_accs = Some(vec![[3u8; 32], [4u8; 32]]);
850 tx.r_accs = Some(vec![[5u8; 32]]);
851 tx.instructions = Some(vec![1, 2, 3, 4]);
852 tx.flags = flags;
853 tx.sign(&signing_key.to_bytes()).unwrap();
854 tx.to_wire()
855 }
856
857 fn make_valid_txn_bytes() -> Vec<u8> {
858 make_valid_txn_bytes_with_flags(0)
859 }
860
861 #[test]
862 fn test_tn_txn_size_basic_transaction() {
863 let bytes = make_valid_txn_bytes();
864 let calculated_size = tn_txn_size(&bytes).unwrap();
865
866 assert_eq!(calculated_size, bytes.len());
868 }
869
870 #[test]
871 fn test_tn_txn_size_with_state_proof() {
872 use crate::tn_state_proof::StateProof;
873
874 let signing_key = SigningKey::from(&[1u8; 32]);
875 let verifying_key = signing_key.verifying_key();
876
877 let path_bitset = [0u8; 32]; let existing_leaf_pubkey = [7u8; 32];
880 let existing_leaf_hash = [8u8; 32];
881 let state_proof = StateProof::creation(
882 100,
883 path_bitset,
884 existing_leaf_pubkey,
885 existing_leaf_hash,
886 vec![],
887 );
888
889 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
891 .with_rw_accounts(vec![[3u8; 32]])
892 .with_instructions(vec![1, 2, 3])
893 .with_fee_payer_state_proof(&state_proof);
894
895 tx.sign(&signing_key.to_bytes()).unwrap();
896 let bytes = tx.to_wire();
897
898 let calculated_size = tn_txn_size(&bytes).unwrap();
899
900 assert_eq!(calculated_size, bytes.len());
902 }
903
904 #[test]
905 fn test_tn_txn_size_minimal_transaction() {
906 let signing_key = SigningKey::from(&[1u8; 32]);
907 let verifying_key = signing_key.verifying_key();
908
909 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
911 tx.sign(&signing_key.to_bytes()).unwrap();
912 let bytes = tx.to_wire();
913
914 let calculated_size = tn_txn_size(&bytes).unwrap();
915
916 assert_eq!(calculated_size, bytes.len());
918
919 assert_eq!(calculated_size, core::mem::size_of::<WireTxnHdrV1>());
921 }
922
923 #[test]
924 fn test_tn_txn_size_invalid_format() {
925 let short_bytes = vec![0u8; 50];
927 let result = tn_txn_size(&short_bytes);
928 assert!(matches!(result, Err(RpcError::InvalidFormat)));
929
930 let mut bytes = make_valid_txn_bytes();
932 bytes.truncate(core::mem::size_of::<WireTxnHdrV1>() + 10); let result = tn_txn_size(&bytes);
934 assert!(matches!(result, Err(RpcError::InvalidFormat)));
935 }
936
937 #[test]
938 fn test_tn_txn_size_consistency_with_validation() {
939 let bytes = make_valid_txn_bytes();
940
941 assert!(validate_wire_transaction(&bytes).is_ok());
943 assert!(tn_txn_size(&bytes).is_ok());
944
945 let calculated_size = tn_txn_size(&bytes).unwrap();
947 assert_eq!(calculated_size, bytes.len());
948 }
949
950 #[test]
951 fn test_valid_transaction() {
952 let bytes = make_valid_txn_bytes();
953 assert!(validate_wire_transaction(&bytes).is_ok());
954 }
955
956 #[test]
957 fn test_oversize_transaction() {
958 let mut bytes = make_valid_txn_bytes();
959 bytes.resize(32_769, 0);
960 let err = validate_wire_transaction(&bytes).unwrap_err();
961 assert!(matches!(
962 err,
963 RpcError::InvalidTransactionSize {
964 size: 32_769,
965 max_size: 32_768
966 }
967 ));
968 }
969
970 #[test]
971 fn test_trailing_bytes() {
972 let mut bytes = make_valid_txn_bytes();
973 bytes.push(0);
974 let err = validate_wire_transaction(&bytes).unwrap_err();
975 assert!(matches!(
976 err,
977 RpcError::TrailingBytes {
978 expected: 276,
979 found: 277
980 }
981 ));
982 }
983
984 #[test]
985 fn test_invalid_transaction_version() {
986 let mut bytes = make_valid_txn_bytes();
987 bytes[64] = 0x02; let err = validate_wire_transaction(&bytes).unwrap_err();
990 assert!(matches!(err, RpcError::InvalidVersion));
991 }
992
993 #[test]
994 fn test_invalid_flags() {
995 let bytes = make_valid_txn_bytes_with_flags(0x07);
997 let err = validate_wire_transaction(&bytes).unwrap_err();
998 assert!(matches!(err, RpcError::InvalidFlags));
999 }
1000
1001 #[test]
1002 fn test_transaction_too_short() {
1003 let bytes = vec![0u8; 50]; let err = validate_wire_transaction(&bytes).unwrap_err();
1005 assert!(matches!(err, RpcError::InvalidFormat));
1006 }
1007
1008 #[test]
1009 fn test_transaction_with_state_proof() {
1010 use crate::tn_state_proof::{StateProof, StateProofType};
1011
1012 let signing_key = SigningKey::from(&[1u8; 32]);
1013 let verifying_key = signing_key.verifying_key();
1014
1015 let path_bitset = [0u8; 32]; let existing_leaf_pubkey = [7u8; 32];
1018 let existing_leaf_hash = [8u8; 32];
1019 let state_proof = StateProof::creation(
1020 100,
1021 path_bitset,
1022 existing_leaf_pubkey,
1023 existing_leaf_hash,
1024 vec![],
1025 );
1026
1027 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1029 .with_fee_payer_state_proof(&state_proof);
1030
1031 assert!(tx.has_fee_payer_state_proof());
1033 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 1);
1034
1035 tx.sign(&signing_key.to_bytes()).unwrap();
1036 let bytes = tx.to_wire();
1037
1038 assert!(bytes.len() > 168); assert!(validate_wire_transaction(&bytes).is_ok());
1041
1042 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1044 assert!(decoded_tx.has_fee_payer_state_proof());
1045 assert!(decoded_tx.fee_payer_state_proof.is_some());
1046
1047 let decoded_proof = decoded_tx.fee_payer_state_proof.unwrap();
1048 assert_eq!(decoded_proof.proof_type(), StateProofType::Creation);
1049 assert_eq!(decoded_proof.slot(), 100);
1050 }
1051
1052 #[test]
1053 fn test_transaction_with_state_proof_serialization_round_trip() {
1054 use crate::tn_state_proof::StateProof;
1055
1056 let signing_key = SigningKey::from(&[1u8; 32]);
1057 let verifying_key = signing_key.verifying_key();
1058
1059 let mut path_bitset = [0u8; 32];
1061 path_bitset[0] = 0b11; let existing_leaf_pubkey = [7u8; 32];
1063 let existing_leaf_hash = [8u8; 32];
1064 let sibling_hashes = vec![[9u8; 32], [10u8; 32]];
1065
1066 let state_proof = StateProof::creation(
1067 200,
1068 path_bitset,
1069 existing_leaf_pubkey,
1070 existing_leaf_hash,
1071 sibling_hashes.clone(),
1072 );
1073
1074 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1076 .with_rw_accounts(vec![[3u8; 32], [4u8; 32]])
1077 .with_r_accounts(vec![[5u8; 32]])
1078 .with_instructions(vec![1, 2, 3, 4])
1079 .with_fee_payer_state_proof(&state_proof);
1080
1081 tx.sign(&signing_key.to_bytes()).unwrap();
1082 let bytes = tx.to_wire();
1083
1084 assert!(validate_wire_transaction(&bytes).is_ok());
1086
1087 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1089 assert_eq!(decoded_tx.fee_payer, tx.fee_payer);
1090 assert_eq!(decoded_tx.program, tx.program);
1091 assert_eq!(decoded_tx.rw_accs, tx.rw_accs);
1092 assert_eq!(decoded_tx.r_accs, tx.r_accs);
1093 assert_eq!(decoded_tx.instructions, tx.instructions);
1094 assert_eq!(decoded_tx.flags, tx.flags);
1095 assert!(decoded_tx.has_fee_payer_state_proof());
1096
1097 let decoded_proof = decoded_tx.fee_payer_state_proof.unwrap();
1098 assert_eq!(decoded_proof.slot(), 200);
1099 assert_eq!(decoded_proof.path_bitset(), &path_bitset);
1100 }
1101
1102 #[test]
1103 fn test_transaction_without_state_proof() {
1104 let signing_key = SigningKey::from(&[1u8; 32]);
1105 let verifying_key = signing_key.verifying_key();
1106
1107 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
1108
1109 assert!(!tx.has_fee_payer_state_proof());
1111 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 0);
1112 assert!(tx.fee_payer_state_proof.is_none());
1113
1114 tx.sign(&signing_key.to_bytes()).unwrap();
1115 let bytes = tx.to_wire();
1116
1117 assert!(validate_wire_transaction(&bytes).is_ok());
1118
1119 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1121 assert!(!decoded_tx.has_fee_payer_state_proof());
1122 assert!(decoded_tx.fee_payer_state_proof.is_none());
1123 }
1124
1125 #[test]
1126 fn test_transaction_remove_state_proof() {
1127 use crate::tn_state_proof::StateProof;
1128
1129 let signing_key = SigningKey::from(&[1u8; 32]);
1130 let verifying_key = signing_key.verifying_key();
1131
1132 let path_bitset = [0u8; 32];
1134 let existing_leaf_pubkey = [7u8; 32];
1135 let existing_leaf_hash = [8u8; 32];
1136 let state_proof = StateProof::creation(
1137 100,
1138 path_bitset,
1139 existing_leaf_pubkey,
1140 existing_leaf_hash,
1141 vec![],
1142 );
1143
1144 let tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1146 .with_fee_payer_state_proof(&state_proof)
1147 .without_fee_payer_state_proof();
1148
1149 assert!(!tx.has_fee_payer_state_proof());
1151 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 0);
1152 assert!(tx.fee_payer_state_proof.is_none());
1153 }
1154}