ckb-cli 1.4.0

ckb command line interface
use std::collections::HashMap;

use anyhow::{anyhow, Result};
use ckb_sdk::{
    constants::{MULTISIG_TYPE_HASH, SIGHASH_TYPE_HASH},
    traits::{
        CellCollector, CellQueryOptions, DefaultCellCollector, DefaultHeaderDepResolver,
        DefaultTransactionDependencyProvider, OffchainTransactionDependencyProvider, Signer,
        SignerError, TransactionDependencyError, TransactionDependencyProvider, ValueRangeOption,
    },
    tx_builder::{balance_tx_capacity, fill_placeholder_witnesses, CapacityBalancer},
    unlock::{
        MultisigConfig, ScriptUnlocker, SecpMultisigScriptSigner, SecpMultisigUnlocker,
        SecpSighashUnlocker,
    },
    Address, ScriptId,
};
use ckb_types::{
    bytes::Bytes,
    core::{HeaderView, TransactionBuilder, TransactionView},
    packed::{self, Byte32, CellOutput, OutPoint},
    prelude::*,
};

use super::state_change::ChangeInfo;
use crate::utils::genesis_info::GenesisInfo;

// build balanced transaction
pub fn build_tx<T: ChangeInfo>(
    (from_address, fee_rate): (&Address, u64),
    multisig_config: Option<&MultisigConfig>,
    lock_script: &packed::Script,
    infos: &[T],
    pending_tx: Option<packed::Transaction>,
    genesis_info: &GenesisInfo,
    ckb_rpc: &str,
) -> Result<Option<packed::Transaction>> {
    let to_capacity: u64 = infos
        .iter()
        .filter(|info| info.has_new_output())
        .map(|info| info.occupied_capacity(lock_script))
        .sum();
    if to_capacity == 0 {
        return Ok(None);
    }

    let mut cell_collector = DefaultCellCollector::new(ckb_rpc);
    if let Some(pending_tx) = pending_tx.as_ref() {
        cell_collector.apply_tx(pending_tx.clone())?;
    }

    let from_script = packed::Script::from(from_address.payload());
    let (mut inputs, mut input_capacities): (Vec<_>, Vec<_>) =
        infos.iter().filter_map(|info| info.build_input()).unzip();
    if inputs.is_empty() {
        let mut query = CellQueryOptions::new_lock(from_script.clone());
        query.secondary_script_len_range = Some(ValueRangeOption::new_exact(0));
        query.data_len_range = Some(ValueRangeOption::new_exact(0));
        let (more_infos, more_capacity) = cell_collector.collect_live_cells(&query, true)?;
        if more_infos.is_empty() {
            return Err(anyhow!("No live cell found from address: {}", from_address));
        }
        inputs.push(packed::CellInput::new(more_infos[0].out_point.clone(), 0));
        input_capacities.push(more_capacity);
    }
    if inputs.is_empty() {
        return Err(anyhow!(
            "Capacity(mature) not enough from {}, require more than {}",
            from_address,
            to_capacity,
        ));
    }

    let first_cell_input = &inputs[0];
    let (outputs, outputs_data): (Vec<_>, Vec<_>) = infos
        .iter()
        .filter_map(|info| info.build_cell_output(lock_script, first_cell_input))
        .unzip();
    let mut cell_deps = vec![genesis_info.sighash_dep()];
    if multisig_config.is_some() {
        cell_deps.push(genesis_info.multisig_dep());
    }
    let mut unlockers = HashMap::new();
    let signer = DummySigner {
        args: vec![from_address.payload().args()],
    };
    let sighash_unlocker = SecpSighashUnlocker::from(Box::new(signer.clone()) as Box<_>);
    let sighash_script_id = ScriptId::new_type(SIGHASH_TYPE_HASH.clone());
    unlockers.insert(
        sighash_script_id,
        Box::new(sighash_unlocker) as Box<dyn ScriptUnlocker>,
    );
    if let Some(cfg) = multisig_config {
        let multisig_signer = SecpMultisigScriptSigner::new(Box::new(signer), cfg.clone());
        let multisig_unlocker = SecpMultisigUnlocker::new(multisig_signer);
        let multisig_script_id = ScriptId::new_type(MULTISIG_TYPE_HASH.clone());
        unlockers.insert(
            multisig_script_id,
            Box::new(multisig_unlocker) as Box<dyn ScriptUnlocker>,
        );
    }

    let placeholder_witness = packed::WitnessArgs::new_builder()
        .lock(Some(Bytes::from(vec![0u8; 65])).pack())
        .build();
    let balancer = CapacityBalancer::new_simple(from_script, placeholder_witness, fee_rate);

    let header_dep_resolver = DefaultHeaderDepResolver::new(ckb_rpc);
    let tx_dep_provider = {
        let inner = DefaultTransactionDependencyProvider::new(ckb_rpc, 0);
        let mut offchain = OffchainTransactionDependencyProvider::default();
        if let Some(pending_tx) = pending_tx {
            let tx_view = pending_tx.into_view();
            let tx_hash = tx_view.hash().unpack();
            offchain.txs.insert(tx_hash.clone(), tx_view.clone());
            for (output_idx, (output, output_data)) in tx_view.outputs_with_data_iter().enumerate()
            {
                offchain
                    .cells
                    .insert((tx_hash.clone(), output_idx as u32), (output, output_data));
            }
        }
        TxDepProviderWrapper { inner, offchain }
    };

    let base_tx = TransactionBuilder::default()
        .cell_deps(cell_deps)
        .inputs(inputs)
        .outputs(outputs)
        .outputs_data(outputs_data.into_iter().map(|data| data.pack()))
        .build();

    let (tx_filled_witnesses, _) =
        fill_placeholder_witnesses(base_tx, &tx_dep_provider, &unlockers)?;
    let balanced_tx = balance_tx_capacity(
        &tx_filled_witnesses,
        &balancer,
        &mut cell_collector,
        &tx_dep_provider,
        &genesis_info.cell_dep_resolver,
        &header_dep_resolver,
    )?;
    Ok(Some(balanced_tx.data()))
}

