1pub type TnPubkey = [u8; 32];
5pub type TnHash = [u8; 32];
6pub type TnSignature = [u8; 64];
7
8use crate::{
9 StateProofType,
10 tn_signature::{sign_transaction, verify_transaction},
11 tn_state_proof::StateProof,
12};
13use bytemuck::{Pod, Zeroable, bytes_of, pod_read_unaligned};
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 InvalidChainId,
40 DuplicateAccount,
41 UnsortedReadwriteAccounts,
42 UnsortedReadonlyAccounts,
43}
44
45impl std::fmt::Display for RpcError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 RpcError::InvalidTransactionSize { size, max_size } => {
49 write!(
50 f,
51 "Transaction size {} exceeds maximum allowed size {}",
52 size, max_size
53 )
54 }
55 RpcError::TrailingBytes { expected, found } => {
56 write!(
57 f,
58 "Transaction has trailing bytes: expected {} bytes, found {} bytes",
59 expected, found
60 )
61 }
62 RpcError::TooManyAccounts { count, max_count } => {
63 write!(
64 f,
65 "Too many accounts: {} exceeds maximum {}",
66 count, max_count
67 )
68 }
69 RpcError::InvalidTransactionSignature => {
70 write!(f, "Invalid transaction signature")
71 }
72 RpcError::InvalidParams(msg) => {
73 write!(f, "Invalid parameters: {}", msg)
74 }
75 RpcError::InvalidFormat => {
76 write!(f, "Invalid transaction format")
77 }
78 RpcError::InvalidVersion => {
79 write!(f, "Invalid transaction version")
80 }
81 RpcError::InvalidFlags => {
82 write!(f, "Invalid transaction flags")
83 }
84 RpcError::InvalidFeePayerStateProofType => {
85 write!(f, "Invalid fee payer state proof type")
86 }
87 RpcError::InvalidChainId => {
88 write!(f, "Invalid chain ID: chain_id cannot be zero")
89 }
90 RpcError::DuplicateAccount => {
91 write!(f, "Duplicate account in transaction")
92 }
93 RpcError::UnsortedReadwriteAccounts => {
94 write!(f, "Read-write accounts are not strictly ascending")
95 }
96 RpcError::UnsortedReadonlyAccounts => {
97 write!(f, "Read-only accounts are not strictly ascending")
98 }
99 }
100 }
101}
102
103impl std::error::Error for RpcError {}
104
105impl RpcError {
106 pub fn invalid_transaction_size(size: usize, max_size: usize) -> Self {
107 Self::InvalidTransactionSize { size, max_size }
108 }
109 pub fn trailing_bytes(expected: usize, found: usize) -> Self {
110 Self::TrailingBytes { expected, found }
111 }
112 pub fn too_many_accounts(count: usize, max_count: usize) -> Self {
113 Self::TooManyAccounts { count, max_count }
114 }
115 pub fn invalid_transaction_signature() -> Self {
116 Self::InvalidTransactionSignature
117 }
118 pub fn invalid_params(msg: &'static str) -> Self {
119 Self::InvalidParams(msg)
120 }
121 pub fn invalid_format() -> Self {
122 Self::InvalidFormat
123 }
124 pub fn invalid_version() -> Self {
125 Self::InvalidVersion
126 }
127 pub fn invalid_flags() -> Self {
128 Self::InvalidFlags
129 }
130 pub fn invalid_fee_payer_state_proof_type() -> Self {
131 Self::InvalidFeePayerStateProofType
132 }
133 pub fn invalid_chain_id() -> Self {
134 Self::InvalidChainId
135 }
136 pub fn duplicate_account() -> Self {
137 Self::DuplicateAccount
138 }
139 pub fn unsorted_readwrite_accounts() -> Self {
140 Self::UnsortedReadwriteAccounts
141 }
142 pub fn unsorted_readonly_accounts() -> Self {
143 Self::UnsortedReadonlyAccounts
144 }
145}
146
147#[repr(C)]
157#[derive(Clone, Copy, Debug)]
158pub struct WireTxnHdrV1 {
159 pub transaction_version: u8,
160 pub flags: u8,
161 pub readwrite_accounts_cnt: u16,
162 pub readonly_accounts_cnt: u16,
163 pub instr_data_sz: u16,
164 pub req_compute_units: u32,
165 pub req_state_units: u16,
166 pub req_memory_units: u16,
167 pub fee: u64,
168 pub nonce: u64,
169 pub start_slot: u64,
170 pub expiry_after: u32,
171 pub chain_id: u16,
172 pub padding_0: [u8; 2],
173 pub fee_payer_pubkey: [u8; 32],
174 pub program_pubkey: [u8; 32],
175}
176
177pub const TN_TXN_SIGNATURE_SZ: usize = 64;
179pub const TN_TXN_MAX_ACCOUNTS: usize = 1024;
180
181impl Default for WireTxnHdrV1 {
182 fn default() -> Self {
183 Self {
184 transaction_version: 0,
185 flags: 0,
186 readwrite_accounts_cnt: 0,
187 readonly_accounts_cnt: 0,
188 instr_data_sz: 0,
189 req_compute_units: 0,
190 req_state_units: 0,
191 req_memory_units: 0,
192 fee: 0,
193 nonce: 0,
194 start_slot: 0,
195 expiry_after: 0,
196 chain_id: 0,
197 padding_0: [0u8; 2],
198 fee_payer_pubkey: [0u8; 32],
199 program_pubkey: [0u8; 32],
200 }
201 }
202}
203
204unsafe impl Pod for WireTxnHdrV1 {}
206unsafe impl Zeroable for WireTxnHdrV1 {}
207
208#[derive(Clone, Debug, Default)]
210pub struct Transaction {
211 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 chain_id: u16, pub signature: Option<TnSignature>, pub fee_payer_state_proof: Option<StateProof>, pub fee_payer_account_meta_raw: Option<Vec<u8>>,
240}
241
242impl Transaction {
243 pub fn new(fee_payer: TnPubkey, program: TnPubkey, fee: u64, nonce: u64) -> Self {
245 Self {
246 fee_payer,
247 program,
248 rw_accs: None,
249 r_accs: None,
250 instructions: None,
251 fee,
252 req_compute_units: 0,
253 req_state_units: 0,
254 req_memory_units: 0,
255 expiry_after: 0,
256 start_slot: 0,
257 nonce,
258 flags: 0,
259 chain_id: 1,
260 signature: None,
261 fee_payer_state_proof: None,
262 fee_payer_account_meta_raw: None,
263 }
264 }
265
266 pub fn new_raw_instruction(
269 fee_payer: &TnPubkey,
270 program: &TnPubkey,
271 instruction_data: &[u8],
272 ) -> Result<Self, Box<dyn std::error::Error>> {
273 Ok(Self {
274 fee_payer: *fee_payer,
275 program: *program,
276 rw_accs: None,
277 r_accs: None,
278 instructions: Some(instruction_data.to_vec()),
279 fee: 0,
280 req_compute_units: 0,
281 req_state_units: 0,
282 req_memory_units: 0,
283 expiry_after: 0,
284 start_slot: 0,
285 nonce: 0,
286 flags: 0,
287 chain_id: 1,
288 signature: None,
289 fee_payer_state_proof: None,
290 fee_payer_account_meta_raw: None,
291 })
292 }
293
294 pub fn has_fee_payer_state_proof(&self) -> bool {
295 (self.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT)) != 0
296 }
297 pub fn may_compress_account(&self) -> bool {
298 (self.flags & (1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT)) != 0
299 }
300
301 pub fn get_signature(&self) -> Option<crate::Signature> {
302 if let Some(sig) = &self.signature {
303 return Some(crate::Signature::from_bytes(&sig));
304 }
305 None
306 }
307
308 pub fn with_may_compress_account(mut self) -> Self {
309 self.flags |= 1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT;
310 self
311 }
312
313 pub fn with_fee_payer_state_proof(mut self, state_proof: &StateProof) -> Self {
315 self.fee_payer_state_proof = Some(state_proof.clone());
316 self.flags |= 1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT;
318 self
319 }
320
321 pub fn with_fee_payer_account_meta_raw(mut self, account_meta_raw: Vec<u8>) -> Self {
323 self.fee_payer_account_meta_raw = Some(account_meta_raw);
324 self
325 }
326
327 pub fn without_fee_payer_state_proof(mut self) -> Self {
329 self.fee_payer_state_proof = None;
330 self.flags &= !(1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT);
332 self
333 }
334
335 pub fn with_rw_accounts(mut self, accounts: Vec<TnPubkey>) -> Self {
337 self.rw_accs = Some(accounts);
338 self
339 }
340
341 pub fn with_r_accounts(mut self, accounts: Vec<TnPubkey>) -> Self {
343 self.r_accs = Some(accounts);
344 self
345 }
346
347 pub fn add_rw_account(mut self, account: TnPubkey) -> Self {
349 match self.rw_accs {
350 Some(ref mut accounts) => accounts.push(account),
351 None => self.rw_accs = Some(vec![account]),
352 }
353 self
354 }
355
356 pub fn add_r_account(mut self, account: TnPubkey) -> Self {
358 match self.r_accs {
359 Some(ref mut accounts) => accounts.push(account),
360 None => self.r_accs = Some(vec![account]),
361 }
362 self
363 }
364
365 pub fn with_instructions(mut self, instructions: Vec<u8>) -> Self {
367 self.instructions = Some(instructions);
368 self
369 }
370
371 pub fn with_compute_units(mut self, units: u32) -> Self {
373 self.req_compute_units = units;
374 self
375 }
376
377 pub fn with_state_units(mut self, units: u16) -> Self {
379 self.req_state_units = units;
380 self
381 }
382
383 pub fn with_memory_units(mut self, units: u16) -> Self {
385 self.req_memory_units = units;
386 self
387 }
388
389 pub fn with_expiry_after(mut self, expiry: u32) -> Self {
391 self.expiry_after = expiry;
392 self
393 }
394
395 pub fn with_nonce(mut self, nonce: u64) -> Self {
397 self.nonce = nonce;
398 self
399 }
400
401 pub fn with_start_slot(mut self, slot: u64) -> Self {
403 self.start_slot = slot;
404 self
405 }
406
407 pub fn with_chain_id(mut self, chain_id: u16) -> Self {
409 self.chain_id = chain_id;
410 self
411 }
412
413 pub fn sign(&mut self, private_key: &[u8; 32]) -> Result<(), Box<dyn std::error::Error>> {
417 self.validate()
418 .map_err(|e| Box::<dyn std::error::Error>::from(e))?;
419 self.sign_unchecked(private_key)
420 }
421
422 pub fn sign_unchecked(
427 &mut self,
428 private_key: &[u8; 32],
429 ) -> Result<(), Box<dyn std::error::Error>> {
430 let wire_bytes = self.to_wire_for_signing();
431 let sig = sign_transaction(&wire_bytes, &self.fee_payer, private_key)
432 .map_err(|e| Box::<dyn std::error::Error>::from(e))?;
433 self.signature = Some(sig);
434 Ok(())
435 }
436
437 pub fn validate(&self) -> Result<(), RpcError> {
439 let rw_accs = self.rw_accs.as_deref().unwrap_or(&[]);
440 let r_accs = self.r_accs.as_deref().unwrap_or(&[]);
441 validate_account_layout(rw_accs, r_accs, &self.fee_payer, &self.program)
442 }
443
444 pub fn verify(&self) -> bool {
446 if let Some(sig_bytes) = &self.signature {
447 let wire_bytes = self.to_wire_for_signing();
448 return verify_transaction(&wire_bytes, sig_bytes, &self.fee_payer).is_ok();
449 }
450 false
451 }
452
453 fn to_wire_for_signing(&self) -> Vec<u8> {
456 let mut wire: WireTxnHdrV1 = unsafe { core::mem::zeroed() };
458 wire.transaction_version = 1;
459 wire.flags = self.flags;
460 wire.readwrite_accounts_cnt = self.rw_accs.as_ref().map_or(0, |v| v.len() as u16);
461 wire.readonly_accounts_cnt = self.r_accs.as_ref().map_or(0, |v| v.len() as u16);
462 wire.instr_data_sz = self.instructions.as_ref().map_or(0, |v| v.len() as u16);
463 wire.req_compute_units = self.req_compute_units;
464 wire.req_state_units = self.req_state_units;
465 wire.req_memory_units = self.req_memory_units;
466 wire.expiry_after = self.expiry_after;
467 wire.chain_id = self.chain_id;
468 wire.fee = self.fee;
469 wire.nonce = self.nonce;
470 wire.start_slot = self.start_slot;
471 wire.fee_payer_pubkey = self.fee_payer;
472 wire.program_pubkey = self.program;
473
474 let mut result = bytes_of(&wire).to_vec();
475
476 if let Some(ref rw_accs) = self.rw_accs {
478 for acc in rw_accs {
479 result.extend_from_slice(acc);
480 }
481 }
482
483 if let Some(ref r_accs) = self.r_accs {
484 for acc in r_accs {
485 result.extend_from_slice(acc);
486 }
487 }
488
489 if let Some(ref instructions) = self.instructions {
490 result.extend_from_slice(instructions);
491 }
492
493 if let Some(ref state_proof) = self.fee_payer_state_proof {
495 result.extend_from_slice(&state_proof.to_wire());
496 }
497
498 if let Some(ref fee_payer_account_meta_raw) = self.fee_payer_account_meta_raw {
500 result.extend_from_slice(fee_payer_account_meta_raw);
501 }
502
503 result
504 }
505
506 pub fn to_wire(&self) -> Vec<u8> {
516 self.serialize_wire()
517 }
518
519 pub fn try_to_wire(&self) -> Result<Vec<u8>, RpcError> {
523 self.validate()?;
524 Ok(self.serialize_wire())
525 }
526
527 fn serialize_wire(&self) -> Vec<u8> {
529 let mut wire = WireTxnHdrV1::default();
530 wire.transaction_version = 1;
531 wire.flags = self.flags;
532 wire.readwrite_accounts_cnt = self.rw_accs.as_ref().map_or(0, |v| v.len() as u16);
533 wire.readonly_accounts_cnt = self.r_accs.as_ref().map_or(0, |v| v.len() as u16);
534 wire.instr_data_sz = self.instructions.as_ref().map_or(0, |v| v.len() as u16);
535 wire.req_compute_units = self.req_compute_units;
536 wire.req_state_units = self.req_state_units;
537 wire.req_memory_units = self.req_memory_units;
538 wire.expiry_after = self.expiry_after;
539 wire.chain_id = self.chain_id;
540 wire.fee = self.fee;
541 wire.nonce = self.nonce;
542 wire.start_slot = self.start_slot;
543 wire.fee_payer_pubkey = self.fee_payer;
544 wire.program_pubkey = self.program;
545
546 let mut result = bytes_of(&wire).to_vec();
547
548 if let Some(ref rw_accs) = self.rw_accs {
550 for acc in rw_accs {
551 result.extend_from_slice(acc);
552 }
553 }
554
555 if let Some(ref r_accs) = self.r_accs {
556 for acc in r_accs {
557 result.extend_from_slice(acc);
558 }
559 }
560
561 if let Some(ref instructions) = self.instructions {
562 result.extend_from_slice(instructions);
563 }
564
565 if let Some(ref state_proof) = self.fee_payer_state_proof {
567 result.extend_from_slice(&state_proof.to_wire());
568 }
569
570 if let Some(ref fee_payer_account_meta_raw) = self.fee_payer_account_meta_raw {
572 result.extend_from_slice(fee_payer_account_meta_raw);
573 }
574
575 if let Some(sig) = &self.signature {
577 result.extend_from_slice(sig);
578 } else {
579 result.extend_from_slice(&[0u8; TN_TXN_SIGNATURE_SZ]);
581 }
582
583 result
584 }
585
586 pub fn from_wire(bytes: &[u8]) -> Option<Self> {
589 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ {
591 return None;
592 }
593
594 let wire: WireTxnHdrV1 = pod_read_unaligned(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
595 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
596
597 let sig_start = bytes.len() - TN_TXN_SIGNATURE_SZ;
598 let mut signature = [0u8; TN_TXN_SIGNATURE_SZ];
599 signature.copy_from_slice(&bytes[sig_start..]);
600
601 let rw_accs = if wire.readwrite_accounts_cnt > 0 {
603 let mut accounts = Vec::new();
604 for _ in 0..wire.readwrite_accounts_cnt {
605 if offset + 32 > sig_start {
606 return None;
607 }
608 let mut acc = [0u8; 32];
609 acc.copy_from_slice(&bytes[offset..offset + 32]);
610 accounts.push(acc);
611 offset += 32;
612 }
613 Some(accounts)
614 } else {
615 None
616 };
617
618 let r_accs = if wire.readonly_accounts_cnt > 0 {
620 let mut accounts = Vec::new();
621 for _ in 0..wire.readonly_accounts_cnt {
622 if offset + 32 > sig_start {
623 return None;
624 }
625 let mut acc = [0u8; 32];
626 acc.copy_from_slice(&bytes[offset..offset + 32]);
627 accounts.push(acc);
628 offset += 32;
629 }
630 Some(accounts)
631 } else {
632 None
633 };
634
635 let instructions = if wire.instr_data_sz > 0 {
637 if offset + wire.instr_data_sz as usize > sig_start {
638 return None;
639 }
640 let instr = bytes[offset..offset + wire.instr_data_sz as usize].to_vec();
641 offset += wire.instr_data_sz as usize;
642 Some(instr)
643 } else {
644 None
645 };
646
647 let mut fee_payer_account_meta_raw: Option<Vec<u8>> = None;
648 let fee_payer_state_proof = if has_fee_payer_state_proof(wire.flags) {
650 if offset >= sig_start {
651 return None;
652 }
653 let state_proof_bytes = &bytes[offset..sig_start];
654 if let Some(state_proof) = StateProof::from_wire(state_proof_bytes) {
655 offset += state_proof.footprint();
656 if state_proof.header.proof_type == StateProofType::Existing {
657 if offset + TN_ACCOUNT_META_FOOTPRINT > sig_start {
658 return None;
659 }
660 let account_meta_bytes = &bytes[offset..offset + TN_ACCOUNT_META_FOOTPRINT];
661 fee_payer_account_meta_raw = Some(account_meta_bytes.to_vec());
662 offset += TN_ACCOUNT_META_FOOTPRINT;
663 }
664 Some(state_proof)
665 } else {
666 return None;
667 }
668 } else {
669 None
670 };
671
672 if offset != sig_start {
674 log::warn!(
675 "Transaction::from_wire: offset != sig_start ({} != {})",
676 offset,
677 sig_start
678 );
679 return None;
680 }
681
682 Some(Transaction {
683 fee_payer: wire.fee_payer_pubkey,
684 program: wire.program_pubkey,
685 rw_accs,
686 r_accs,
687 instructions,
688 flags: wire.flags,
689 chain_id: wire.chain_id,
690 fee: wire.fee,
691 req_compute_units: wire.req_compute_units,
692 req_state_units: wire.req_state_units,
693 req_memory_units: wire.req_memory_units,
694 expiry_after: wire.expiry_after,
695 start_slot: wire.start_slot,
696 nonce: wire.nonce,
697 signature: Some(signature),
698 fee_payer_state_proof,
699 fee_payer_account_meta_raw,
700 })
701 }
702
703 pub fn get_field_from_wire(bytes: &[u8], field: &str) -> Option<Vec<u8>> {
705 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ {
706 return None;
707 }
708 let wire: WireTxnHdrV1 = pod_read_unaligned(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
709 match field {
710 "fee_payer_signature" => {
711 let sig_start = bytes.len() - TN_TXN_SIGNATURE_SZ;
712 Some(bytes[sig_start..].to_vec())
713 }
714 "transaction_version" => Some(vec![wire.transaction_version]),
715 "flags" => Some(vec![wire.flags]),
716 "readwrite_accounts_cnt" => Some(wire.readwrite_accounts_cnt.to_le_bytes().to_vec()),
717 "readonly_accounts_cnt" => Some(wire.readonly_accounts_cnt.to_le_bytes().to_vec()),
718 "instr_data_sz" => Some(wire.instr_data_sz.to_le_bytes().to_vec()),
719 "req_compute_units" => Some(wire.req_compute_units.to_le_bytes().to_vec()),
720 "req_state_units" => Some(wire.req_state_units.to_le_bytes().to_vec()),
721 "req_memory_units" => Some(wire.req_memory_units.to_le_bytes().to_vec()),
722 "expiry_after" => Some(wire.expiry_after.to_le_bytes().to_vec()),
723 "chain_id" => Some(wire.chain_id.to_le_bytes().to_vec()),
724 "fee" => Some(wire.fee.to_le_bytes().to_vec()),
725 "nonce" => Some(wire.nonce.to_le_bytes().to_vec()),
726 "start_slot" => Some(wire.start_slot.to_le_bytes().to_vec()),
727 "fee_payer_pubkey" => Some(wire.fee_payer_pubkey.to_vec()),
728 "program_pubkey" => Some(wire.program_pubkey.to_vec()),
729 _ => None,
730 }
731 }
732}
733
734fn has_fee_payer_state_proof(flags: u8) -> bool {
736 (flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT)) != 0
737}
738
739fn extract_state_proof_type(type_slot: u64) -> u64 {
741 (type_slot >> 62) & 0x3 }
743
744fn calculate_state_proof_footprint(state_proof_data: &[u8]) -> Result<usize, RpcError> {
746 if state_proof_data.len() < TN_STATE_PROOF_HDR_SIZE {
747 return Err(RpcError::invalid_format());
748 }
749
750 let type_slot = u64::from_le_bytes([
752 state_proof_data[0],
753 state_proof_data[1],
754 state_proof_data[2],
755 state_proof_data[3],
756 state_proof_data[4],
757 state_proof_data[5],
758 state_proof_data[6],
759 state_proof_data[7],
760 ]);
761
762 let mut sibling_hash_cnt = 0u32;
764 for i in 0..4 {
765 let start = 8 + i * 8;
766 let word = u64::from_le_bytes([
767 state_proof_data[start],
768 state_proof_data[start + 1],
769 state_proof_data[start + 2],
770 state_proof_data[start + 3],
771 state_proof_data[start + 4],
772 state_proof_data[start + 5],
773 state_proof_data[start + 6],
774 state_proof_data[start + 7],
775 ]);
776 sibling_hash_cnt += word.count_ones();
777 }
778
779 let proof_type = extract_state_proof_type(type_slot);
780 let body_sz = (proof_type + sibling_hash_cnt as u64) * 32; Ok(TN_STATE_PROOF_HDR_SIZE + body_sz as usize)
783}
784
785pub fn tn_txn_size(bytes: &[u8]) -> Result<usize, RpcError> {
786 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ {
788 return Err(RpcError::invalid_format());
789 }
790
791 let hdr: WireTxnHdrV1 =
794 unsafe { std::ptr::read_unaligned(bytes.as_ptr() as *const WireTxnHdrV1) };
795 let hdr = &hdr;
796 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
797
798 let accs_sz = (hdr.readwrite_accounts_cnt as usize + hdr.readonly_accounts_cnt as usize) * 32;
800 if offset + accs_sz > bytes.len() {
801 return Err(RpcError::invalid_format());
802 }
803 offset += accs_sz;
804
805 let instr_sz = hdr.instr_data_sz as usize;
807 if offset + instr_sz > bytes.len() {
808 return Err(RpcError::invalid_format());
809 }
810 offset += instr_sz;
811
812 if has_fee_payer_state_proof(hdr.flags) {
814 if offset + TN_STATE_PROOF_HDR_SIZE > bytes.len() {
816 return Err(RpcError::invalid_format());
817 }
818
819 let state_proof_data = &bytes[offset..];
821 let state_proof_sz = calculate_state_proof_footprint(state_proof_data)?;
822
823 if offset + state_proof_sz > bytes.len() {
824 return Err(RpcError::invalid_format());
825 }
826 offset += state_proof_sz;
827
828 let type_slot = u64::from_le_bytes([
830 state_proof_data[0],
831 state_proof_data[1],
832 state_proof_data[2],
833 state_proof_data[3],
834 state_proof_data[4],
835 state_proof_data[5],
836 state_proof_data[6],
837 state_proof_data[7],
838 ]);
839 let proof_type = extract_state_proof_type(type_slot);
840
841 if proof_type == TN_STATE_PROOF_TYPE_EXISTING {
843 if offset + TN_ACCOUNT_META_FOOTPRINT > bytes.len() {
844 return Err(RpcError::invalid_format());
845 }
846 offset += TN_ACCOUNT_META_FOOTPRINT;
847 }
848 }
849
850 offset += TN_TXN_SIGNATURE_SZ;
852
853 if offset > bytes.len() {
855 return Err(RpcError::invalid_format());
856 }
857
858 Ok(offset)
859}
860
861pub fn validate_account_layout(
870 rw_accs: &[TnPubkey],
871 r_accs: &[TnPubkey],
872 fee_payer: &TnPubkey,
873 program: &TnPubkey,
874) -> Result<(), RpcError> {
875 let total_accounts = 2usize
876 .checked_add(rw_accs.len())
877 .and_then(|v| v.checked_add(r_accs.len()))
878 .ok_or_else(|| RpcError::too_many_accounts(usize::MAX, TN_TXN_MAX_ACCOUNTS))?;
879 if total_accounts > TN_TXN_MAX_ACCOUNTS {
880 return Err(RpcError::too_many_accounts(total_accounts, TN_TXN_MAX_ACCOUNTS));
881 }
882 if fee_payer == program {
883 return Err(RpcError::duplicate_account());
884 }
885
886 let (mut i, mut j) = (0usize, 0usize);
887 let mut prev_rw: Option<&[u8; 32]> = None;
888 let mut prev_ro: Option<&[u8; 32]> = None;
889 while i < rw_accs.len() || j < r_accs.len() {
890 let use_rw = j >= r_accs.len() || (i < rw_accs.len() && rw_accs[i] <= r_accs[j]);
891 if use_rw {
892 let key = &rw_accs[i];
893 if j < r_accs.len() && *key == r_accs[j] {
894 return Err(RpcError::duplicate_account());
895 }
896 if let Some(prev) = prev_rw {
897 if prev == key {
898 return Err(RpcError::duplicate_account());
899 }
900 if prev > key {
901 return Err(RpcError::unsorted_readwrite_accounts());
902 }
903 }
904 prev_rw = Some(key);
905 i += 1;
906 } else {
907 let key = &r_accs[j];
908 if let Some(prev) = prev_ro {
909 if prev == key {
910 return Err(RpcError::duplicate_account());
911 }
912 if prev > key {
913 return Err(RpcError::unsorted_readonly_accounts());
914 }
915 }
916 prev_ro = Some(key);
917 j += 1;
918 }
919 }
920
921 for account in rw_accs.iter().chain(r_accs.iter()) {
922 if account == fee_payer || account == program {
923 return Err(RpcError::duplicate_account());
924 }
925 }
926
927 Ok(())
928}
929
930pub fn validate_wire_transaction(bytes: &[u8]) -> Result<(), RpcError> {
932 const TN_TXN_MTU: usize = 32_768;
933 const TN_TXN_VERSION_OFFSET: usize = 0;
935 const TN_TXN_FLAGS_OFFSET: usize = 1;
936
937 use bytemuck::pod_read_unaligned;
938
939 if bytes.len() > TN_TXN_MTU {
941 return Err(RpcError::invalid_transaction_size(bytes.len(), TN_TXN_MTU));
942 }
943
944 if bytes.len() < core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ {
946 return Err(RpcError::invalid_format());
947 }
948
949 let transaction_version = bytes[TN_TXN_VERSION_OFFSET];
951 if transaction_version != 0x01 {
952 return Err(RpcError::invalid_version());
953 }
954
955 let flags = bytes[TN_TXN_FLAGS_OFFSET];
957 let flags_without_proof_bit = flags & !(1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT);
959 let flags_cleared = flags_without_proof_bit & !(1 << TN_TXN_FLAG_MAY_COMPRESS_ACCOUNT_BIT);
960 if flags_cleared != 0 {
961 return Err(RpcError::invalid_flags());
962 }
963
964 let hdr: WireTxnHdrV1 = pod_read_unaligned(&bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
966 let mut offset = core::mem::size_of::<WireTxnHdrV1>();
967
968 let sig_start = bytes.len() - TN_TXN_SIGNATURE_SZ;
969
970 if hdr.chain_id == 0 {
972 return Err(RpcError::invalid_chain_id());
973 }
974
975 let accs_sz = (hdr.readwrite_accounts_cnt as usize + hdr.readonly_accounts_cnt as usize) * 32;
977 if offset + accs_sz > sig_start {
978 return Err(RpcError::invalid_format());
979 }
980 offset += accs_sz;
981
982 let instr_sz = hdr.instr_data_sz as usize;
984 if offset + instr_sz > sig_start {
985 return Err(RpcError::invalid_format());
986 }
987 offset += instr_sz;
988
989 if has_fee_payer_state_proof(flags) {
991 if offset + TN_STATE_PROOF_HDR_SIZE > sig_start {
993 return Err(RpcError::invalid_format());
994 }
995
996 let state_proof_data = &bytes[offset..sig_start];
997
998 let type_slot = u64::from_le_bytes([
1003 state_proof_data[0],
1004 state_proof_data[1],
1005 state_proof_data[2],
1006 state_proof_data[3],
1007 state_proof_data[4],
1008 state_proof_data[5],
1009 state_proof_data[6],
1010 state_proof_data[7],
1011 ]);
1012 let proof_type = extract_state_proof_type(type_slot);
1013 if proof_type != TN_STATE_PROOF_TYPE_EXISTING && proof_type != TN_STATE_PROOF_TYPE_CREATION
1014 {
1015 return Err(RpcError::invalid_fee_payer_state_proof_type());
1016 }
1017
1018 let state_proof_sz = calculate_state_proof_footprint(state_proof_data)?;
1020
1021 if offset + state_proof_sz > sig_start {
1022 return Err(RpcError::invalid_format());
1023 }
1024
1025 offset += state_proof_sz;
1026
1027 if proof_type == TN_STATE_PROOF_TYPE_EXISTING {
1029 if offset + TN_ACCOUNT_META_FOOTPRINT > sig_start {
1030 return Err(RpcError::invalid_format());
1031 }
1032 offset += TN_ACCOUNT_META_FOOTPRINT;
1033 }
1034 }
1035
1036 if offset != sig_start {
1038 return Err(RpcError::trailing_bytes(
1039 offset + TN_TXN_SIGNATURE_SZ,
1040 bytes.len(),
1041 ));
1042 }
1043
1044 {
1049 let accs_start = core::mem::size_of::<WireTxnHdrV1>();
1050 let rw_cnt = hdr.readwrite_accounts_cnt as usize;
1051 let ro_cnt = hdr.readonly_accounts_cnt as usize;
1052 let mut rw_accs: Vec<TnPubkey> = Vec::with_capacity(rw_cnt);
1053 for k in 0..rw_cnt {
1054 let s = accs_start + k * 32;
1055 let mut a = [0u8; 32];
1056 a.copy_from_slice(&bytes[s..s + 32]);
1057 rw_accs.push(a);
1058 }
1059 let ro_start = accs_start + rw_cnt * 32;
1060 let mut ro_accs: Vec<TnPubkey> = Vec::with_capacity(ro_cnt);
1061 for k in 0..ro_cnt {
1062 let s = ro_start + k * 32;
1063 let mut a = [0u8; 32];
1064 a.copy_from_slice(&bytes[s..s + 32]);
1065 ro_accs.push(a);
1066 }
1067 validate_account_layout(
1068 &rw_accs,
1069 &ro_accs,
1070 &hdr.fee_payer_pubkey,
1071 &hdr.program_pubkey,
1072 )?;
1073 }
1074
1075 let wire_for_signing = &bytes[..sig_start];
1077 let signature = &bytes[sig_start..];
1078 if verify_transaction(
1079 wire_for_signing,
1080 signature.try_into().expect("signature should be 64 bytes"),
1081 &hdr.fee_payer_pubkey,
1082 )
1083 .is_err()
1084 {
1085 return Err(RpcError::invalid_transaction_signature());
1086 }
1087
1088 Ok(())
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094 use ed25519_dalek::SigningKey;
1095
1096 fn make_valid_txn_bytes_with_flags(flags: u8) -> Vec<u8> {
1097 let signing_key = SigningKey::from(&[1u8; 32]);
1098 let verifying_key = signing_key.verifying_key();
1099 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
1100 tx.rw_accs = Some(vec![[3u8; 32], [4u8; 32]]);
1101 tx.r_accs = Some(vec![[5u8; 32]]);
1102 tx.instructions = Some(vec![1, 2, 3, 4]);
1103 tx.flags = flags;
1104 tx.sign(&signing_key.to_bytes()).unwrap();
1105 tx.to_wire()
1106 }
1107
1108 fn make_valid_txn_bytes() -> Vec<u8> {
1109 make_valid_txn_bytes_with_flags(0)
1110 }
1111
1112 #[test]
1113 fn test_tn_txn_size_basic_transaction() {
1114 let bytes = make_valid_txn_bytes();
1115 let calculated_size = tn_txn_size(&bytes).unwrap();
1116
1117 assert_eq!(calculated_size, bytes.len());
1119 }
1120
1121 #[test]
1122 fn test_tn_txn_size_with_state_proof() {
1123 use crate::tn_state_proof::StateProof;
1124
1125 let signing_key = SigningKey::from(&[1u8; 32]);
1126 let verifying_key = signing_key.verifying_key();
1127
1128 let path_bitset = [0u8; 32]; let existing_leaf_pubkey = [7u8; 32];
1131 let existing_leaf_hash = [8u8; 32];
1132 let state_proof = StateProof::creation(
1133 100,
1134 path_bitset,
1135 existing_leaf_pubkey,
1136 existing_leaf_hash,
1137 vec![],
1138 );
1139
1140 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1142 .with_rw_accounts(vec![[3u8; 32]])
1143 .with_instructions(vec![1, 2, 3])
1144 .with_fee_payer_state_proof(&state_proof);
1145
1146 tx.sign(&signing_key.to_bytes()).unwrap();
1147 let bytes = tx.to_wire();
1148
1149 let calculated_size = tn_txn_size(&bytes).unwrap();
1150
1151 assert_eq!(calculated_size, bytes.len());
1153 }
1154
1155 #[test]
1156 fn test_tn_txn_size_minimal_transaction() {
1157 let signing_key = SigningKey::from(&[1u8; 32]);
1158 let verifying_key = signing_key.verifying_key();
1159
1160 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
1162 tx.sign(&signing_key.to_bytes()).unwrap();
1163 let bytes = tx.to_wire();
1164
1165 let calculated_size = tn_txn_size(&bytes).unwrap();
1166
1167 assert_eq!(calculated_size, bytes.len());
1169
1170 let expected_min_size = core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ;
1172 assert_eq!(calculated_size, expected_min_size);
1173 }
1174
1175 #[test]
1176 fn test_tn_txn_size_invalid_format() {
1177 let short_bytes = vec![0u8; 50];
1179 let result = tn_txn_size(&short_bytes);
1180 assert!(matches!(result, Err(RpcError::InvalidFormat)));
1181
1182 let mut bytes = make_valid_txn_bytes();
1184 bytes.truncate(core::mem::size_of::<WireTxnHdrV1>() + 10); let result = tn_txn_size(&bytes);
1186 assert!(matches!(result, Err(RpcError::InvalidFormat)));
1187 }
1188
1189 #[test]
1190 fn test_tn_txn_size_consistency_with_validation() {
1191 let bytes = make_valid_txn_bytes();
1192
1193 assert!(validate_wire_transaction(&bytes).is_ok());
1195 assert!(tn_txn_size(&bytes).is_ok());
1196
1197 let calculated_size = tn_txn_size(&bytes).unwrap();
1199 assert_eq!(calculated_size, bytes.len());
1200 }
1201
1202 #[test]
1203 fn test_valid_transaction() {
1204 let bytes = make_valid_txn_bytes();
1205 assert!(validate_wire_transaction(&bytes).is_ok());
1206 }
1207
1208 #[test]
1209 fn test_transaction_duplicate_accounts_rejected() {
1210 let signing_key = SigningKey::from(&[1u8; 32]);
1211 let fee = signing_key.verifying_key().to_bytes();
1212 let program = [2u8; 32];
1213 let a = [3u8; 32];
1214 let b = [4u8; 32];
1215
1216 let cases = [
1217 Transaction::new(fee, fee, 100, 42),
1218 Transaction::new(fee, program, 100, 42).with_rw_accounts(vec![fee]),
1219 Transaction::new(fee, program, 100, 42).with_r_accounts(vec![fee]),
1220 Transaction::new(fee, program, 100, 42).with_rw_accounts(vec![program]),
1221 Transaction::new(fee, program, 100, 42).with_r_accounts(vec![program]),
1222 Transaction::new(fee, program, 100, 42).with_rw_accounts(vec![a, a]),
1223 Transaction::new(fee, program, 100, 42).with_r_accounts(vec![a, a]),
1224 Transaction::new(fee, program, 100, 42)
1225 .with_rw_accounts(vec![a])
1226 .with_r_accounts(vec![a, b]),
1227 ];
1228
1229 for mut tx in cases {
1230 assert!(matches!(tx.validate(), Err(RpcError::DuplicateAccount)));
1231 assert!(matches!(tx.try_to_wire(), Err(RpcError::DuplicateAccount)));
1232 assert!(tx.sign(&signing_key.to_bytes()).is_err());
1233 }
1234 }
1235
1236 #[test]
1237 fn test_oversize_transaction() {
1238 let mut bytes = make_valid_txn_bytes();
1239 bytes.resize(32_769, 0);
1240 let err = validate_wire_transaction(&bytes).unwrap_err();
1241 assert!(matches!(
1242 err,
1243 RpcError::InvalidTransactionSize {
1244 size: 32_769,
1245 max_size: 32_768
1246 }
1247 ));
1248 }
1249
1250 #[test]
1251 fn test_trailing_bytes() {
1252 let mut bytes = make_valid_txn_bytes();
1253 let orig_len = bytes.len();
1254 bytes.insert(orig_len - TN_TXN_SIGNATURE_SZ, 0);
1256 let err = validate_wire_transaction(&bytes).unwrap_err();
1257 assert!(matches!(err, RpcError::TrailingBytes { .. }));
1259 }
1260
1261 #[test]
1262 fn test_invalid_transaction_version() {
1263 let mut bytes = make_valid_txn_bytes();
1264 bytes[0] = 0x00; let err = validate_wire_transaction(&bytes).unwrap_err();
1267 assert!(matches!(err, RpcError::InvalidVersion));
1268 }
1269
1270 #[test]
1271 fn test_invalid_flags() {
1272 let bytes = make_valid_txn_bytes_with_flags(0x07);
1274 let err = validate_wire_transaction(&bytes).unwrap_err();
1275 assert!(matches!(err, RpcError::InvalidFlags));
1276 }
1277
1278 #[test]
1279 fn test_invalid_chain_id_zero() {
1280 let mut bytes = make_valid_txn_bytes();
1281
1282 let hdr: &mut WireTxnHdrV1 =
1284 bytemuck::from_bytes_mut(&mut bytes[0..core::mem::size_of::<WireTxnHdrV1>()]);
1285 hdr.chain_id = 0;
1286
1287 let err = validate_wire_transaction(&bytes).unwrap_err();
1288 assert!(matches!(err, RpcError::InvalidChainId));
1289 }
1290
1291 #[test]
1292 fn test_transaction_too_short() {
1293 let bytes = vec![0u8; 50]; let err = validate_wire_transaction(&bytes).unwrap_err();
1295 assert!(matches!(err, RpcError::InvalidFormat));
1296 }
1297
1298 #[test]
1299 fn test_transaction_with_state_proof() {
1300 use crate::tn_state_proof::{StateProof, StateProofType};
1301
1302 let signing_key = SigningKey::from(&[1u8; 32]);
1303 let verifying_key = signing_key.verifying_key();
1304
1305 let path_bitset = [0u8; 32]; let existing_leaf_pubkey = [7u8; 32];
1308 let existing_leaf_hash = [8u8; 32];
1309 let state_proof = StateProof::creation(
1310 100,
1311 path_bitset,
1312 existing_leaf_pubkey,
1313 existing_leaf_hash,
1314 vec![],
1315 );
1316
1317 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1319 .with_fee_payer_state_proof(&state_proof);
1320
1321 assert!(tx.has_fee_payer_state_proof());
1323 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 1);
1324
1325 tx.sign(&signing_key.to_bytes()).unwrap();
1326 let bytes = tx.to_wire();
1327
1328 assert!(bytes.len() > core::mem::size_of::<WireTxnHdrV1>() + TN_TXN_SIGNATURE_SZ);
1330 assert!(validate_wire_transaction(&bytes).is_ok());
1331
1332 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1334 assert!(decoded_tx.has_fee_payer_state_proof());
1335 assert!(decoded_tx.fee_payer_state_proof.is_some());
1336
1337 let decoded_proof = decoded_tx.fee_payer_state_proof.unwrap();
1338 assert_eq!(decoded_proof.proof_type(), StateProofType::Creation);
1339 assert_eq!(decoded_proof.slot(), 100);
1340 }
1341
1342 #[test]
1343 fn test_transaction_with_state_proof_serialization_round_trip() {
1344 use crate::tn_state_proof::StateProof;
1345
1346 let signing_key = SigningKey::from(&[1u8; 32]);
1347 let verifying_key = signing_key.verifying_key();
1348
1349 let mut path_bitset = [0u8; 32];
1351 path_bitset[0] = 0b11; let existing_leaf_pubkey = [7u8; 32];
1353 let existing_leaf_hash = [8u8; 32];
1354 let sibling_hashes = vec![[9u8; 32], [10u8; 32]];
1355
1356 let state_proof = StateProof::creation(
1357 200,
1358 path_bitset,
1359 existing_leaf_pubkey,
1360 existing_leaf_hash,
1361 sibling_hashes.clone(),
1362 );
1363
1364 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1366 .with_rw_accounts(vec![[3u8; 32], [4u8; 32]])
1367 .with_r_accounts(vec![[5u8; 32]])
1368 .with_instructions(vec![1, 2, 3, 4])
1369 .with_fee_payer_state_proof(&state_proof);
1370
1371 tx.sign(&signing_key.to_bytes()).unwrap();
1372 let bytes = tx.to_wire();
1373
1374 assert!(validate_wire_transaction(&bytes).is_ok());
1376
1377 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1379 assert_eq!(decoded_tx.fee_payer, tx.fee_payer);
1380 assert_eq!(decoded_tx.program, tx.program);
1381 assert_eq!(decoded_tx.rw_accs, tx.rw_accs);
1382 assert_eq!(decoded_tx.r_accs, tx.r_accs);
1383 assert_eq!(decoded_tx.instructions, tx.instructions);
1384 assert_eq!(decoded_tx.flags, tx.flags);
1385 assert!(decoded_tx.has_fee_payer_state_proof());
1386
1387 let decoded_proof = decoded_tx.fee_payer_state_proof.unwrap();
1388 assert_eq!(decoded_proof.slot(), 200);
1389 assert_eq!(decoded_proof.path_bitset(), &path_bitset);
1390 }
1391
1392 #[test]
1393 fn test_transaction_without_state_proof() {
1394 let signing_key = SigningKey::from(&[1u8; 32]);
1395 let verifying_key = signing_key.verifying_key();
1396
1397 let mut tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42);
1398
1399 assert!(!tx.has_fee_payer_state_proof());
1401 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 0);
1402 assert!(tx.fee_payer_state_proof.is_none());
1403
1404 tx.sign(&signing_key.to_bytes()).unwrap();
1405 let bytes = tx.to_wire();
1406
1407 assert!(validate_wire_transaction(&bytes).is_ok());
1408
1409 let decoded_tx = Transaction::from_wire(&bytes).unwrap();
1411 assert!(!decoded_tx.has_fee_payer_state_proof());
1412 assert!(decoded_tx.fee_payer_state_proof.is_none());
1413 }
1414
1415 #[test]
1416 fn test_transaction_remove_state_proof() {
1417 use crate::tn_state_proof::StateProof;
1418
1419 let signing_key = SigningKey::from(&[1u8; 32]);
1420 let verifying_key = signing_key.verifying_key();
1421
1422 let path_bitset = [0u8; 32];
1424 let existing_leaf_pubkey = [7u8; 32];
1425 let existing_leaf_hash = [8u8; 32];
1426 let state_proof = StateProof::creation(
1427 100,
1428 path_bitset,
1429 existing_leaf_pubkey,
1430 existing_leaf_hash,
1431 vec![],
1432 );
1433
1434 let tx = Transaction::new(verifying_key.to_bytes(), [2u8; 32], 100, 42)
1436 .with_fee_payer_state_proof(&state_proof)
1437 .without_fee_payer_state_proof();
1438
1439 assert!(!tx.has_fee_payer_state_proof());
1441 assert_eq!(tx.flags & (1 << TN_TXN_FLAG_HAS_FEE_PAYER_PROOF_BIT), 0);
1442 assert!(tx.fee_payer_state_proof.is_none());
1443 }
1444}