From 1d48914115ff21b616ad19416f5a34ef5d9b9f07 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 14 Jun 2024 10:50:52 +0800 Subject: [PATCH] verify shutdown fee for shutdown command --- src/ckb/channel.rs | 104 +++++++++++++----- src/ckb/config.rs | 3 + src/rpc/channel.rs | 34 ++++-- .../udt/08-node1-send-shutdown-channel.bru | 2 +- .../udt/09-node2-send-shutdown-channel.bru | 2 +- 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/ckb/channel.rs b/src/ckb/channel.rs index 4d429a514..1fca20a81 100644 --- a/src/ckb/channel.rs +++ b/src/ckb/channel.rs @@ -32,7 +32,10 @@ use std::{ }; use crate::{ - ckb::types::Shutdown, + ckb::{ + config::{DEFAULT_UDT_MINIMAL_CKB_AMOUNT, MIN_OCCUPIED_CAPACITY}, + types::Shutdown, + }, ckb_chain::{ contracts::{get_cell_deps_by_contracts, get_script_by_contract, Contract}, FundingRequest, FundingUdtInfo, @@ -41,7 +44,7 @@ use crate::{ }; use super::{ - config::{CKB_SHANNONS, DEFAULT_MIN_SHUTDOWN_FEE}, + config::MIN_UDT_OCCUPIED_CAPACITY, key::blake2b_hash_with_salt, network::PCNMessageWithPeerId, serde_utils::EntityHex, @@ -89,7 +92,7 @@ pub enum ChannelCommand { CommitmentSigned(), AddTlc(AddTlcCommand, RpcReplyPort>), RemoveTlc(RemoveTlcCommand, RpcReplyPort>), - Shutdown(ShutdownCommand), + Shutdown(ShutdownCommand, RpcReplyPort>), } #[derive(Debug)] @@ -136,7 +139,6 @@ pub const DEFAULT_MAX_TLC_VALUE_IN_FLIGHT: u128 = u128::MAX; pub const DEFAULT_MAX_ACCEPT_TLCS: u64 = u64::MAX; pub const DEFAULT_MIN_TLC_VALUE: u128 = 0; pub const DEFAULT_TO_LOCAL_DELAY_BLOCKS: u64 = 10; -pub const DEFAULT_UDT_MINIMAL_CKB_AMOUNT: u64 = 142 * CKB_SHANNONS + DEFAULT_MIN_SHUTDOWN_FEE; // 143 CKB for minimal UDT amount #[derive(Debug)] pub struct TxUpdateCommand { @@ -709,6 +711,8 @@ impl ChannelActor { )); } }; + + state.verify_shutdown_fee(command.fee)?; self.network .send_message(NetworkActorMessage::new_command( NetworkActorCommand::SendPcnMessage(PCNMessageWithPeerId { @@ -875,7 +879,18 @@ impl ChannelActor { } } } - ChannelCommand::Shutdown(command) => self.handle_shutdown_command(state, command), + ChannelCommand::Shutdown(command, reply) => { + match self.handle_shutdown_command(state, command) { + Ok(_) => { + let _ = reply.send(Ok(())); + Ok(()) + } + Err(err) => { + let _ = reply.send(Err(err.to_string())); + Err(err) + } + } + } } } @@ -1797,6 +1812,33 @@ impl ChannelActorState { &self.get_remote_channel_parameters().pubkeys.funding_pubkey } + pub fn verify_shutdown_fee(&self, fee: u64) -> ProcessingChannelResult { + let available_max_fee = if self.funding_udt_type_script.is_none() { + self.to_local_amount as u64 - MIN_OCCUPIED_CAPACITY + } else { + self.local_ckb_amount - MIN_UDT_OCCUPIED_CAPACITY + }; + if fee > available_max_fee { + return Err(ProcessingChannelError::InvalidParameter(format!( + "Local balance is not enough to pay the fee, you can pay at most {} as fee", + available_max_fee + ))); + } + + let (shutdown_tx, _) = self.build_shutdown_tx(false)?; + let tx_size = shutdown_tx.data().serialized_size_in_block(); + let expected_minimal_fee = tx_size as u64 * self.commitment_fee_rate; + if let Some(fee) = self.local_shutdown_fee { + if fee < expected_minimal_fee { + return Err(ProcessingChannelError::InvalidParameter(format!( + "Shutdown fee is not enough for minimal fee rate, expected fee >= {}", + expected_minimal_fee + ))); + } + } + Ok(()) + } + pub fn check_state_for_tlc_update(&self) -> ProcessingChannelResult { match self.state { ChannelState::ChannelReady(flags) => { @@ -2000,7 +2042,7 @@ impl ChannelActorState { flags | ShuttingDownFlags::DROPPING_PENDING, )); - let (shutdown_tx, message) = self.build_shutdown_tx()?; + let (shutdown_tx, message) = self.build_shutdown_tx(true)?; let sign_ctx = Musig2SignContext::from(&*self); // Create our shutdown signature if we haven't already. @@ -2638,7 +2680,12 @@ impl ChannelActorState { && self.should_local_go_first_in_musig2() } - pub fn build_shutdown_tx(&self) -> Result<(TransactionView, [u8; 32]), ProcessingChannelError> { + // `apply` = true means we are building the shutdown transaction to be broadcasted + // `apply` = false means we want to mock the shutdown transaction and only for calculating the fee + pub fn build_shutdown_tx( + &self, + apply: bool, + ) -> Result<(TransactionView, [u8; 32]), ProcessingChannelError> { // Don't use get_local_shutdown_script and get_remote_shutdown_script here // as they will panic if the scripts are not present. // This function may be called in a state where these scripts are not present. @@ -2647,30 +2694,35 @@ impl ChannelActorState { remote_shutdown_script, local_shutdown_fee, remote_shutdown_fee, - ) = match ( - self.local_shutdown_script.clone(), - self.remote_shutdown_script.clone(), - self.local_shutdown_fee, - self.remote_shutdown_fee, - ) { - ( - Some(local_shutdown_script), - Some(remote_shutdown_script), - Some(local_shutdown_fee), - Some(remote_shutdown_fee), - ) => ( - local_shutdown_script, - remote_shutdown_script, - local_shutdown_fee, - remote_shutdown_fee, - ), - _ => { - return Err(ProcessingChannelError::InvalidState(format!( + ) = if apply { + match ( + self.local_shutdown_script.clone(), + self.remote_shutdown_script.clone(), + self.local_shutdown_fee, + self.remote_shutdown_fee, + ) { + ( + Some(local_shutdown_script), + Some(remote_shutdown_script), + Some(local_shutdown_fee), + Some(remote_shutdown_fee), + ) => ( + local_shutdown_script, + remote_shutdown_script, + local_shutdown_fee, + remote_shutdown_fee, + ), + _ => { + return Err(ProcessingChannelError::InvalidState(format!( "Shutdown scripts are not present: local_shutdown_script {:?}, remote_shutdown_script {:?}, local_shutdown_fee {:?}, remote_shutdown_fee {:?}", &self.local_shutdown_script, &self.remote_shutdown_script, &self.local_shutdown_fee, &self.remote_shutdown_fee ))); + } } + } else { + let script = get_script_by_contract(Contract::Secp256k1Lock, &[0u8; 20]); + (script.clone(), script.clone(), 0, 0) }; let mut contracts = vec![Contract::CommitmentLock]; diff --git a/src/ckb/config.rs b/src/ckb/config.rs index 5e09b5d99..7c8f73ab0 100644 --- a/src/ckb/config.rs +++ b/src/ckb/config.rs @@ -14,6 +14,9 @@ pub const CKB_SHANNONS: u64 = 100_000_000; pub const DEFAULT_MIN_INBOUND_LIQUIDITY: u64 = 100 * CKB_SHANNONS; // 100 CKB for minimal inbound liquidity pub const DEFAULT_MIN_SHUTDOWN_FEE: u64 = 1 * CKB_SHANNONS; // 1 ckb prepared for shutdown transaction fee pub const MIN_OCCUPIED_CAPACITY: u64 = 61 * CKB_SHANNONS; // 61 CKB for occupied capacity +pub const MIN_UDT_OCCUPIED_CAPACITY: u64 = 142 * CKB_SHANNONS; // 142 CKB for UDT occupied capacity +pub const DEFAULT_UDT_MINIMAL_CKB_AMOUNT: u64 = + MIN_UDT_OCCUPIED_CAPACITY + DEFAULT_MIN_SHUTDOWN_FEE; // 143 CKB for minimal UDT amount // See comment in `LdkConfig` for why do we need to specify both name and long, // and prefix them with `ckb-`/`CKB_`. diff --git a/src/rpc/channel.rs b/src/rpc/channel.rs index 5dab83841..53be7ce57 100644 --- a/src/rpc/channel.rs +++ b/src/rpc/channel.rs @@ -333,16 +333,28 @@ where &self, params: ShutdownChannelParams, ) -> Result<(), ErrorObjectOwned> { - let message = NetworkActorMessage::Command(NetworkActorCommand::ControlPcnChannel( - ChannelCommandWithId { - channel_id: params.channel_id, - command: ChannelCommand::Shutdown(ShutdownCommand { - close_script: params.close_script.into(), - fee: params.fee, - }), - }, - )); - self.actor.cast(message).unwrap(); - Ok(()) + let message = |rpc_reply| -> NetworkActorMessage { + NetworkActorMessage::Command(NetworkActorCommand::ControlPcnChannel( + ChannelCommandWithId { + channel_id: params.channel_id, + command: ChannelCommand::Shutdown( + ShutdownCommand { + close_script: params.close_script.clone().into(), + fee: params.fee, + }, + rpc_reply, + ), + }, + )) + }; + + match call!(self.actor, message).unwrap() { + Ok(_response) => Ok(()), + Err(e) => Err(ErrorObjectOwned::owned( + CALL_EXECUTION_FAILED_CODE, + e, + Some(params), + )), + } } } diff --git a/tests/bruno/e2e/udt/08-node1-send-shutdown-channel.bru b/tests/bruno/e2e/udt/08-node1-send-shutdown-channel.bru index fd5f59c1e..b53e50b6e 100644 --- a/tests/bruno/e2e/udt/08-node1-send-shutdown-channel.bru +++ b/tests/bruno/e2e/udt/08-node1-send-shutdown-channel.bru @@ -28,7 +28,7 @@ body:json { "hash_type": "data", "args": "0x0101010101010101010101010101010101010101" }, - "fee": "0xbebc200" + "fee": "0x5F5E100" } ] } diff --git a/tests/bruno/e2e/udt/09-node2-send-shutdown-channel.bru b/tests/bruno/e2e/udt/09-node2-send-shutdown-channel.bru index 38211c5f2..5477d4dae 100644 --- a/tests/bruno/e2e/udt/09-node2-send-shutdown-channel.bru +++ b/tests/bruno/e2e/udt/09-node2-send-shutdown-channel.bru @@ -28,7 +28,7 @@ body:json { "hash_type": "data", "args": "0x0101010101010101010101010101010101010101" }, - "fee": "0xbebc200" + "fee": "0x5F5E100" } ] }