1use crate::utils::codec::{Decode, Encode};
7use crate::{
8 Hash, PrimitiveError, PrimitiveResult, Validate,
9 types::{AccountId, Balance, Gas, ServiceId, Signature, Timestamp, Weight},
10};
11use serde::{Deserialize, Serialize};
12
13pub type Nonce = u64;
15
16pub type TransactionType = u8;
18
19pub type GasPrice = u64;
21
22#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
24pub enum TransactionKind {
25 Transfer {
27 to: AccountId,
29 amount: Balance,
31 },
32 ServiceCall {
34 service: ServiceId,
36 method: u32,
38 data: Vec<u8>,
40 },
41 ServiceDeploy {
43 code: Vec<u8>,
45 initial_state: Vec<u8>,
47 },
48 ServiceUpdate {
50 service: ServiceId,
52 code: Vec<u8>,
54 },
55 ValidatorRegister {
57 keys: ValidatorKeys,
59 stake: Balance,
61 },
62 ValidatorUnregister,
64 Delegate {
66 validator: AccountId,
68 amount: Balance,
70 },
71 Undelegate {
73 validator: AccountId,
75 amount: Balance,
77 },
78 CoreAssign {
80 core: u16,
82 service: ServiceId,
84 duration: u64,
86 },
87 AvailabilityReport {
89 core: u16,
91 data_hash: Hash,
93 chunks: Vec<Vec<u8>>,
95 },
96 DisputeInitiate {
98 core: u16,
100 block_number: u32,
102 evidence: Vec<u8>,
104 },
105 DisputeVote {
107 dispute_id: Hash,
109 vote: bool,
111 justification: Vec<u8>,
113 },
114}
115
116impl TransactionKind {
117 pub fn transaction_type(&self) -> TransactionType {
119 match self {
120 TransactionKind::Transfer { .. } => 0,
121 TransactionKind::ServiceCall { .. } => 1,
122 TransactionKind::ServiceDeploy { .. } => 2,
123 TransactionKind::ServiceUpdate { .. } => 3,
124 TransactionKind::ValidatorRegister { .. } => 4,
125 TransactionKind::ValidatorUnregister => 5,
126 TransactionKind::Delegate { .. } => 6,
127 TransactionKind::Undelegate { .. } => 7,
128 TransactionKind::CoreAssign { .. } => 8,
129 TransactionKind::AvailabilityReport { .. } => 9,
130 TransactionKind::DisputeInitiate { .. } => 10,
131 TransactionKind::DisputeVote { .. } => 11,
132 }
133 }
134
135 pub fn type_name(&self) -> &'static str {
137 match self {
138 TransactionKind::Transfer { .. } => "Transfer",
139 TransactionKind::ServiceCall { .. } => "ServiceCall",
140 TransactionKind::ServiceDeploy { .. } => "ServiceDeploy",
141 TransactionKind::ServiceUpdate { .. } => "ServiceUpdate",
142 TransactionKind::ValidatorRegister { .. } => "ValidatorRegister",
143 TransactionKind::ValidatorUnregister => "ValidatorUnregister",
144 TransactionKind::Delegate { .. } => "Delegate",
145 TransactionKind::Undelegate { .. } => "Undelegate",
146 TransactionKind::CoreAssign { .. } => "CoreAssign",
147 TransactionKind::AvailabilityReport { .. } => "AvailabilityReport",
148 TransactionKind::DisputeInitiate { .. } => "DisputeInitiate",
149 TransactionKind::DisputeVote { .. } => "DisputeVote",
150 }
151 }
152
153 pub fn requires_stake(&self) -> bool {
155 matches!(
156 self,
157 TransactionKind::ValidatorRegister { .. } | TransactionKind::Delegate { .. }
158 )
159 }
160
161 pub fn target_service(&self) -> Option<ServiceId> {
163 match self {
164 TransactionKind::ServiceCall { service, .. }
165 | TransactionKind::ServiceUpdate { service, .. }
166 | TransactionKind::CoreAssign { service, .. } => Some(*service),
167 _ => None,
168 }
169 }
170
171 pub fn target_core(&self) -> Option<u16> {
173 match self {
174 TransactionKind::CoreAssign { core, .. }
175 | TransactionKind::AvailabilityReport { core, .. }
176 | TransactionKind::DisputeInitiate { core, .. } => Some(*core),
177 _ => None,
178 }
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
184pub struct ValidatorKeys {
185 pub grandpa: crate::crypto::Ed25519PublicKey,
187 pub babe: crate::crypto::bandersnatch::PublicKey,
189 pub session: crate::crypto::Ed25519PublicKey,
191 pub authority_discovery: crate::crypto::Ed25519PublicKey,
193}
194
195impl ValidatorKeys {
196 pub fn new(
198 grandpa: crate::crypto::Ed25519PublicKey,
199 babe: crate::crypto::bandersnatch::PublicKey,
200 session: crate::crypto::Ed25519PublicKey,
201 authority_discovery: crate::crypto::Ed25519PublicKey,
202 ) -> Self {
203 Self {
204 grandpa,
205 babe,
206 session,
207 authority_discovery,
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
214pub struct Transaction {
215 pub sender: AccountId,
217 pub nonce: Nonce,
219 pub gas_limit: Gas,
221 pub gas_price: GasPrice,
223 pub kind: TransactionKind,
225 pub signature: Signature,
227}
228
229impl Transaction {
230 pub fn new(
232 sender: AccountId,
233 nonce: Nonce,
234 gas_limit: Gas,
235 gas_price: GasPrice,
236 kind: TransactionKind,
237 signature: Signature,
238 ) -> Self {
239 Self {
240 sender,
241 nonce,
242 gas_limit,
243 gas_price,
244 kind,
245 signature,
246 }
247 }
248
249 pub fn hash(&self) -> Hash {
251 use crate::crypto::hashing::{Blake3Hasher, Hasher};
252 let hasher = Blake3Hasher::new();
253 hasher.hash(&self.encode())
254 }
255
256 pub fn fee(&self) -> Balance {
258 self.gas_limit.saturating_mul(self.gas_price)
259 }
260
261 pub fn verify_signature(&self) -> PrimitiveResult<bool> {
263 let _message = self.signature_message();
265
266 Ok(true) }
270
271 pub fn signature_message(&self) -> Vec<u8> {
273 let mut message = Vec::new();
274 message.extend_from_slice(self.sender.as_ref());
275 message.extend_from_slice(&self.nonce.to_le_bytes());
276 message.extend_from_slice(&self.gas_limit.0.to_le_bytes());
277 message.extend_from_slice(&self.gas_price.to_le_bytes());
278 message.extend_from_slice(&self.kind.encode());
279 message
280 }
281
282 pub fn is_system_transaction(&self) -> bool {
284 matches!(
285 self.kind,
286 TransactionKind::ValidatorUnregister
287 | TransactionKind::AvailabilityReport { .. }
288 | TransactionKind::DisputeVote { .. }
289 )
290 }
291
292 pub fn weight(&self) -> Weight {
294 let base_weight = 10_000; let kind_weight = match &self.kind {
296 TransactionKind::Transfer { .. } => 25_000,
297 TransactionKind::ServiceCall { data, .. } => 50_000 + (data.len() as u64 * 10),
298 TransactionKind::ServiceDeploy {
299 code,
300 initial_state,
301 } => 100_000 + (code.len() as u64 * 50) + (initial_state.len() as u64 * 10),
302 TransactionKind::ServiceUpdate { code, .. } => 80_000 + (code.len() as u64 * 50),
303 TransactionKind::ValidatorRegister { .. } => 200_000,
304 TransactionKind::ValidatorUnregister => 50_000,
305 TransactionKind::Delegate { .. } => 30_000,
306 TransactionKind::Undelegate { .. } => 30_000,
307 TransactionKind::CoreAssign { .. } => 40_000,
308 TransactionKind::AvailabilityReport { chunks, .. } => {
309 60_000 + (chunks.iter().map(|c| c.len()).sum::<usize>() as u64 * 5)
310 }
311 TransactionKind::DisputeInitiate { evidence, .. } => {
312 150_000 + (evidence.len() as u64 * 20)
313 }
314 TransactionKind::DisputeVote { justification, .. } => {
315 30_000 + (justification.len() as u64 * 10)
316 }
317 };
318 Weight(base_weight + kind_weight)
319 }
320}
321
322impl Validate for Transaction {
323 fn validate(&self) -> PrimitiveResult<()> {
324 if self.gas_limit == Gas(0) {
326 return Err(PrimitiveError::InvalidTransaction(
327 "Gas limit cannot be zero".to_string(),
328 ));
329 }
330
331 if self.gas_limit > Gas(10_000_000) {
332 return Err(PrimitiveError::InvalidTransaction(
333 "Gas limit too high".to_string(),
334 ));
335 }
336
337 if self.gas_price == 0 && !self.is_system_transaction() {
339 return Err(PrimitiveError::InvalidTransaction(
340 "Gas price cannot be zero for non-system transactions".to_string(),
341 ));
342 }
343
344 self.validate_kind()?;
346
347 if !self.verify_signature()? {
349 return Err(PrimitiveError::InvalidTransaction(
350 "Invalid signature".to_string(),
351 ));
352 }
353
354 Ok(())
355 }
356}
357
358impl Transaction {
359 fn validate_kind(&self) -> PrimitiveResult<()> {
361 match &self.kind {
362 TransactionKind::Transfer { amount, .. } => {
363 if *amount == Balance(0) {
364 return Err(PrimitiveError::InvalidTransaction(
365 "Transfer amount cannot be zero".to_string(),
366 ));
367 }
368 }
369 TransactionKind::ServiceCall { data, .. } => {
370 if data.len() > 1024 * 1024 {
371 return Err(PrimitiveError::InvalidTransaction(
372 "Service call data too large".to_string(),
373 ));
374 }
375 }
376 TransactionKind::ServiceDeploy {
377 code,
378 initial_state,
379 } => {
380 if code.is_empty() {
381 return Err(PrimitiveError::InvalidTransaction(
382 "Service code cannot be empty".to_string(),
383 ));
384 }
385 if code.len() > 5 * 1024 * 1024 {
386 return Err(PrimitiveError::InvalidTransaction(
387 "Service code too large".to_string(),
388 ));
389 }
390 if initial_state.len() > 1024 * 1024 {
391 return Err(PrimitiveError::InvalidTransaction(
392 "Initial state too large".to_string(),
393 ));
394 }
395 }
396 TransactionKind::ServiceUpdate { code, .. } => {
397 if code.is_empty() {
398 return Err(PrimitiveError::InvalidTransaction(
399 "Service code cannot be empty".to_string(),
400 ));
401 }
402 if code.len() > 5 * 1024 * 1024 {
403 return Err(PrimitiveError::InvalidTransaction(
404 "Service code too large".to_string(),
405 ));
406 }
407 }
408 TransactionKind::ValidatorRegister { stake, .. } => {
409 if *stake == Balance(0) {
410 return Err(PrimitiveError::InvalidTransaction(
411 "Validator stake cannot be zero".to_string(),
412 ));
413 }
414 }
415 TransactionKind::Delegate { amount, .. }
416 | TransactionKind::Undelegate { amount, .. } => {
417 if *amount == Balance(0) {
418 return Err(PrimitiveError::InvalidTransaction(
419 "Delegation amount cannot be zero".to_string(),
420 ));
421 }
422 }
423 TransactionKind::CoreAssign { duration, .. } => {
424 if *duration == 0 {
425 return Err(PrimitiveError::InvalidTransaction(
426 "Core assignment duration cannot be zero".to_string(),
427 ));
428 }
429 }
430 TransactionKind::AvailabilityReport { chunks, .. } => {
431 if chunks.is_empty() {
432 return Err(PrimitiveError::InvalidTransaction(
433 "Availability report must include chunks".to_string(),
434 ));
435 }
436 }
437 TransactionKind::DisputeInitiate { evidence, .. } => {
438 if evidence.is_empty() {
439 return Err(PrimitiveError::InvalidTransaction(
440 "Dispute must include evidence".to_string(),
441 ));
442 }
443 }
444 _ => {} }
446 Ok(())
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
452pub struct TransactionPoolEntry {
453 pub transaction: Transaction,
455 pub received_at: Timestamp,
457 pub priority: u64,
459 pub propagation_count: u32,
461}
462
463impl TransactionPoolEntry {
464 pub fn new(transaction: Transaction, received_at: Timestamp) -> Self {
466 let priority = transaction.gas_price; Self {
468 transaction,
469 received_at,
470 priority,
471 propagation_count: 0,
472 }
473 }
474
475 pub fn update_priority(&mut self, current_time: Timestamp) {
477 let age_bonus = current_time.saturating_sub(self.received_at) / 1000; self.priority = self.transaction.gas_price + age_bonus;
480 }
481
482 pub fn is_expired(&self, current_time: Timestamp, max_lifetime: u64) -> bool {
484 current_time.saturating_sub(self.received_at) > max_lifetime
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use crate::types::*;
492
493 fn dummy_signature() -> Signature {
494 Signature::Ed25519([0u8; 64])
495 }
496
497 fn dummy_account() -> AccountId {
498 AccountId::from([0u8; 32])
499 }
500
501 #[test]
502 fn test_transaction_creation() {
503 let tx = Transaction::new(
504 AccountId::from([1u8; 32]),
505 1,
506 Gas(21000),
507 20,
508 TransactionKind::Transfer {
509 to: AccountId::from([2u8; 32]),
510 amount: Balance(1000),
511 },
512 dummy_signature(),
513 );
514
515 assert_eq!(tx.nonce, 1);
516 assert_eq!(tx.gas_limit, Gas(21000));
517 assert_eq!(tx.fee(), Balance(420000));
518 }
519
520 #[test]
521 fn test_transaction_kind_types() {
522 let transfer = TransactionKind::Transfer {
523 to: dummy_account(),
524 amount: Balance(1000),
525 };
526 assert_eq!(transfer.transaction_type(), 0);
527 assert_eq!(transfer.type_name(), "Transfer");
528 assert!(!transfer.requires_stake());
529
530 let validator_register = TransactionKind::ValidatorRegister {
531 keys: ValidatorKeys::new(
532 crate::crypto::Ed25519PublicKey::from([0u8; 32]),
533 crate::crypto::bandersnatch::PublicKey::from([0u8; 32]),
534 crate::crypto::Ed25519PublicKey::from([0u8; 32]),
535 crate::crypto::Ed25519PublicKey::from([0u8; 32]),
536 ),
537 stake: Balance(1000000),
538 };
539 assert_eq!(validator_register.transaction_type(), 4);
540 assert!(validator_register.requires_stake());
541 }
542
543 #[test]
544 fn test_transaction_validation() {
545 let valid_tx = Transaction::new(
546 AccountId::from([1u8; 32]),
547 1,
548 Gas(21000),
549 20,
550 TransactionKind::Transfer {
551 to: AccountId::from([2u8; 32]),
552 amount: Balance(1000),
553 },
554 dummy_signature(),
555 );
556 assert!(valid_tx.validate().is_ok());
557
558 let invalid_tx = Transaction::new(
559 AccountId::from([1u8; 32]),
560 1,
561 Gas(0), 20,
563 TransactionKind::Transfer {
564 to: AccountId::from([2u8; 32]),
565 amount: Balance(1000),
566 },
567 dummy_signature(),
568 );
569 assert!(invalid_tx.validate().is_err());
570 }
571
572 #[test]
573 fn test_transaction_pool_entry() {
574 let tx = Transaction::new(
575 AccountId::from([1u8; 32]),
576 1,
577 Gas(21000),
578 20,
579 TransactionKind::Transfer {
580 to: AccountId::from([2u8; 32]),
581 amount: Balance(1000),
582 },
583 dummy_signature(),
584 );
585
586 let mut entry = TransactionPoolEntry::new(tx, Timestamp(1000000));
587 assert_eq!(entry.priority, 20);
588
589 entry.update_priority(Timestamp(1001000)); assert_eq!(entry.priority, 21); assert!(!entry.is_expired(Timestamp(1005000), 10000)); assert!(entry.is_expired(Timestamp(1015000), 10000)); }
595
596 #[test]
597 fn test_transaction_weight_calculation() {
598 let transfer = Transaction::new(
599 AccountId::from([1u8; 32]),
600 1,
601 Gas(21000),
602 20,
603 TransactionKind::Transfer {
604 to: AccountId::from([2u8; 32]),
605 amount: Balance(1000),
606 },
607 dummy_signature(),
608 );
609
610 assert_eq!(transfer.weight(), Weight(35_000)); let service_call = Transaction::new(
613 AccountId::from([1u8; 32]),
614 1,
615 Gas(100000),
616 20,
617 TransactionKind::ServiceCall {
618 service: ServiceId::new(1),
619 method: 0,
620 data: vec![0u8; 100],
621 },
622 dummy_signature(),
623 );
624
625 assert_eq!(service_call.weight(), Weight(61_000)); }
627}