@@ -25,7 +25,7 @@ use chain::transaction::OutPoint;
25
25
use chain:: keysinterface:: { ChannelKeys , KeysInterface } ;
26
26
use util:: transaction_utils;
27
27
use util:: ser:: { Readable , ReadableArgs , Writeable , Writer , WriterWriteAdaptor } ;
28
- use util:: logger:: Logger ;
28
+ use util:: logger:: { Logger , LogHolder } ;
29
29
use util:: errors:: APIError ;
30
30
use util:: config:: { UserConfig , ChannelConfig } ;
31
31
@@ -783,6 +783,8 @@ impl Channel {
783
783
let mut local_htlc_total_msat = 0 ;
784
784
let mut value_to_self_msat_offset = 0 ;
785
785
786
+ log_trace ! ( self , "Building commitment transaction number {} for {}, generated by {} with fee {}..." , commitment_number, if local { "us" } else { "remote" } , if generated_by_local { "us" } else { "remote" } , feerate_per_kw) ;
787
+
786
788
macro_rules! get_htlc_in_commitment {
787
789
( $htlc: expr, $offered: expr) => {
788
790
HTLCOutputInCommitment {
@@ -796,44 +798,49 @@ impl Channel {
796
798
}
797
799
798
800
macro_rules! add_htlc_output {
799
- ( $htlc: expr, $outbound: expr, $source: expr) => {
801
+ ( $htlc: expr, $outbound: expr, $source: expr, $state_name : expr ) => {
800
802
if $outbound == local { // "offered HTLC output"
801
803
let htlc_in_tx = get_htlc_in_commitment!( $htlc, true ) ;
802
804
if $htlc. amount_msat / 1000 >= dust_limit_satoshis + ( feerate_per_kw * HTLC_TIMEOUT_TX_WEIGHT / 1000 ) {
805
+ log_trace!( self , " ...including {} {} HTLC {} (hash {}) with value {}" , if $outbound { "outbound" } else { "inbound" } , $state_name, $htlc. htlc_id, log_bytes!( $htlc. payment_hash. 0 ) , $htlc. amount_msat) ;
803
806
txouts. push( ( TxOut {
804
807
script_pubkey: chan_utils:: get_htlc_redeemscript( & htlc_in_tx, & keys) . to_v0_p2wsh( ) ,
805
808
value: $htlc. amount_msat / 1000
806
809
} , Some ( ( htlc_in_tx, $source) ) ) ) ;
807
810
} else {
811
+ log_trace!( self , " ...including {} {} dust HTLC {} (hash {}) with value {} due to dust limit" , if $outbound { "outbound" } else { "inbound" } , $state_name, $htlc. htlc_id, log_bytes!( $htlc. payment_hash. 0 ) , $htlc. amount_msat) ;
808
812
included_dust_htlcs. push( ( htlc_in_tx, $source) ) ;
809
813
}
810
814
} else {
811
815
let htlc_in_tx = get_htlc_in_commitment!( $htlc, false ) ;
812
816
if $htlc. amount_msat / 1000 >= dust_limit_satoshis + ( feerate_per_kw * HTLC_SUCCESS_TX_WEIGHT / 1000 ) {
817
+ log_trace!( self , " ...including {} {} HTLC {} (hash {}) with value {}" , if $outbound { "outbound" } else { "inbound" } , $state_name, $htlc. htlc_id, log_bytes!( $htlc. payment_hash. 0 ) , $htlc. amount_msat) ;
813
818
txouts. push( ( TxOut { // "received HTLC output"
814
819
script_pubkey: chan_utils:: get_htlc_redeemscript( & htlc_in_tx, & keys) . to_v0_p2wsh( ) ,
815
820
value: $htlc. amount_msat / 1000
816
821
} , Some ( ( htlc_in_tx, $source) ) ) ) ;
817
822
} else {
823
+ log_trace!( self , " ...including {} {} dust HTLC {} (hash {}) with value {}" , if $outbound { "outbound" } else { "inbound" } , $state_name, $htlc. htlc_id, log_bytes!( $htlc. payment_hash. 0 ) , $htlc. amount_msat) ;
818
824
included_dust_htlcs. push( ( htlc_in_tx, $source) ) ;
819
825
}
820
826
}
821
827
}
822
828
}
823
829
824
830
for ref htlc in self . pending_inbound_htlcs . iter ( ) {
825
- let include = match htlc. state {
826
- InboundHTLCState :: RemoteAnnounced ( _) => !generated_by_local,
827
- InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( _) => !generated_by_local,
828
- InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( _) => true ,
829
- InboundHTLCState :: Committed => true ,
830
- InboundHTLCState :: LocalRemoved ( _) => !generated_by_local,
831
+ let ( include, state_name ) = match htlc. state {
832
+ InboundHTLCState :: RemoteAnnounced ( _) => ( !generated_by_local, "RemoteAnnounced" ) ,
833
+ InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( _) => ( !generated_by_local, "AwaitingRemoteRevokeToAnnounce" ) ,
834
+ InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( _) => ( true , "AwaitingAnnouncedRemoteRevoke" ) ,
835
+ InboundHTLCState :: Committed => ( true , "Committed" ) ,
836
+ InboundHTLCState :: LocalRemoved ( _) => ( !generated_by_local, "LocalRemoved" ) ,
831
837
} ;
832
838
833
839
if include {
834
- add_htlc_output ! ( htlc, false , None ) ;
840
+ add_htlc_output ! ( htlc, false , None , state_name ) ;
835
841
remote_htlc_total_msat += htlc. amount_msat ;
836
842
} else {
843
+ log_trace ! ( self , " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})" , htlc. htlc_id, log_bytes!( htlc. payment_hash. 0 ) , htlc. amount_msat, state_name) ;
837
844
match & htlc. state {
838
845
& InboundHTLCState :: LocalRemoved ( ref reason) => {
839
846
if generated_by_local {
@@ -848,18 +855,19 @@ impl Channel {
848
855
}
849
856
850
857
for ref htlc in self . pending_outbound_htlcs . iter ( ) {
851
- let include = match htlc. state {
852
- OutboundHTLCState :: LocalAnnounced ( _) => generated_by_local,
853
- OutboundHTLCState :: Committed => true ,
854
- OutboundHTLCState :: RemoteRemoved => generated_by_local,
855
- OutboundHTLCState :: AwaitingRemoteRevokeToRemove => generated_by_local,
856
- OutboundHTLCState :: AwaitingRemovedRemoteRevoke => false ,
858
+ let ( include, state_name ) = match htlc. state {
859
+ OutboundHTLCState :: LocalAnnounced ( _) => ( generated_by_local, "LocalAnnounced" ) ,
860
+ OutboundHTLCState :: Committed => ( true , "Committed" ) ,
861
+ OutboundHTLCState :: RemoteRemoved => ( generated_by_local, "RemoteRemoved" ) ,
862
+ OutboundHTLCState :: AwaitingRemoteRevokeToRemove => ( generated_by_local, "AwaitingRemoteRevokeToRemove" ) ,
863
+ OutboundHTLCState :: AwaitingRemovedRemoteRevoke => ( false , "AwaitingRemovedRemoteRevoke" ) ,
857
864
} ;
858
865
859
866
if include {
860
- add_htlc_output ! ( htlc, true , Some ( & htlc. source) ) ;
867
+ add_htlc_output ! ( htlc, true , Some ( & htlc. source) , state_name ) ;
861
868
local_htlc_total_msat += htlc. amount_msat ;
862
869
} else {
870
+ log_trace ! ( self , " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})" , htlc. htlc_id, log_bytes!( htlc. payment_hash. 0 ) , htlc. amount_msat, state_name) ;
863
871
match htlc. state {
864
872
OutboundHTLCState :: AwaitingRemoteRevokeToRemove |OutboundHTLCState :: AwaitingRemovedRemoteRevoke => {
865
873
if htlc. fail_reason . is_none ( ) {
@@ -1217,6 +1225,7 @@ impl Channel {
1217
1225
_ => { }
1218
1226
}
1219
1227
}
1228
+ log_trace ! ( self , "Adding HTLC claim to holding_cell! Current state: {}" , self . channel_state) ;
1220
1229
self . holding_cell_htlc_updates . push ( HTLCUpdateAwaitingACK :: ClaimHTLC {
1221
1230
payment_preimage : payment_preimage_arg, htlc_id : htlc_id_arg,
1222
1231
} ) ;
@@ -1230,6 +1239,7 @@ impl Channel {
1230
1239
debug_assert ! ( false , "Have an inbound HTLC we tried to claim before it was fully committed to" ) ;
1231
1240
return Ok ( ( None , Some ( self . channel_monitor . clone ( ) ) ) ) ;
1232
1241
}
1242
+ log_trace ! ( self , "Upgrading HTLC {} to LocalRemoved with a Fulfill!" , log_bytes!( htlc. payment_hash. 0 ) ) ;
1233
1243
htlc. state = InboundHTLCState :: LocalRemoved ( InboundHTLCRemovalReason :: Fulfill ( payment_preimage_arg. clone ( ) ) ) ;
1234
1244
}
1235
1245
@@ -1712,6 +1722,7 @@ impl Channel {
1712
1722
} ;
1713
1723
let local_commitment_txid = local_commitment_tx. 0 . txid ( ) ;
1714
1724
let local_sighash = hash_to_message ! ( & bip143:: SighashComponents :: new( & local_commitment_tx. 0 ) . sighash_all( & local_commitment_tx. 0 . input[ 0 ] , & funding_script, self . channel_value_satoshis) [ ..] ) ;
1725
+ log_trace ! ( self , "Checking commitment tx signature {} by key {} against tx {} with redeemscript {}" , log_bytes!( msg. signature. serialize_compact( ) [ ..] ) , log_bytes!( self . their_funding_pubkey. unwrap( ) . serialize( ) ) , encode:: serialize_hex( & local_commitment_tx. 0 ) , encode:: serialize_hex( & funding_script) ) ;
1715
1726
secp_check ! ( self . secp_ctx. verify( & local_sighash, & msg. signature, & self . their_funding_pubkey. unwrap( ) ) , "Invalid commitment tx signature from peer" ) ;
1716
1727
1717
1728
//If channel fee was updated by funder confirm funder can afford the new fee rate when applied to the current local commitment transaction
@@ -1737,6 +1748,7 @@ impl Channel {
1737
1748
if let Some ( _) = htlc. transaction_output_index {
1738
1749
let mut htlc_tx = self . build_htlc_transaction ( & local_commitment_txid, & htlc, true , & local_keys, feerate_per_kw) ;
1739
1750
let htlc_redeemscript = chan_utils:: get_htlc_redeemscript ( & htlc, & local_keys) ;
1751
+ log_trace ! ( self , "Checking HTLC tx signature {} by key {} against tx {} with redeemscript {}" , log_bytes!( msg. htlc_signatures[ idx] . serialize_compact( ) [ ..] ) , log_bytes!( local_keys. b_htlc_key. serialize( ) ) , encode:: serialize_hex( & htlc_tx) , encode:: serialize_hex( & htlc_redeemscript) ) ;
1740
1752
let htlc_sighash = hash_to_message ! ( & bip143:: SighashComponents :: new( & htlc_tx) . sighash_all( & htlc_tx. input[ 0 ] , & htlc_redeemscript, htlc. amount_msat / 1000 ) [ ..] ) ;
1741
1753
secp_check ! ( self . secp_ctx. verify( & htlc_sighash, & msg. htlc_signatures[ idx] , & local_keys. b_htlc_key) , "Invalid HTLC tx signature from peer" ) ;
1742
1754
let htlc_sig = if htlc. offered {
@@ -1982,74 +1994,89 @@ impl Channel {
1982
1994
self . monitor_pending_order = None ;
1983
1995
}
1984
1996
1997
+ log_trace ! ( self , "Updating HTLCs on receipt of RAA..." ) ;
1985
1998
let mut to_forward_infos = Vec :: new ( ) ;
1986
1999
let mut revoked_htlcs = Vec :: new ( ) ;
1987
2000
let mut update_fail_htlcs = Vec :: new ( ) ;
1988
2001
let mut update_fail_malformed_htlcs = Vec :: new ( ) ;
1989
2002
let mut require_commitment = false ;
1990
2003
let mut value_to_self_msat_diff: i64 = 0 ;
1991
- // We really shouldnt have two passes here, but retain gives a non-mutable ref (Rust bug)
1992
- self . pending_inbound_htlcs . retain ( |htlc| {
1993
- if let & InboundHTLCState :: LocalRemoved ( ref reason) = & htlc. state {
1994
- if let & InboundHTLCRemovalReason :: Fulfill ( _) = reason {
1995
- value_to_self_msat_diff += htlc. amount_msat as i64 ;
1996
- }
1997
- false
1998
- } else { true }
1999
- } ) ;
2000
- self . pending_outbound_htlcs . retain ( |htlc| {
2001
- if let OutboundHTLCState :: AwaitingRemovedRemoteRevoke = htlc. state {
2002
- if let Some ( reason) = htlc. fail_reason . clone ( ) { // We really want take() here, but, again, non-mut ref :(
2003
- revoked_htlcs. push ( ( htlc. source . clone ( ) , htlc. payment_hash , reason) ) ;
2004
- } else {
2005
- // They fulfilled, so we sent them money
2006
- value_to_self_msat_diff -= htlc. amount_msat as i64 ;
2007
- }
2008
- false
2009
- } else { true }
2010
- } ) ;
2011
- for htlc in self . pending_inbound_htlcs . iter_mut ( ) {
2012
- let swap = if let & InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( _) = & htlc. state {
2013
- true
2014
- } else if let & InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( _) = & htlc. state {
2015
- true
2016
- } else { false } ;
2017
- if swap {
2018
- let mut state = InboundHTLCState :: Committed ;
2019
- mem:: swap ( & mut state, & mut htlc. state ) ;
2020
-
2021
- if let InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( forward_info) = state {
2022
- htlc. state = InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( forward_info) ;
2023
- require_commitment = true ;
2024
- } else if let InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( forward_info) = state {
2025
- match forward_info {
2026
- PendingHTLCStatus :: Fail ( fail_msg) => {
2027
- require_commitment = true ;
2028
- match fail_msg {
2029
- HTLCFailureMsg :: Relay ( msg) => {
2030
- htlc. state = InboundHTLCState :: LocalRemoved ( InboundHTLCRemovalReason :: FailRelay ( msg. reason . clone ( ) ) ) ;
2031
- update_fail_htlcs. push ( msg)
2032
- } ,
2033
- HTLCFailureMsg :: Malformed ( msg) => {
2034
- htlc. state = InboundHTLCState :: LocalRemoved ( InboundHTLCRemovalReason :: FailMalformed ( ( msg. sha256_of_onion , msg. failure_code ) ) ) ;
2035
- update_fail_malformed_htlcs. push ( msg)
2036
- } ,
2004
+
2005
+ {
2006
+ // Take references explicitly so that we can hold multiple references to self.
2007
+ let pending_inbound_htlcs: & mut Vec < _ > = & mut self . pending_inbound_htlcs ;
2008
+ let pending_outbound_htlcs: & mut Vec < _ > = & mut self . pending_outbound_htlcs ;
2009
+ let logger = LogHolder { logger : & self . logger } ;
2010
+
2011
+ // We really shouldnt have two passes here, but retain gives a non-mutable ref (Rust bug)
2012
+ pending_inbound_htlcs. retain ( |htlc| {
2013
+ if let & InboundHTLCState :: LocalRemoved ( ref reason) = & htlc. state {
2014
+ log_trace ! ( logger, " ...removing inbound LocalRemoved {}" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2015
+ if let & InboundHTLCRemovalReason :: Fulfill ( _) = reason {
2016
+ value_to_self_msat_diff += htlc. amount_msat as i64 ;
2017
+ }
2018
+ false
2019
+ } else { true }
2020
+ } ) ;
2021
+ pending_outbound_htlcs. retain ( |htlc| {
2022
+ if let OutboundHTLCState :: AwaitingRemovedRemoteRevoke = htlc. state {
2023
+ log_trace ! ( logger, " ...removing outbound AwaitingRemovedRemoteRevoke {}" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2024
+ if let Some ( reason) = htlc. fail_reason . clone ( ) { // We really want take() here, but, again, non-mut ref :(
2025
+ revoked_htlcs. push ( ( htlc. source . clone ( ) , htlc. payment_hash , reason) ) ;
2026
+ } else {
2027
+ // They fulfilled, so we sent them money
2028
+ value_to_self_msat_diff -= htlc. amount_msat as i64 ;
2029
+ }
2030
+ false
2031
+ } else { true }
2032
+ } ) ;
2033
+ for htlc in pending_inbound_htlcs. iter_mut ( ) {
2034
+ let swap = if let & InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( _) = & htlc. state {
2035
+ log_trace ! ( logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce {} to Committed" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2036
+ true
2037
+ } else if let & InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( _) = & htlc. state {
2038
+ log_trace ! ( logger, " ...promoting inbound AwaitingAnnouncedRemoteRevoke {} to Committed" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2039
+ true
2040
+ } else { false } ;
2041
+ if swap {
2042
+ let mut state = InboundHTLCState :: Committed ;
2043
+ mem:: swap ( & mut state, & mut htlc. state ) ;
2044
+
2045
+ if let InboundHTLCState :: AwaitingRemoteRevokeToAnnounce ( forward_info) = state {
2046
+ htlc. state = InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( forward_info) ;
2047
+ require_commitment = true ;
2048
+ } else if let InboundHTLCState :: AwaitingAnnouncedRemoteRevoke ( forward_info) = state {
2049
+ match forward_info {
2050
+ PendingHTLCStatus :: Fail ( fail_msg) => {
2051
+ require_commitment = true ;
2052
+ match fail_msg {
2053
+ HTLCFailureMsg :: Relay ( msg) => {
2054
+ htlc. state = InboundHTLCState :: LocalRemoved ( InboundHTLCRemovalReason :: FailRelay ( msg. reason . clone ( ) ) ) ;
2055
+ update_fail_htlcs. push ( msg)
2056
+ } ,
2057
+ HTLCFailureMsg :: Malformed ( msg) => {
2058
+ htlc. state = InboundHTLCState :: LocalRemoved ( InboundHTLCRemovalReason :: FailMalformed ( ( msg. sha256_of_onion , msg. failure_code ) ) ) ;
2059
+ update_fail_malformed_htlcs. push ( msg)
2060
+ } ,
2061
+ }
2062
+ } ,
2063
+ PendingHTLCStatus :: Forward ( forward_info) => {
2064
+ to_forward_infos. push ( ( forward_info, htlc. htlc_id ) ) ;
2065
+ htlc. state = InboundHTLCState :: Committed ;
2037
2066
}
2038
- } ,
2039
- PendingHTLCStatus :: Forward ( forward_info) => {
2040
- to_forward_infos. push ( ( forward_info, htlc. htlc_id ) ) ;
2041
- htlc. state = InboundHTLCState :: Committed ;
2042
2067
}
2043
2068
}
2044
2069
}
2045
2070
}
2046
- }
2047
- for htlc in self . pending_outbound_htlcs . iter_mut ( ) {
2048
- if let OutboundHTLCState :: LocalAnnounced ( _) = htlc. state {
2049
- htlc. state = OutboundHTLCState :: Committed ;
2050
- } else if let OutboundHTLCState :: AwaitingRemoteRevokeToRemove = htlc. state {
2051
- htlc. state = OutboundHTLCState :: AwaitingRemovedRemoteRevoke ;
2052
- require_commitment = true ;
2071
+ for htlc in pending_outbound_htlcs. iter_mut ( ) {
2072
+ if let OutboundHTLCState :: LocalAnnounced ( _) = htlc. state {
2073
+ log_trace ! ( logger, " ...promoting outbound LocalAnnounced {} to Committed" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2074
+ htlc. state = OutboundHTLCState :: Committed ;
2075
+ } else if let OutboundHTLCState :: AwaitingRemoteRevokeToRemove = htlc. state {
2076
+ log_trace ! ( logger, " ...promoting outbound AwaitingRemoteRevokeToRemove {} to AwaitingRemovedRemoteRevoke" , log_bytes!( htlc. payment_hash. 0 ) ) ;
2077
+ htlc. state = OutboundHTLCState :: AwaitingRemovedRemoteRevoke ;
2078
+ require_commitment = true ;
2079
+ }
2053
2080
}
2054
2081
}
2055
2082
self . value_to_self_msat = ( self . value_to_self_msat as i64 + value_to_self_msat_diff) as u64 ;
@@ -2346,6 +2373,8 @@ impl Channel {
2346
2373
}
2347
2374
}
2348
2375
2376
+ log_trace ! ( self , "Regenerated latest commitment update with {} update_adds, {} update_fulfills, {} update_fails, and {} update_fail_malformeds" ,
2377
+ update_add_htlcs. len( ) , update_fulfill_htlcs. len( ) , update_fail_htlcs. len( ) , update_fail_malformed_htlcs. len( ) ) ;
2349
2378
msgs:: CommitmentUpdate {
2350
2379
update_add_htlcs, update_fulfill_htlcs, update_fail_htlcs, update_fail_malformed_htlcs,
2351
2380
update_fee : None , //TODO: We need to support re-generating any update_fees in the last commitment_signed!
@@ -3320,6 +3349,7 @@ impl Channel {
3320
3349
let remote_commitment_txid = remote_commitment_tx. 0 . txid ( ) ;
3321
3350
let remote_sighash = hash_to_message ! ( & bip143:: SighashComponents :: new( & remote_commitment_tx. 0 ) . sighash_all( & remote_commitment_tx. 0 . input[ 0 ] , & funding_script, self . channel_value_satoshis) [ ..] ) ;
3322
3351
let our_sig = self . secp_ctx . sign ( & remote_sighash, & self . local_keys . funding_key ) ;
3352
+ log_trace ! ( self , "Signing remote commitment tx {} with redeemscript {} with pubkey {} -> {}" , encode:: serialize_hex( & remote_commitment_tx. 0 ) , encode:: serialize_hex( & funding_script) , log_bytes!( PublicKey :: from_secret_key( & self . secp_ctx, & self . local_keys. funding_key) . serialize( ) ) , log_bytes!( our_sig. serialize_compact( ) [ ..] ) ) ;
3323
3353
3324
3354
let mut htlc_sigs = Vec :: with_capacity ( remote_commitment_tx. 1 ) ;
3325
3355
for & ( ref htlc, _) in remote_commitment_tx. 2 . iter ( ) {
@@ -3329,6 +3359,7 @@ impl Channel {
3329
3359
let htlc_sighash = hash_to_message ! ( & bip143:: SighashComponents :: new( & htlc_tx) . sighash_all( & htlc_tx. input[ 0 ] , & htlc_redeemscript, htlc. amount_msat / 1000 ) [ ..] ) ;
3330
3360
let our_htlc_key = secp_check ! ( chan_utils:: derive_private_key( & self . secp_ctx, & remote_keys. per_commitment_point, & self . local_keys. htlc_base_key) , "Derived invalid key, peer is maliciously selecting parameters" ) ;
3331
3361
htlc_sigs. push ( self . secp_ctx . sign ( & htlc_sighash, & our_htlc_key) ) ;
3362
+ log_trace ! ( self , "Signing remote HTLC tx {} with redeemscript {} with pubkey {} -> {}" , encode:: serialize_hex( & htlc_tx) , encode:: serialize_hex( & htlc_redeemscript) , log_bytes!( PublicKey :: from_secret_key( & self . secp_ctx, & our_htlc_key) . serialize( ) ) , log_bytes!( htlc_sigs. last( ) . unwrap( ) . serialize_compact( ) [ ..] ) ) ;
3332
3363
}
3333
3364
}
3334
3365
0 commit comments