diff --git a/src/cli/init.rs b/src/cli/init.rs index cbcdb77..e2f0b0d 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -18,8 +18,8 @@ use ckb_sdk::{ TransactionBuilderConfiguration, }, types::{ - Address as CkbAddress, AddressPayload as CkbAddressPayload, NetworkInfo, ScriptGroup, - TransactionWithScriptGroups, + Address as CkbAddress, AddressPayload as CkbAddressPayload, NetworkInfo, NetworkType, + ScriptGroup, TransactionWithScriptGroups, }, SECP256K1, }; @@ -75,9 +75,8 @@ pub struct Args { #[arg(long, value_parser = value_parsers::OutPointValueParser)] pub(crate) spv_contract_out_point: OutPoint, - /// The owner of Bitcoin SPV cells. - #[arg(long, value_parser = value_parsers::AddressValueParser)] - pub(crate) spv_owner: CkbAddress, + #[clap(flatten)] + pub(crate) spv_owner: super::SpvOwner, /// Disable the on-chain difficulty check. /// @@ -110,9 +109,14 @@ impl Args { pub fn execute(&self) -> Result<()> { log::info!("Try to initialize a Bitcoin SPV instance on CKB"); + if self.disable_difficulty_check && self.ckb.network == NetworkType::Mainnet { + let msg = "For safety, the option `self.disable_difficulty_check` \ + are not allowed on the mainnet"; + return Err(Error::other(msg)); + } + self.check_inputs()?; log::info!("The bitcoin start height is {}", self.bitcoin_start_height); - self.check_remotes()?; let btc_start_header = self .bitcoin @@ -228,7 +232,7 @@ impl Args { Error::other(msg) })?; let spv_cell = CellOutput::new_builder() - .lock((&self.spv_owner).into()) + .lock(self.spv_owner.lock_script()) .type_(Some(spv_type_script).pack()) .build(); let spv_info = spv_cell @@ -389,36 +393,7 @@ impl Args { } fn check_inputs(&self) -> Result<()> { - if self.spv_owner.network() != self.ckb.network { - let msg = "The input addresses and the selected network are not matched"; - return Err(Error::cli(msg)); - } - - if self.spv_clients_count < 3 { - let msg = format!( - "The Bitcoint SPV clients count should be 3 at least but got {}", - self.spv_clients_count - ); - return Err(Error::cli(msg)); - } - - if self.bitcoin_start_height % DIFFCHANGE_INTERVAL != 0 { - let msg = format!( - "invalid Bitcoint start height, expected multiples of \ - {DIFFCHANGE_INTERVAL} but got {}", - self.bitcoin_start_height - ); - return Err(Error::cli(msg)); - } - - Ok(()) - } - - fn check_remotes(&self) -> Result<()> { - if self.spv_owner.network() != self.ckb.network { - let msg = "The input addresses and the selected network are not matched"; - return Err(Error::cli(msg)); - } + self.spv_owner.check_network(self.ckb.network)?; if self.spv_clients_count < 3 { let msg = format!( diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 96f3d80..b3f506a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,10 @@ //! The command line argument. -use ckb_sdk::{rpc::CkbRpcClient, types::NetworkType}; -use ckb_types::core::FeeRate; +use ckb_sdk::{ + rpc::CkbRpcClient, + types::{Address, NetworkType}, +}; +use ckb_types::{core::FeeRate, packed::Script}; use clap::{Args, Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; use url::Url; @@ -9,7 +12,7 @@ use url::Url; use crate::{ components::BitcoinClient, prelude::*, - result::Result, + result::{Error, Result}, utilities::{value_parsers, Key256Bits}, }; @@ -96,6 +99,55 @@ pub struct CkbRoArgs { pub(crate) network: NetworkType, } +#[derive(Parser)] +pub struct SpvOwner { + /// The owner of Bitcoin SPV cells. + /// + /// ### Warnings + /// + /// The owner should be an address which uses a ACP-like script. + /// + /// Current standard ACP lock isn't satisfied, because it's has the + /// following limits: + /// + /// > if 2 input cells are using the same type script, or are both missing + /// type scripts, the lock returns with an error state. + /// + /// > if 2 output cells are using the same type script, or are both missing + /// type scripts, the lock returns with an error state. + /// + /// ### References + /// + /// - [Anyone-Can-Pay Lock (a.k.a ACP)](https://github.com/nervosnetwork/rfcs/blob/198fc90ab7582953ed85a6655e88e51346857475/rfcs/0026-anyone-can-pay/0026-anyone-can-pay.md) + #[arg(long, value_parser = value_parsers::AddressValueParser)] + pub(crate) spv_owner: Address, +} + +#[derive(Parser)] +pub struct SpvOwnerOpt { + /// The owner of Bitcoin SPV cells. + /// If no owner is provided, the previous owner will be kept. + /// + /// ### Warnings + /// + /// The owner should be an address which uses a ACP-like script. + /// + /// Current standard ACP lock isn't satisfied, because it's has the + /// following limits: + /// + /// > if 2 input cells are using the same type script, or are both missing + /// type scripts, the lock returns with an error state. + /// + /// > if 2 output cells are using the same type script, or are both missing + /// type scripts, the lock returns with an error state. + /// + /// ### References + /// + /// - [Anyone-Can-Pay Lock (a.k.a ACP)](https://github.com/nervosnetwork/rfcs/blob/198fc90ab7582953ed85a6655e88e51346857475/rfcs/0026-anyone-can-pay/0026-anyone-can-pay.md) + #[arg(long, value_parser = value_parsers::AddressValueParser)] + pub(crate) spv_owner: Option
, +} + #[derive(Args)] #[group(multiple = false)] pub struct FeeRateArgs { @@ -110,16 +162,20 @@ pub struct FeeRateArgs { /// [Experimental] Enable dynamic fee rate for CKB transactions. /// - /// The actual fee rate will be the `median` fee rate which is fetched through the CKB RPC method `get_fee_rate_statistics`. + /// The actual fee rate will be the `median` fee rate which is fetched + /// through the CKB RPC method `get_fee_rate_statistics`. /// /// For security, a hard limit is required. - /// When the returned dynamic fee rate is larger than the hard limit, the hard limit will be used. + /// When the returned dynamic fee rate is larger than the hard limit, the + /// hard limit will be used. /// /// ### Warning /// /// Users have to make sure the remote CKB node they used are trustsed. /// - /// Ref: + /// ### References + /// + /// - [CKB JSON-RPC method `get_fee_rate_statistics`](https://github.com/nervosnetwork/ckb/tree/v0.114.0/rpc#method-get_fee_rate_statistics) #[arg( group = "dynamic-fee-rate", conflicts_with = "fixed-fee-rate", @@ -224,6 +280,48 @@ impl CkbRoArgs { } } +impl AsRef
for SpvOwner { + fn as_ref(&self) -> &Address { + &self.spv_owner + } +} + +impl SpvOwner { + pub fn check_network(&self, expected: NetworkType) -> Result<()> { + let actual = self.as_ref().network(); + if actual == expected { + Ok(()) + } else { + let msg = "The input addresses and the selected network are not matched"; + Err(Error::cli(msg)) + } + } + + pub fn lock_script(&self) -> Script { + self.as_ref().into() + } +} + +impl SpvOwnerOpt { + pub fn check_network(&self, expected: NetworkType) -> Result<()> { + let is_same_network = self + .spv_owner + .as_ref() + .map(|actual| actual.network() == expected) + .unwrap_or(true); + if is_same_network { + Ok(()) + } else { + let msg = "The input addresses and the selected network are not matched"; + Err(Error::cli(msg)) + } + } + + pub fn lock_script(&self) -> Option