@@ -37,10 +37,12 @@ pub struct SenderAllocationTask<T: NetworkVersion> {
37
37
_phantom : PhantomData < T > ,
38
38
}
39
39
40
- /// Simple state structure for the task (will be enhanced incrementally)
40
+ /// Enhanced state structure for the task with invalid receipts tracking
41
41
struct TaskState {
42
42
/// Sum of all receipt fees for the current allocation
43
43
unaggregated_fees : UnaggregatedReceipts ,
44
+ /// Sum of all invalid receipt fees
45
+ invalid_receipts_fees : UnaggregatedReceipts ,
44
46
/// Handle to communicate with parent SenderAccount
45
47
sender_account_handle : TaskHandle < SenderAccountMessage > ,
46
48
/// Current allocation ID
@@ -57,6 +59,7 @@ impl<T: NetworkVersion> SenderAllocationTask<T> {
57
59
) -> anyhow:: Result < TaskHandle < SenderAllocationMessage > > {
58
60
let state = TaskState {
59
61
unaggregated_fees : UnaggregatedReceipts :: default ( ) ,
62
+ invalid_receipts_fees : UnaggregatedReceipts :: default ( ) ,
60
63
sender_account_handle,
61
64
allocation_id,
62
65
} ;
@@ -115,14 +118,14 @@ impl<T: NetworkVersion> SenderAllocationTask<T> {
115
118
Ok ( ( ) )
116
119
}
117
120
118
- /// Handle new receipt - with basic validation
121
+ /// Handle new receipt - with basic validation and invalid receipt tracking
119
122
async fn handle_new_receipt (
120
123
state : & mut TaskState ,
121
124
notification : NewReceiptNotification ,
122
125
) -> anyhow:: Result < ( ) > {
123
- let ( id, value, timestamp_ns) = match notification {
124
- NewReceiptNotification :: V1 ( ref n) => ( n. id , n. value , n. timestamp_ns ) ,
125
- NewReceiptNotification :: V2 ( ref n) => ( n. id , n. value , n. timestamp_ns ) ,
126
+ let ( id, value, timestamp_ns, signer_address ) = match notification {
127
+ NewReceiptNotification :: V1 ( ref n) => ( n. id , n. value , n. timestamp_ns , n . signer_address ) ,
128
+ NewReceiptNotification :: V2 ( ref n) => ( n. id , n. value , n. timestamp_ns , n . signer_address ) ,
126
129
} ;
127
130
128
131
// Basic receipt ID validation - reject already processed receipts
@@ -136,31 +139,86 @@ impl<T: NetworkVersion> SenderAllocationTask<T> {
136
139
return Ok ( ( ) ) ; // Silently ignore duplicate/old receipts
137
140
}
138
141
139
- // Update local state with new receipt
140
- state. unaggregated_fees . value += value;
141
- state. unaggregated_fees . counter += 1 ;
142
- state. unaggregated_fees . last_id = id;
142
+ // Simulate basic receipt validation (in production this would be TAP manager)
143
+ let is_valid = Self :: validate_receipt_basic ( id, value, signer_address) ;
143
144
144
- tracing:: debug!(
145
- allocation_id = ?state. allocation_id,
146
- receipt_id = id,
147
- value = value,
148
- new_total = state. unaggregated_fees. value,
149
- "Processed new receipt"
150
- ) ;
145
+ if is_valid {
146
+ // Valid receipt - update state and notify parent
147
+ state. unaggregated_fees . value += value;
148
+ state. unaggregated_fees . counter += 1 ;
149
+ state. unaggregated_fees . last_id = id;
151
150
152
- // Notify parent
153
- state
154
- . sender_account_handle
155
- . cast ( SenderAccountMessage :: UpdateReceiptFees (
156
- state. allocation_id ,
157
- ReceiptFees :: NewReceipt ( value, timestamp_ns) ,
158
- ) )
159
- . await ?;
151
+ tracing:: debug!(
152
+ allocation_id = ?state. allocation_id,
153
+ receipt_id = id,
154
+ value = value,
155
+ new_total = state. unaggregated_fees. value,
156
+ "Processed valid receipt"
157
+ ) ;
158
+
159
+ // Notify parent of valid receipt
160
+ state
161
+ . sender_account_handle
162
+ . cast ( SenderAccountMessage :: UpdateReceiptFees (
163
+ state. allocation_id ,
164
+ ReceiptFees :: NewReceipt ( value, timestamp_ns) ,
165
+ ) )
166
+ . await ?;
167
+ } else {
168
+ // Invalid receipt - track separately
169
+ state. invalid_receipts_fees . value += value;
170
+ state. invalid_receipts_fees . counter += 1 ;
171
+ state. invalid_receipts_fees . last_id = id;
172
+
173
+ tracing:: warn!(
174
+ allocation_id = ?state. allocation_id,
175
+ receipt_id = id,
176
+ value = value,
177
+ signer = %signer_address,
178
+ total_invalid_value = state. invalid_receipts_fees. value,
179
+ "Receipt failed validation - tracked as invalid"
180
+ ) ;
181
+
182
+ // Notify parent of invalid receipt fees
183
+ state
184
+ . sender_account_handle
185
+ . cast ( SenderAccountMessage :: UpdateInvalidReceiptFees (
186
+ state. allocation_id ,
187
+ state. invalid_receipts_fees ,
188
+ ) )
189
+ . await ?;
190
+ }
160
191
161
192
Ok ( ( ) )
162
193
}
163
194
195
+ /// Basic receipt validation (placeholder for TAP manager integration)
196
+ fn validate_receipt_basic (
197
+ id : u64 ,
198
+ value : u128 ,
199
+ signer_address : thegraph_core:: alloy:: primitives:: Address ,
200
+ ) -> bool {
201
+ // Simple validation rules for demonstration:
202
+ // 1. Reject receipts with zero value
203
+ // 2. Reject receipts from zero address (obviously invalid signer)
204
+ // 3. Reject receipts with suspicious patterns (e.g., ID ending in 666)
205
+
206
+ if value == 0 {
207
+ return false ;
208
+ }
209
+
210
+ if signer_address == thegraph_core:: alloy:: primitives:: Address :: ZERO {
211
+ return false ;
212
+ }
213
+
214
+ // Simulate some receipts being invalid due to signature issues
215
+ if id % 1000 == 666 {
216
+ return false ; // Simulate signature validation failure
217
+ }
218
+
219
+ true // Most receipts are valid
220
+ }
221
+
164
222
/// Handle RAV request - enhanced but still simplified version
165
223
async fn handle_rav_request ( state : & mut TaskState ) -> anyhow:: Result < ( ) > {
166
224
let start_time = Instant :: now ( ) ;
@@ -310,12 +368,15 @@ mod tests {
310
368
super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
311
369
id : 100 ,
312
370
allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
313
- signer_address : thegraph_core:: alloy:: primitives:: Address :: ZERO ,
371
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) , // Valid signer
314
372
timestamp_ns : 1000 ,
315
373
value : 100 ,
316
374
} ,
317
375
) ;
318
376
377
+ // Consume the initial message from task initialization
378
+ let _initial_message = parent_rx. recv ( ) . await . unwrap ( ) ;
379
+
319
380
task_handle
320
381
. cast ( SenderAllocationMessage :: NewReceipt ( notification1) )
321
382
. await
@@ -329,7 +390,7 @@ mod tests {
329
390
super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
330
391
id : 100 , // Same ID - should be rejected
331
392
allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
332
- signer_address : thegraph_core:: alloy:: primitives:: Address :: ZERO ,
393
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) , // Valid signer
333
394
timestamp_ns : 2000 ,
334
395
value : 200 ,
335
396
} ,
@@ -345,7 +406,7 @@ mod tests {
345
406
super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
346
407
id : 101 , // Higher ID - should be accepted
347
408
allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
348
- signer_address : thegraph_core:: alloy:: primitives:: Address :: ZERO ,
409
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) , // Valid signer
349
410
timestamp_ns : 3000 ,
350
411
value : 300 ,
351
412
} ,
@@ -357,6 +418,7 @@ mod tests {
357
418
. unwrap ( ) ;
358
419
359
420
// Should only receive one more message (for the third receipt)
421
+ // The second receipt should be silently ignored, so we should get the third receipt's message
360
422
let second_message =
361
423
tokio:: time:: timeout ( std:: time:: Duration :: from_millis ( 100 ) , parent_rx. recv ( ) )
362
424
. await
@@ -368,4 +430,101 @@ mod tests {
368
430
SenderAccountMessage :: UpdateReceiptFees ( ..)
369
431
) ) ;
370
432
}
433
+
434
+ #[ tokio:: test]
435
+ async fn test_invalid_receipt_tracking ( ) {
436
+ let lifecycle = LifecycleManager :: new ( ) ;
437
+
438
+ // Create a dummy parent handle for testing
439
+ let ( parent_tx, mut parent_rx) = mpsc:: channel ( 10 ) ;
440
+ let parent_handle = TaskHandle :: new_for_test (
441
+ parent_tx,
442
+ Some ( "test_parent" . to_string ( ) ) ,
443
+ std:: sync:: Arc :: new ( lifecycle. clone ( ) ) ,
444
+ ) ;
445
+
446
+ let allocation_id =
447
+ AllocationId :: Legacy ( thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) ) ;
448
+
449
+ let task_handle = SenderAllocationTask :: < Legacy > :: spawn_simple (
450
+ & lifecycle,
451
+ Some ( "test_allocation" . to_string ( ) ) ,
452
+ allocation_id,
453
+ parent_handle,
454
+ )
455
+ . await
456
+ . unwrap ( ) ;
457
+
458
+ // Consume the initial message from task initialization
459
+ let _initial_message = parent_rx. recv ( ) . await . unwrap ( ) ;
460
+
461
+ // Send valid receipt
462
+ let valid_notification = NewReceiptNotification :: V1 (
463
+ super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
464
+ id : 100 ,
465
+ allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
466
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) , // Valid signer
467
+ timestamp_ns : 1000 ,
468
+ value : 100 ,
469
+ } ,
470
+ ) ;
471
+
472
+ task_handle
473
+ . cast ( SenderAllocationMessage :: NewReceipt ( valid_notification) )
474
+ . await
475
+ . unwrap ( ) ;
476
+
477
+ // Should receive UpdateReceiptFees for valid receipt
478
+ let valid_message = parent_rx. recv ( ) . await . unwrap ( ) ;
479
+ assert ! ( matches!(
480
+ valid_message,
481
+ SenderAccountMessage :: UpdateReceiptFees ( ..)
482
+ ) ) ;
483
+
484
+ // Send invalid receipt (zero value)
485
+ let invalid_notification = NewReceiptNotification :: V1 (
486
+ super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
487
+ id : 101 ,
488
+ allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
489
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) ,
490
+ timestamp_ns : 2000 ,
491
+ value : 0 , // Invalid: zero value
492
+ } ,
493
+ ) ;
494
+
495
+ task_handle
496
+ . cast ( SenderAllocationMessage :: NewReceipt ( invalid_notification) )
497
+ . await
498
+ . unwrap ( ) ;
499
+
500
+ // Should receive UpdateInvalidReceiptFees for invalid receipt
501
+ let invalid_message = parent_rx. recv ( ) . await . unwrap ( ) ;
502
+ assert ! ( matches!(
503
+ invalid_message,
504
+ SenderAccountMessage :: UpdateInvalidReceiptFees ( ..)
505
+ ) ) ;
506
+
507
+ // Send receipt with suspicious ID pattern
508
+ let suspicious_notification = NewReceiptNotification :: V1 (
509
+ super :: super :: sender_accounts_manager:: NewReceiptNotificationV1 {
510
+ id : 1666 , // ID ending in 666 - should be marked invalid
511
+ allocation_id : thegraph_core:: AllocationId :: new ( [ 1u8 ; 20 ] . into ( ) ) . into_inner ( ) ,
512
+ signer_address : thegraph_core:: alloy:: primitives:: Address :: from ( [ 1u8 ; 20 ] ) ,
513
+ timestamp_ns : 3000 ,
514
+ value : 200 ,
515
+ } ,
516
+ ) ;
517
+
518
+ task_handle
519
+ . cast ( SenderAllocationMessage :: NewReceipt ( suspicious_notification) )
520
+ . await
521
+ . unwrap ( ) ;
522
+
523
+ // Should receive UpdateInvalidReceiptFees for suspicious receipt
524
+ let suspicious_message = parent_rx. recv ( ) . await . unwrap ( ) ;
525
+ assert ! ( matches!(
526
+ suspicious_message,
527
+ SenderAccountMessage :: UpdateInvalidReceiptFees ( ..)
528
+ ) ) ;
529
+ }
371
530
}
0 commit comments