@@ -115,9 +115,13 @@ impl LocalFeatures {
115
115
}
116
116
117
117
/// Tracks globalfeatures which are in init messages and routing announcements
118
- #[ derive( Clone , PartialEq ) ]
118
+ #[ derive( Clone , PartialEq , Debug ) ]
119
119
pub struct GlobalFeatures {
120
+ #[ cfg( not( test) ) ]
120
121
flags : Vec < u8 > ,
122
+ // Used to test encoding of diverse msgs
123
+ #[ cfg( test) ]
124
+ pub flags : Vec < u8 >
121
125
}
122
126
123
127
impl GlobalFeatures {
@@ -327,7 +331,7 @@ pub struct ChannelReestablish {
327
331
}
328
332
329
333
/// An announcement_signatures message to be sent or received from a peer
330
- #[ derive( Clone ) ]
334
+ #[ derive( PartialEq , Clone , Debug ) ]
331
335
pub struct AnnouncementSignatures {
332
336
pub ( crate ) channel_id : [ u8 ; 32 ] ,
333
337
pub ( crate ) short_channel_id : u64 ,
@@ -336,7 +340,7 @@ pub struct AnnouncementSignatures {
336
340
}
337
341
338
342
/// An address which can be used to connect to a remote peer
339
- #[ derive( Clone ) ]
343
+ #[ derive( Clone , PartialEq , Debug ) ]
340
344
pub enum NetAddress {
341
345
/// An IPv4 address/port on which the peer is listenting.
342
346
IPv4 {
@@ -387,6 +391,7 @@ impl NetAddress {
387
391
#[ derive( Clone ) ]
388
392
// Only exposed as broadcast of node_announcement should be filtered by node_id
389
393
/// The unsigned part of a node_announcement
394
+ #[ derive( PartialEq , Debug ) ]
390
395
pub struct UnsignedNodeAnnouncement {
391
396
pub ( crate ) features : GlobalFeatures ,
392
397
pub ( crate ) timestamp : u32 ,
@@ -401,7 +406,7 @@ pub struct UnsignedNodeAnnouncement {
401
406
pub ( crate ) excess_address_data : Vec < u8 > ,
402
407
pub ( crate ) excess_data : Vec < u8 > ,
403
408
}
404
- #[ derive( Clone ) ]
409
+ #[ derive( PartialEq , Clone , Debug ) ]
405
410
/// A node_announcement message to be sent or received from a peer
406
411
pub struct NodeAnnouncement {
407
412
pub ( crate ) signature : Signature ,
@@ -410,7 +415,7 @@ pub struct NodeAnnouncement {
410
415
411
416
// Only exposed as broadcast of channel_announcement should be filtered by node_id
412
417
/// The unsigned part of a channel_announcement
413
- #[ derive( PartialEq , Clone ) ]
418
+ #[ derive( PartialEq , Clone , Debug ) ]
414
419
pub struct UnsignedChannelAnnouncement {
415
420
pub ( crate ) features : GlobalFeatures ,
416
421
pub ( crate ) chain_hash : Sha256dHash ,
@@ -424,7 +429,7 @@ pub struct UnsignedChannelAnnouncement {
424
429
pub ( crate ) excess_data : Vec < u8 > ,
425
430
}
426
431
/// A channel_announcement message to be sent or received from a peer
427
- #[ derive( PartialEq , Clone ) ]
432
+ #[ derive( PartialEq , Clone , Debug ) ]
428
433
pub struct ChannelAnnouncement {
429
434
pub ( crate ) node_signature_1 : Signature ,
430
435
pub ( crate ) node_signature_2 : Signature ,
@@ -433,7 +438,7 @@ pub struct ChannelAnnouncement {
433
438
pub ( crate ) contents : UnsignedChannelAnnouncement ,
434
439
}
435
440
436
- #[ derive( PartialEq , Clone ) ]
441
+ #[ derive( PartialEq , Clone , Debug ) ]
437
442
pub ( crate ) struct UnsignedChannelUpdate {
438
443
pub ( crate ) chain_hash : Sha256dHash ,
439
444
pub ( crate ) short_channel_id : u64 ,
@@ -446,7 +451,7 @@ pub(crate) struct UnsignedChannelUpdate {
446
451
pub ( crate ) excess_data : Vec < u8 > ,
447
452
}
448
453
/// A channel_update message to be sent or received from a peer
449
- #[ derive( PartialEq , Clone ) ]
454
+ #[ derive( PartialEq , Clone , Debug ) ]
450
455
pub struct ChannelUpdate {
451
456
pub ( crate ) signature : Signature ,
452
457
pub ( crate ) contents : UnsignedChannelUpdate ,
@@ -1322,9 +1327,15 @@ impl_writeable_len_match!(NodeAnnouncement, {
1322
1327
mod tests {
1323
1328
use hex;
1324
1329
use ln:: msgs;
1325
- use util:: ser:: Writeable ;
1330
+ use ln:: msgs:: GlobalFeatures ;
1331
+ use util:: ser:: { Writer , Writeable } ;
1332
+
1333
+ use bitcoin:: util:: hash:: Sha256dHash ;
1334
+ use bitcoin:: util:: address:: Address ;
1335
+ use bitcoin:: network:: constants:: Network ;
1336
+
1326
1337
use secp256k1:: key:: { PublicKey , SecretKey } ;
1327
- use secp256k1:: Secp256k1 ;
1338
+ use secp256k1:: { Secp256k1 , Message } ;
1328
1339
1329
1340
#[ test]
1330
1341
fn encoding_channel_reestablish_no_secret ( ) {
@@ -1362,4 +1373,162 @@ mod tests {
1362
1373
vec![ 4 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 5 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 4 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 9 , 3 , 27 , 132 , 197 , 86 , 123 , 18 , 100 , 64 , 153 , 93 , 62 , 213 , 170 , 186 , 5 , 101 , 215 , 30 , 24 , 52 , 96 , 72 , 25 , 255 , 156 , 23 , 245 , 233 , 213 , 221 , 7 , 143 ]
1363
1374
) ;
1364
1375
}
1376
+
1377
+ macro_rules! get_keys_from {
1378
+ ( $slice: expr) => {
1379
+ {
1380
+ let secp_ctx = Secp256k1 :: new( ) ;
1381
+ let privkey = SecretKey :: from_slice( & secp_ctx, & hex:: decode( $slice) . unwrap( ) [ ..] ) . unwrap( ) ;
1382
+ let pubkey = PublicKey :: from_secret_key( & secp_ctx, & privkey) ;
1383
+ ( privkey, pubkey, secp_ctx)
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ macro_rules! get_sig_on {
1389
+ ( $privkey: expr, $ctx: expr, $string: expr) => {
1390
+ {
1391
+ let sighash = Message :: from_slice( & $string. into_bytes( ) [ ..] ) . unwrap( ) ;
1392
+ $ctx. sign( & sighash, & $privkey)
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ #[ test]
1398
+ fn encoding_announcement_signatures ( ) {
1399
+ let ( privkey, _, secp_ctx) = get_keys_from ! ( "0101010101010101010101010101010101010101010101010101010101010101" ) ;
1400
+ let sig_1 = get_sig_on ! ( privkey, secp_ctx, String :: from( "01010101010101010101010101010101" ) ) ;
1401
+ let sig_2 = get_sig_on ! ( privkey, secp_ctx, String :: from( "02020202020202020202020202020202" ) ) ;
1402
+ let announcement_signatures = msgs:: AnnouncementSignatures {
1403
+ channel_id : [ 4 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 5 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ,
1404
+ short_channel_id : 2316138423780173 ,
1405
+ node_signature : sig_1,
1406
+ bitcoin_signature : sig_2,
1407
+ } ;
1408
+
1409
+ let encoded_value = announcement_signatures. encode ( ) ;
1410
+ assert_eq ! (
1411
+ encoded_value,
1412
+ vec![ 4 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 5 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 , 58 , 132 , 0 , 0 , 3 , 77 , 217 , 119 , 203 , 155 , 83 , 217 , 58 , 111 , 246 , 75 , 181 , 241 , 225 , 88 , 180 , 9 , 75 , 102 , 231 , 152 , 251 , 18 , 145 , 17 , 104 , 163 , 204 , 223 , 128 , 168 , 48 , 150 , 52 , 10 , 106 , 149 , 218 , 10 , 232 , 217 , 247 , 118 , 82 , 142 , 236 , 219 , 183 , 71 , 235 , 107 , 84 , 84 , 149 , 164 , 49 , 158 , 213 , 55 , 142 , 53 , 178 , 30 , 7 , 58 , 207 , 153 , 83 , 206 , 244 , 112 , 8 , 96 , 245 , 150 , 120 , 56 , 235 , 162 , 186 , 232 , 146 , 136 , 173 , 24 , 142 , 191 , 139 , 32 , 191 , 153 , 92 , 62 , 165 , 58 , 38 , 223 , 24 , 118 , 208 , 163 , 160 , 225 , 49 , 114 , 186 , 40 , 106 , 103 , 49 , 64 , 25 , 12 , 2 , 186 , 157 , 166 , 10 , 46 , 67 , 167 , 69 , 24 , 140 , 138 , 131 , 199 , 243 , 239 ] ) ;
1413
+ }
1414
+
1415
+ fn do_encoding_channel_announcement ( unknown_features_bits : bool , non_bitcoin_chain_hash : bool , excess_data : bool ) {
1416
+ let ( privkey_1, pubkey_1, secp_ctx_1) = get_keys_from ! ( "0101010101010101010101010101010101010101010101010101010101010101" ) ;
1417
+ let ( privkey_2, pubkey_2, secp_ctx_2) = get_keys_from ! ( "0202020202020202020202020202020202020202020202020202020202020202" ) ;
1418
+ let ( privkey_3, pubkey_3, secp_ctx_3) = get_keys_from ! ( "0303030303030303030303030303030303030303030303030303030303030303" ) ;
1419
+ let ( privkey_4, pubkey_4, secp_ctx_4) = get_keys_from ! ( "0404040404040404040404040404040404040404040404040404040404040404" ) ;
1420
+ let sig_1 = get_sig_on ! ( privkey_1, secp_ctx_1, String :: from( "01010101010101010101010101010101" ) ) ;
1421
+ let sig_2 = get_sig_on ! ( privkey_2, secp_ctx_2, String :: from( "01010101010101010101010101010101" ) ) ;
1422
+ let sig_3 = get_sig_on ! ( privkey_3, secp_ctx_3, String :: from( "01010101010101010101010101010101" ) ) ;
1423
+ let sig_4 = get_sig_on ! ( privkey_4, secp_ctx_4, String :: from( "01010101010101010101010101010101" ) ) ;
1424
+ let mut features = GlobalFeatures :: new ( ) ;
1425
+ if unknown_features_bits {
1426
+ features. flags = vec ! [ 0xFF , 0xFF ] ;
1427
+ }
1428
+ let unsigned_channel_announcement = msgs:: UnsignedChannelAnnouncement {
1429
+ features,
1430
+ chain_hash : if !non_bitcoin_chain_hash { Sha256dHash :: from_hex ( "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" ) . unwrap ( ) } else { Sha256dHash :: from_hex ( "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ) . unwrap ( ) } ,
1431
+ short_channel_id : 2316138423780173 ,
1432
+ node_id_1 : pubkey_1,
1433
+ node_id_2 : pubkey_2,
1434
+ bitcoin_key_1 : pubkey_3,
1435
+ bitcoin_key_2 : pubkey_4,
1436
+ excess_data : if excess_data { vec ! [ 10 , 0 , 0 , 20 , 0 , 0 , 30 , 0 , 0 , 40 ] } else { Vec :: new ( ) } ,
1437
+ } ;
1438
+ let channel_announcement = msgs:: ChannelAnnouncement {
1439
+ node_signature_1 : sig_1,
1440
+ node_signature_2 : sig_2,
1441
+ bitcoin_signature_1 : sig_3,
1442
+ bitcoin_signature_2 : sig_4,
1443
+ contents : unsigned_channel_announcement,
1444
+ } ;
1445
+ let encoded_value = channel_announcement. encode ( ) ;
1446
+ let mut target_value = vec ! [ 217 , 119 , 203 , 155 , 83 , 217 , 58 , 111 , 246 , 75 , 181 , 241 , 225 , 88 , 180 , 9 , 75 , 102 , 231 , 152 , 251 , 18 , 145 , 17 , 104 , 163 , 204 , 223 , 128 , 168 , 48 , 150 , 52 , 10 , 106 , 149 , 218 , 10 , 232 , 217 , 247 , 118 , 82 , 142 , 236 , 219 , 183 , 71 , 235 , 107 , 84 , 84 , 149 , 164 , 49 , 158 , 213 , 55 , 142 , 53 , 178 , 30 , 7 , 58 , 23 , 53 , 182 , 164 , 39 , 232 , 13 , 95 , 231 , 205 , 144 , 162 , 244 , 238 , 8 , 220 , 156 , 39 , 205 , 167 , 195 , 90 , 65 , 114 , 229 , 216 , 91 , 18 , 196 , 157 , 66 , 50 , 83 , 126 , 152 , 249 , 177 , 243 , 197 , 230 , 152 , 154 , 139 , 150 , 68 , 233 , 14 , 137 , 24 , 18 , 118 , 128 , 219 , 208 , 212 , 4 , 53 , 16 , 132 , 15 , 192 , 241 , 225 , 26 , 33 , 108 , 40 , 11 , 83 , 149 , 162 , 84 , 110 , 126 , 75 , 38 , 99 , 224 , 79 , 129 , 22 , 34 , 241 , 90 , 79 , 145 , 232 , 58 , 162 , 233 , 43 , 162 , 165 , 115 , 193 , 57 , 20 , 44 , 84 , 174 , 99 , 7 , 42 , 30 , 193 , 238 , 125 , 192 , 192 , 75 , 222 , 92 , 132 , 120 , 6 , 23 , 42 , 160 , 92 , 146 , 194 , 42 , 232 , 227 , 8 , 209 , 210 , 105 , 43 , 18 , 204 , 25 , 92 , 224 , 162 , 209 , 189 , 166 , 168 , 139 , 239 , 161 , 159 , 160 , 127 , 81 , 202 , 167 , 92 , 232 , 56 , 55 , 242 , 137 , 101 , 96 , 11 , 138 , 172 , 171 , 8 , 85 , 255 , 176 , 231 , 65 , 236 , 95 , 124 , 65 , 66 , 30 , 152 , 41 , 169 , 212 , 134 , 17 , 200 , 200 , 49 , 247 , 27 , 229 , 234 , 115 , 230 , 101 , 148 , 151 , 127 , 253 ] ;
1447
+ if unknown_features_bits {
1448
+ target_value. append ( & mut vec ! [ 0 , 2 , 255 , 255 ] ) ;
1449
+ } else {
1450
+ target_value. append ( & mut vec ! [ 0 , 0 ] ) ;
1451
+ }
1452
+ if non_bitcoin_chain_hash {
1453
+ target_value. append ( & mut vec ! [ 67 , 73 , 127 , 215 , 248 , 38 , 149 , 113 , 8 , 244 , 163 , 15 , 217 , 206 , 195 , 174 , 186 , 121 , 151 , 32 , 132 , 233 , 14 , 173 , 1 , 234 , 51 , 9 , 0 , 0 , 0 , 0 ] ) ;
1454
+ } else {
1455
+ target_value. append ( & mut vec ! [ 0 , 0 , 0 , 0 , 0 , 25 , 214 , 104 , 156 , 8 , 90 , 225 , 101 , 131 , 30 , 147 , 79 , 247 , 99 , 174 , 70 , 162 , 166 , 193 , 114 , 179 , 241 , 182 , 10 , 140 , 226 , 111 ] ) ;
1456
+ }
1457
+ target_value. append ( & mut vec ! [ 0 , 8 , 58 , 132 , 0 , 0 , 3 , 77 , 3 , 27 , 132 , 197 , 86 , 123 , 18 , 100 , 64 , 153 , 93 , 62 , 213 , 170 , 186 , 5 , 101 , 215 , 30 , 24 , 52 , 96 , 72 , 25 , 255 , 156 , 23 , 245 , 233 , 213 , 221 , 7 , 143 , 2 , 77 , 75 , 108 , 209 , 54 , 16 , 50 , 202 , 155 , 210 , 174 , 185 , 217 , 0 , 170 , 77 , 69 , 217 , 234 , 216 , 10 , 201 , 66 , 51 , 116 , 196 , 81 , 167 , 37 , 77 , 7 , 102 , 2 , 83 , 31 , 230 , 6 , 129 , 52 , 80 , 61 , 39 , 35 , 19 , 50 , 39 , 200 , 103 , 172 , 143 , 166 , 200 , 60 , 83 , 126 , 154 , 68 , 195 , 197 , 189 , 189 , 203 , 31 , 227 , 55 , 3 , 70 , 39 , 121 , 173 , 74 , 173 , 57 , 81 , 70 , 20 , 117 , 26 , 113 , 8 , 95 , 47 , 16 , 225 , 199 , 165 , 147 , 228 , 224 , 48 , 239 , 181 , 184 , 114 , 28 , 229 , 91 , 11 ] ) ;
1458
+ if excess_data {
1459
+ target_value. append ( & mut vec ! [ 10 , 0 , 0 , 20 , 0 , 0 , 30 , 0 , 0 , 40 ] ) ;
1460
+ }
1461
+ assert_eq ! ( encoded_value, target_value) ;
1462
+ }
1463
+
1464
+ #[ test]
1465
+ fn encoding_channel_announcement ( ) {
1466
+ do_encoding_channel_announcement ( false , false , false ) ;
1467
+ do_encoding_channel_announcement ( true , false , false ) ;
1468
+ do_encoding_channel_announcement ( false , true , false ) ;
1469
+ do_encoding_channel_announcement ( false , false , true ) ;
1470
+ do_encoding_channel_announcement ( true , true , true ) ;
1471
+ }
1472
+
1473
+ #[ test]
1474
+ fn encoding_node_announcement ( ) {
1475
+ //TODO:
1476
+ }
1477
+
1478
+ fn do_encoding_channel_update ( non_bitcoin_chain_hash : bool , direction : bool , disable : bool , htlc_maximum_msat : bool ) {
1479
+ let ( privkey_1, _, secp_ctx_1) = get_keys_from ! ( "0101010101010101010101010101010101010101010101010101010101010101" ) ;
1480
+ let sig_1 = get_sig_on ! ( privkey_1, secp_ctx_1, String :: from( "01010101010101010101010101010101" ) ) ;
1481
+ let unsigned_channel_update = msgs:: UnsignedChannelUpdate {
1482
+ chain_hash : if !non_bitcoin_chain_hash { Sha256dHash :: from_hex ( "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" ) . unwrap ( ) } else { Sha256dHash :: from_hex ( "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ) . unwrap ( ) } ,
1483
+ short_channel_id : 2316138423780173 ,
1484
+ timestamp : 20190119 ,
1485
+ flags : if direction { 1 } else { 0 } | if disable { 1 << 1 } else { 0 } | if htlc_maximum_msat { 1 << 8 } else { 0 } ,
1486
+ cltv_expiry_delta : 144 ,
1487
+ htlc_minimum_msat : 1000000 ,
1488
+ fee_base_msat : 10000 ,
1489
+ fee_proportional_millionths : 20 ,
1490
+ excess_data : if htlc_maximum_msat { vec ! [ 0 , 0 , 0 , 0 , 59 , 154 , 202 , 0 ] } else { Vec :: new ( ) }
1491
+ } ;
1492
+ let channel_update = msgs:: ChannelUpdate {
1493
+ signature : sig_1,
1494
+ contents : unsigned_channel_update
1495
+ } ;
1496
+ let encoded_value = channel_update. encode ( ) ;
1497
+ let mut target_value = vec ! [ 217 , 119 , 203 , 155 , 83 , 217 , 58 , 111 , 246 , 75 , 181 , 241 , 225 , 88 , 180 , 9 , 75 , 102 , 231 , 152 , 251 , 18 , 145 , 17 , 104 , 163 , 204 , 223 , 128 , 168 , 48 , 150 , 52 , 10 , 106 , 149 , 218 , 10 , 232 , 217 , 247 , 118 , 82 , 142 , 236 , 219 , 183 , 71 , 235 , 107 , 84 , 84 , 149 , 164 , 49 , 158 , 213 , 55 , 142 , 53 , 178 , 30 , 7 , 58 ] ;
1498
+ if non_bitcoin_chain_hash {
1499
+ target_value. append ( & mut vec ! [ 67 , 73 , 127 , 215 , 248 , 38 , 149 , 113 , 8 , 244 , 163 , 15 , 217 , 206 , 195 , 174 , 186 , 121 , 151 , 32 , 132 , 233 , 14 , 173 , 1 , 234 , 51 , 9 , 0 , 0 , 0 , 0 ] ) ;
1500
+ } else {
1501
+ target_value. append ( & mut vec ! [ 0 , 0 , 0 , 0 , 0 , 25 , 214 , 104 , 156 , 8 , 90 , 225 , 101 , 131 , 30 , 147 , 79 , 247 , 99 , 174 , 70 , 162 , 166 , 193 , 114 , 179 , 241 , 182 , 10 , 140 , 226 , 111 ] ) ;
1502
+ }
1503
+ target_value. append ( & mut vec ! [ 0 , 8 , 58 , 132 , 0 , 0 , 3 , 77 , 1 , 52 , 19 , 167 ] ) ;
1504
+ if htlc_maximum_msat {
1505
+ target_value. append ( & mut vec ! [ 1 ] ) ;
1506
+ } else {
1507
+ target_value. append ( & mut vec ! [ 0 ] ) ;
1508
+ }
1509
+ target_value. append ( & mut vec ! [ 0 ] ) ;
1510
+ if direction {
1511
+ let flag = target_value. last_mut ( ) . unwrap ( ) ;
1512
+ * flag = 1 ;
1513
+ }
1514
+ if disable {
1515
+ let flag = target_value. last_mut ( ) . unwrap ( ) ;
1516
+ * flag = * flag | 1 << 1 ;
1517
+ }
1518
+ target_value. append ( & mut vec ! [ 0 , 144 , 0 , 0 , 0 , 0 , 0 , 15 , 66 , 64 , 0 , 0 , 39 , 16 , 0 , 0 , 0 , 20 ] ) ;
1519
+ if htlc_maximum_msat {
1520
+ target_value. append ( & mut vec ! [ 0 , 0 , 0 , 0 , 59 , 154 , 202 , 0 ] ) ;
1521
+ }
1522
+ assert_eq ! ( encoded_value, target_value) ;
1523
+ }
1524
+
1525
+ #[ test]
1526
+ fn encoding_channel_update ( ) {
1527
+ do_encoding_channel_update ( false , false , false , false ) ;
1528
+ do_encoding_channel_update ( true , false , false , false ) ;
1529
+ do_encoding_channel_update ( false , true , false , false ) ;
1530
+ do_encoding_channel_update ( false , false , true , false ) ;
1531
+ do_encoding_channel_update ( false , false , false , true ) ;
1532
+ do_encoding_channel_update ( true , true , true , true ) ;
1533
+ }
1365
1534
}
0 commit comments