#[derive(Clone)]
struct DummySigner {
    args: Vec<Bytes>,
}
impl Signer for DummySigner {
    fn match_id(&self, id: &[u8]) -> bool {
        self.args.iter().any(|arg| arg.as_ref() == id)
    }
    fn sign(&self, _: &[u8], _: &[u8], _: bool, _: &TransactionView) -> Result<Bytes, SignerError> {
        unreachable!()
    }
}

struct TxDepProviderWrapper {
    inner: DefaultTransactionDependencyProvider,
    offchain: OffchainTransactionDependencyProvider,
}

impl TransactionDependencyProvider for TxDepProviderWrapper {
    fn get_transaction(
        &self,
        tx_hash: &Byte32,
    ) -> Result<TransactionView, TransactionDependencyError> {
        self.offchain
            .get_transaction(tx_hash)
            .or_else(|_| self.inner.get_transaction(tx_hash))
    }
    fn get_cell(&self, out_point: &OutPoint) -> Result<CellOutput, TransactionDependencyError> {
        self.offchain
            .get_cell(out_point)
            .or_else(|_| self.inner.get_cell(out_point))
    }
    fn get_cell_data(&self, out_point: &OutPoint) -> Result<Bytes, TransactionDependencyError> {
        self.offchain
            .get_cell_data(out_point)
            .or_else(|_| self.inner.get_cell_data(out_point))
    }
    fn get_header(&self, block_hash: &Byte32) -> Result<HeaderView, TransactionDependencyError> {
        self.offchain
            .get_header(block_hash)
            .or_else(|_| self.inner.get_header(block_hash))
    }
}