@@ -979,6 +979,8 @@ mod fuzzy_onion_utils {
979
979
pub ( crate ) onion_error_code : Option < LocalHTLCFailureReason > ,
980
980
#[ cfg( any( test, feature = "_test_utils" ) ) ]
981
981
pub ( crate ) onion_error_data : Option < Vec < u8 > > ,
982
+ #[ cfg( test) ]
983
+ pub ( crate ) attribution_failed_channel : Option < u64 > ,
982
984
}
983
985
}
984
986
#[ cfg( fuzzing) ]
@@ -1053,6 +1055,8 @@ where
1053
1055
onion_error_code : None ,
1054
1056
#[ cfg( any( test, feature = "_test_utils" ) ) ]
1055
1057
onion_error_data : None ,
1058
+ #[ cfg( test) ]
1059
+ attribution_failed_channel : None ,
1056
1060
} ;
1057
1061
}
1058
1062
@@ -1120,6 +1124,9 @@ where
1120
1124
// the first 20 hops. Determine the number of hops to be used for attribution data.
1121
1125
let attributable_hop_count = usize:: min ( path. hops . len ( ) , MAX_HOPS ) ;
1122
1126
1127
+ // Keep track of the first hop for which the attribution data failed to check out.
1128
+ let mut attribution_failed_channel = None ;
1129
+
1123
1130
// Handle packed channel/node updates for passing back for the route handler
1124
1131
let mut iter = nontrampolines. chain ( trampolines. into_iter ( ) . flatten ( ) ) . enumerate ( ) . peekable ( ) ;
1125
1132
while let Some ( ( route_hop_idx, ( route_hop_option, shared_secret) ) ) = iter. next ( ) {
@@ -1191,44 +1198,53 @@ where
1191
1198
1192
1199
let um = gen_um_from_shared_secret ( shared_secret. as_ref ( ) ) ;
1193
1200
1194
- // Check attr error HMACs if present.
1195
- if let Some ( ref mut attribution_data) = encrypted_packet. attribution_data {
1196
- // Only consider hops in the regular path for attribution data. Failures in a blinded path are not attributable.
1197
- if route_hop_idx < attributable_hop_count {
1198
- // Calculate position relative to the last attributable hop. The last attributable hop is at position 0.
1199
- // The failure node does not need to come from the last attributable hop, but we need to look at the
1200
- // chain of HMACs that does include all data up to the last attributable hop. For a more nearby failure,
1201
- // the verified HMACs will include some zero padding data. Failures beyond the last attributable hop
1202
- // will not be attributable.
1203
- let position = attributable_hop_count - route_hop_idx - 1 ;
1204
- let hold_time = attribution_data. verify (
1205
- & encrypted_packet. data ,
1206
- shared_secret. as_ref ( ) ,
1207
- position,
1208
- ) ;
1209
- if let Some ( hold_time) = hold_time {
1210
- hop_hold_times. push ( hold_time) ;
1211
-
1212
- log_debug ! ( logger, "Htlc hold time at pos {}: {} ms" , route_hop_idx, hold_time) ;
1213
-
1214
- // Shift attribution data to prepare for processing the next hop.
1215
- attribution_data. shift_left ( ) ;
1216
- } else {
1217
- res = Some ( FailureLearnings {
1218
- network_update : None ,
1219
- short_channel_id : route_hop. short_channel_id ( ) ,
1220
- payment_failed_permanently : false ,
1221
- failed_within_blinded_path : false ,
1222
- } ) ;
1223
-
1224
- log_debug ! (
1225
- logger,
1226
- "Invalid HMAC in attribution data for node at pos {}" ,
1227
- route_hop_idx
1201
+ // Only check attribution when an attribution data failure has not yet occurred.
1202
+ if attribution_failed_channel. is_none ( ) {
1203
+ // Check attr error HMACs if present.
1204
+ if let Some ( ref mut attribution_data) = encrypted_packet. attribution_data {
1205
+ // Only consider hops in the regular path for attribution data. Failures in a blinded path are not
1206
+ // attributable.
1207
+ if route_hop_idx < attributable_hop_count {
1208
+ // Calculate position relative to the last attributable hop. The last attributable hop is at
1209
+ // position 0. The failure node does not need to come from the last attributable hop, but we need to
1210
+ // look at the chain of HMACs that does include all data up to the last attributable hop. For a more
1211
+ // nearby failure, the verified HMACs will include some zero padding data. Failures beyond the last
1212
+ // attributable hop will not be attributable.
1213
+ let position = attributable_hop_count - route_hop_idx - 1 ;
1214
+ let hold_time = attribution_data. verify (
1215
+ & encrypted_packet. data ,
1216
+ shared_secret. as_ref ( ) ,
1217
+ position,
1228
1218
) ;
1219
+ if let Some ( hold_time) = hold_time {
1220
+ hop_hold_times. push ( hold_time) ;
1221
+
1222
+ log_debug ! (
1223
+ logger,
1224
+ "Htlc hold time at pos {}: {} ms" ,
1225
+ route_hop_idx,
1226
+ hold_time
1227
+ ) ;
1229
1228
1230
- break ;
1229
+ // Shift attribution data to prepare for processing the next hop.
1230
+ attribution_data. shift_left ( ) ;
1231
+ } else {
1232
+ // Store the failing hop, but continue processing the failure for the remaining hops. During the
1233
+ // upgrade period, it may happen that nodes along the way drop attribution data. If the legacy
1234
+ // failure is still valid, it should be processed normally.
1235
+ attribution_failed_channel = route_hop. short_channel_id ( ) ;
1236
+
1237
+ log_debug ! (
1238
+ logger,
1239
+ "Invalid HMAC in attribution data for node at pos {}" ,
1240
+ route_hop_idx
1241
+ ) ;
1242
+ }
1231
1243
}
1244
+ } else {
1245
+ // When no attribution data is provided at all, blame the first hop when the failing node turns out to
1246
+ // be unindentifiable.
1247
+ attribution_failed_channel = route_hop. short_channel_id ( ) ;
1232
1248
}
1233
1249
}
1234
1250
@@ -1421,14 +1437,17 @@ where
1421
1437
onion_error_code : _error_code_ret,
1422
1438
#[ cfg( any( test, feature = "_test_utils" ) ) ]
1423
1439
onion_error_data : _error_packet_ret,
1440
+ #[ cfg( test) ]
1441
+ attribution_failed_channel,
1424
1442
}
1425
1443
} else {
1426
1444
// only not set either packet unparseable or hmac does not match with any
1427
1445
// payment not retryable only when garbage is from the final node
1428
1446
log_warn ! (
1429
1447
logger,
1430
- "Non-attributable failure encountered on route {}" ,
1431
- path. hops. iter( ) . map( |h| h. pubkey. to_string( ) ) . collect:: <Vec <_>>( ) . join( "->" )
1448
+ "Non-attributable failure encountered on route {}. Attributation data failed for channel {}" ,
1449
+ path. hops. iter( ) . map( |h| h. pubkey. to_string( ) ) . collect:: <Vec <_>>( ) . join( "->" ) ,
1450
+ attribution_failed_channel. unwrap_or_default( ) ,
1432
1451
) ;
1433
1452
1434
1453
DecodedOnionFailure {
@@ -1441,6 +1460,8 @@ where
1441
1460
onion_error_code : None ,
1442
1461
#[ cfg( any( test, feature = "_test_utils" ) ) ]
1443
1462
onion_error_data : None ,
1463
+ #[ cfg( test) ]
1464
+ attribution_failed_channel,
1444
1465
}
1445
1466
}
1446
1467
}
@@ -2046,6 +2067,8 @@ impl HTLCFailReason {
2046
2067
onion_error_code : Some ( * failure_reason) ,
2047
2068
#[ cfg( any( test, feature = "_test_utils" ) ) ]
2048
2069
onion_error_data : Some ( data. clone ( ) ) ,
2070
+ #[ cfg( test) ]
2071
+ attribution_failed_channel : None ,
2049
2072
}
2050
2073
} else {
2051
2074
unreachable ! ( ) ;
@@ -2787,6 +2810,7 @@ fn process_failure_packet(
2787
2810
2788
2811
#[ cfg( test) ]
2789
2812
mod tests {
2813
+ use core:: iter;
2790
2814
use std:: sync:: Arc ;
2791
2815
2792
2816
use crate :: io;
@@ -3130,18 +3154,32 @@ mod tests {
3130
3154
3131
3155
for mutation_type in failure_mutations
3132
3156
. chain ( attribution_data_mutations. map ( MutationType :: AttributionData ) )
3157
+ . chain ( iter:: once ( MutationType :: DropAttributionData ) )
3133
3158
{
3159
+ // If the mutation is in the attribution data and not in the failure message itself, the invalid
3160
+ // attribution data should be ignored and the failure should still surface.
3161
+ let failure_ok = matches ! ( mutation_type, MutationType :: DropAttributionData )
3162
+ || matches ! ( mutation_type, MutationType :: AttributionData ( _) ) ;
3163
+
3134
3164
let mutation = Mutation { node : mutating_node, mutation_type } ;
3135
3165
let decrypted_failure =
3136
3166
test_attributable_failure_packet_onion_with_mutation ( Some ( mutation) ) ;
3137
3167
3138
- if decrypted_failure. onion_error_code
3139
- == Some ( LocalHTLCFailureReason :: IncorrectPaymentDetails )
3140
- {
3168
+ if failure_ok {
3169
+ assert_eq ! (
3170
+ decrypted_failure. onion_error_code,
3171
+ Some ( LocalHTLCFailureReason :: IncorrectPaymentDetails )
3172
+ ) ;
3141
3173
continue ;
3142
3174
}
3143
3175
3144
- assert ! ( decrypted_failure. short_channel_id == Some ( mutating_node as u64 ) ) ;
3176
+ // Currently attribution data isn't used yet to identify the failing node, because this would hinder the
3177
+ // upgrade path.
3178
+ assert ! ( decrypted_failure. short_channel_id. is_none( ) ) ;
3179
+
3180
+ // Assert that attribution data is interpreted correctly via a test-only field.
3181
+ assert ! ( decrypted_failure. attribution_failed_channel == Some ( mutating_node as u64 ) ) ;
3182
+
3145
3183
assert_eq ! ( decrypted_failure. hold_times, [ 5 , 4 , 3 , 2 , 1 ] [ ..mutating_node] ) ;
3146
3184
}
3147
3185
}
@@ -3165,6 +3203,7 @@ mod tests {
3165
3203
enum MutationType {
3166
3204
FailureMessage ( usize ) ,
3167
3205
AttributionData ( AttributionDataMutationType ) ,
3206
+ DropAttributionData ,
3168
3207
}
3169
3208
3170
3209
struct Mutation {
@@ -3252,6 +3291,10 @@ mod tests {
3252
3291
// Mutate hold times.
3253
3292
packet. attribution_data . as_mut ( ) . unwrap ( ) . hmacs [ * i] ^= 1 ;
3254
3293
} ,
3294
+ MutationType :: DropAttributionData => {
3295
+ // Drop attribution data completely. This simulates a node that does not support the feature.
3296
+ packet. attribution_data = None ;
3297
+ } ,
3255
3298
}
3256
3299
} ;
3257
3300
@@ -3560,7 +3603,7 @@ mod tests {
3560
3603
// With attributable failures, it should still be possible to identify the failing node.
3561
3604
let logger: TestLogger = TestLogger :: new ( ) ;
3562
3605
let decrypted_failure = test_failure_attribution ( & logger, onion_error_packet) ;
3563
- assert_eq ! ( decrypted_failure. short_channel_id , Some ( 0 ) ) ;
3606
+ assert_eq ! ( decrypted_failure. attribution_failed_channel , Some ( 0 ) ) ;
3564
3607
}
3565
3608
3566
3609
#[ test]
@@ -3628,7 +3671,7 @@ mod tests {
3628
3671
// Expect attribution up to hop 20.
3629
3672
let expected_failed_chan =
3630
3673
if failure_pos < MAX_HOPS { Some ( failure_pos as u64 ) } else { None } ;
3631
- assert_eq ! ( decrypted_failure. short_channel_id , expected_failed_chan) ;
3674
+ assert_eq ! ( decrypted_failure. attribution_failed_channel , expected_failed_chan) ;
3632
3675
}
3633
3676
}
3634
3677
0 commit comments