Created
April 1, 2021 15:40
-
-
Save e00dan/f3c3eccb2da793e0f4d67e0efef337c7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::ops::Deref; | |
use std::path::Path; | |
use std::str::FromStr; | |
use serde::{Deserialize, Serialize}; | |
use tempfile::NamedTempFile; | |
use ckb_fixed_hash::H256; | |
use ckb_hash::new_blake2b; | |
use ckb_jsonrpc_types as rpc_types; | |
use ckb_sdk::{ | |
calc_max_mature_number, | |
constants::{CELLBASE_MATURITY, MIN_SECP_CELL_CAPACITY, ONE_CKB}, | |
Address, AddressPayload, GenesisInfo, HttpRpcClient, HumanCapacity, SECP256K1, | |
}; | |
use ckb_types::{ | |
bytes::{Bytes, BytesMut}, | |
core::{ | |
BlockView, Capacity, EpochNumberWithFraction, ScriptHashType, TransactionBuilder, | |
TransactionView, | |
}, | |
packed as ckb_packed, | |
prelude::Builder as CKBBuilder, | |
prelude::Pack as CKBPack, | |
prelude::Unpack as CKBUnpack, | |
}; | |
use gw_config::GenesisConfig; | |
use gw_generator::{genesis::build_genesis}; | |
use gw_types::{ | |
packed as gw_packed, packed::RollupConfig, prelude::Entity as GwEntity, | |
prelude::Pack as GwPack, prelude::PackVec as GwPackVec, | |
}; | |
use super::deploy_scripts::{ | |
get_network_type, run_cmd, wait_for_tx, DeploymentResult, TYPE_ID_CODE_HASH, | |
}; | |
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Default)] | |
pub struct UserRollupConfig { | |
pub l1_sudt_script_type_hash: H256, | |
pub burn_lock_hash: H256, | |
pub required_staking_capacity: u64, | |
pub challenge_maturity_blocks: u64, | |
pub finality_blocks: u64, | |
pub reward_burn_rate: u8, // * reward_burn_rate / 100 | |
pub allowed_eoa_type_hashes: Vec<H256>, | |
} | |
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Default)] | |
pub struct PoAConfig { | |
pub poa_setup: PoASetup, | |
} | |
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Default)] | |
pub struct PoASetup { | |
pub identity_size: u8, | |
pub round_interval_uses_seconds: bool, | |
pub identities: Vec<Bytes>, | |
pub aggregator_change_threshold: u8, | |
pub round_intervals: u32, | |
pub subblocks_per_round: u32, | |
} | |
pub fn serialize_poa_setup(setup: &PoASetup) -> Bytes { | |
let mut buffer = BytesMut::new(); | |
if setup.round_interval_uses_seconds { | |
buffer.extend_from_slice(&[1]); | |
} else { | |
buffer.extend_from_slice(&[0]); | |
} | |
if setup.identities.len() > 255 { | |
panic!("Too many identities!"); | |
} | |
buffer.extend_from_slice(&[ | |
setup.identity_size, | |
setup.identities.len() as u8, | |
setup.aggregator_change_threshold, | |
]); | |
buffer.extend_from_slice(&setup.round_intervals.to_le_bytes()[..]); | |
buffer.extend_from_slice(&setup.subblocks_per_round.to_le_bytes()[..]); | |
for identity in &setup.identities { | |
if identity.len() < setup.identity_size as usize { | |
panic!("Invalid identity!"); | |
} | |
buffer.extend_from_slice(&identity.slice(0..setup.identity_size as usize)); | |
} | |
buffer.freeze() | |
} | |
pub struct PoAData { | |
pub round_initial_subtime: u64, | |
pub subblock_subtime: u64, | |
pub subblock_index: u32, | |
pub aggregator_index: u16, | |
} | |
pub fn serialize_poa_data(data: &PoAData) -> Bytes { | |
let mut buffer = BytesMut::new(); | |
buffer.extend_from_slice(&data.round_initial_subtime.to_le_bytes()[..]); | |
buffer.extend_from_slice(&data.subblock_subtime.to_le_bytes()[..]); | |
buffer.extend_from_slice(&data.subblock_index.to_le_bytes()[..]); | |
buffer.extend_from_slice(&data.aggregator_index.to_le_bytes()[..]); | |
buffer.freeze() | |
} | |
pub fn deploy_genesis( | |
privkey_path: &Path, | |
ckb_rpc_url: &str, | |
deployment_result_path: &Path, | |
user_rollup_config_path: &Path, | |
poa_config_path: &Path, | |
_runner_config_path: &Path, | |
) -> Result<(), String> { | |
let deployment_result_string = | |
std::fs::read_to_string(deployment_result_path).map_err(|err| err.to_string())?; | |
let deployment_result: DeploymentResult = | |
serde_json::from_str(&deployment_result_string).map_err(|err| err.to_string())?; | |
let user_rollup_config_string = | |
std::fs::read_to_string(user_rollup_config_path).map_err(|err| err.to_string())?; | |
let user_rollup_config: UserRollupConfig = | |
serde_json::from_str(&user_rollup_config_string).map_err(|err| err.to_string())?; | |
let poa_config_string = | |
std::fs::read_to_string(poa_config_path).map_err(|err| err.to_string())?; | |
let poa_config: PoAConfig = | |
serde_json::from_str(&poa_config_string).map_err(|err| err.to_string())?; | |
let poa_setup = poa_config.poa_setup; | |
let mut rpc_client = HttpRpcClient::new(ckb_rpc_url.to_string()); | |
let network_type = get_network_type(&mut rpc_client)?; | |
let privkey_string = std::fs::read_to_string(privkey_path) | |
.map_err(|err| err.to_string())? | |
.split_whitespace() | |
.next() | |
.map(ToOwned::to_owned) | |
.ok_or_else(|| "File is empty".to_string())?; | |
let privkey_data = | |
H256::from_str(&privkey_string.trim()[2..]).map_err(|err| err.to_string())?; | |
let privkey = secp256k1::SecretKey::from_slice(privkey_data.as_bytes()) | |
.map_err(|err| format!("Invalid secp256k1 secret key format, error: {}", err))?; | |
let pubkey = secp256k1::PublicKey::from_secret_key(&SECP256K1, &privkey); | |
let owner_address_payload = AddressPayload::from_pubkey(&pubkey); | |
let owner_address = Address::new(network_type, owner_address_payload.clone()); | |
let owner_address_string = owner_address.to_string(); | |
let max_mature_number = get_max_mature_number(&mut rpc_client)?; | |
let tip_number = rpc_client.get_tip_block_number()?; | |
let genesis_block: BlockView = rpc_client | |
.get_block_by_number(0)? | |
.expect("Can not get genesis block?") | |
.into(); | |
let genesis_info = GenesisInfo::from_block(&genesis_block)?; | |
// FIXME: millisecond or second? | |
let timestamp = 0; | |
let first_cell_input: ckb_packed::CellInput = get_live_cells( | |
rpc_client.url(), | |
owner_address_string.as_str(), | |
max_mature_number, | |
None, | |
None, | |
Some(1), | |
)? | |
.into_iter() | |
.next() | |
.map(|(input, _)| input) | |
.ok_or_else(|| format!("No live cell found for address: {}", owner_address_string))?; | |
let rollup_cell_type_id: Bytes = calculate_type_id(&first_cell_input, 0); | |
let poa_setup_cell_type_id: Bytes = calculate_type_id(&first_cell_input, 1); | |
let poa_data_cell_type_id: Bytes = calculate_type_id(&first_cell_input, 2); | |
// calculate by: blake2b_hash(firstInput + rullupCell.outputIndex) | |
let rollup_type_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack(&TYPE_ID_CODE_HASH)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack(&rollup_cell_type_id)) | |
.build(); | |
let rollup_script_hash: H256 = CKBUnpack::unpack(&rollup_type_script.calc_script_hash()); | |
log::info!("rollup_script_hash: {:#x}", rollup_script_hash); | |
// 1. build genesis block | |
let allowed_contract_type_hashes: Vec<gw_packed::Byte32> = vec![ | |
GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.meta_contract_validator.script_type_hash.clone(), | |
)), | |
GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.l2_sudt_validator.script_type_hash.clone(), | |
)), | |
GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.polyjuice_validator.script_type_hash, | |
)), | |
]; | |
let rollup_config = RollupConfig::new_builder() | |
.l1_sudt_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
user_rollup_config.l1_sudt_script_type_hash, | |
))) | |
.custodian_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.custodian_lock.script_type_hash, | |
))) | |
.deposition_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.deposition_lock.script_type_hash, | |
))) | |
.withdrawal_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.withdrawal_lock.script_type_hash, | |
))) | |
.challenge_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.challenge_lock.script_type_hash, | |
))) | |
.stake_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.stake_lock.script_type_hash, | |
))) | |
.l2_sudt_validator_script_type_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
deployment_result.l2_sudt_validator.script_type_hash, | |
))) | |
.burn_lock_hash(GwPack::pack(&Into::<[u8; 32]>::into( | |
user_rollup_config.burn_lock_hash, | |
))) | |
.required_staking_capacity(GwPack::pack(&user_rollup_config.required_staking_capacity)) | |
.challenge_maturity_blocks(GwPack::pack(&user_rollup_config.challenge_maturity_blocks)) | |
.finality_blocks(GwPack::pack(&user_rollup_config.finality_blocks)) | |
.reward_burn_rate(user_rollup_config.reward_burn_rate.into()) | |
.allowed_eoa_type_hashes(GwPackVec::pack( | |
user_rollup_config | |
.allowed_eoa_type_hashes | |
.into_iter() | |
.map(|hash| GwPack::pack(&Into::<[u8; 32]>::into(hash))), | |
)) | |
.allowed_contract_type_hashes(GwPackVec::pack(allowed_contract_type_hashes)) | |
.build(); | |
let genesis_config = GenesisConfig { | |
timestamp, | |
meta_contract_validator_type_hash: deployment_result | |
.meta_contract_validator | |
.script_type_hash | |
.clone(), | |
rollup_type_hash: rollup_script_hash.into(), | |
rollup_config: rollup_config.into(), | |
}; | |
let genesis_with_global_state = | |
build_genesis(&genesis_config).map_err(|err| err.to_string())?; | |
// 2. build rollup cell (with type id) | |
let (rollup_output, rollup_data): (ckb_packed::CellOutput, Bytes) = { | |
let data = genesis_with_global_state.global_state.as_bytes(); | |
let lock_args = Bytes::from( | |
[ | |
poa_setup_cell_type_id.deref(), | |
poa_data_cell_type_id.deref(), | |
] | |
.concat() | |
.to_vec(), | |
); | |
let lock_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack( | |
&deployment_result.state_validator_lock.script_type_hash, | |
)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack(&lock_args)) | |
.build(); | |
let output = ckb_packed::CellOutput::new_builder() | |
.lock(lock_script) | |
.type_(CKBPack::pack(&Some(rollup_type_script))) | |
.build(); | |
let output = fit_output_capacity(output, data.len()); | |
(output, data) | |
}; | |
// 3. build PoA setup cell (with type id) | |
let (poa_setup_output, poa_setup_data): (ckb_packed::CellOutput, Bytes) = { | |
let data = serialize_poa_setup(&poa_setup); | |
let lock_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack(&deployment_result.poa_state.script_type_hash)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack( | |
&rollup_output.lock().calc_script_hash().as_bytes(), | |
)) | |
.build(); | |
let type_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack(&TYPE_ID_CODE_HASH)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack(&poa_setup_cell_type_id)) | |
.build(); | |
let output = ckb_packed::CellOutput::new_builder() | |
.lock(lock_script) | |
.type_(CKBPack::pack(&Some(type_script))) | |
.build(); | |
let output = fit_output_capacity(output, data.len()); | |
(output, data) | |
}; | |
// 4. build PoA data cell (with type id) | |
let (poa_data_output, poa_data_data): (ckb_packed::CellOutput, Bytes) = { | |
let median_time = rpc_client.get_blockchain_info()?.median_time.0 / 1000; | |
let poa_data = PoAData { | |
round_initial_subtime: median_time, | |
subblock_subtime: median_time, | |
subblock_index: 0, | |
aggregator_index: 0, | |
}; | |
let data = serialize_poa_data(&poa_data); | |
let lock_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack(&deployment_result.poa_state.script_type_hash)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack( | |
&rollup_output.lock().calc_script_hash().as_bytes(), | |
)) | |
.build(); | |
let type_script = ckb_packed::Script::new_builder() | |
.code_hash(CKBPack::pack(&TYPE_ID_CODE_HASH)) | |
.hash_type(ScriptHashType::Type.into()) | |
.args(CKBPack::pack(&poa_data_cell_type_id)) | |
.build(); | |
let output = ckb_packed::CellOutput::new_builder() | |
.lock(lock_script) | |
.type_(CKBPack::pack(&Some(type_script))) | |
.build(); | |
let output = fit_output_capacity(output, data.len()); | |
(output, data) | |
}; | |
// 5. put genesis block in rollup'cell witness | |
let witness_0: ckb_packed::WitnessArgs = { | |
let output_type = genesis_with_global_state.genesis.as_bytes(); | |
ckb_packed::WitnessArgs::new_builder() | |
.output_type(CKBPack::pack(&Some(output_type))) | |
.build() | |
}; | |
// 6. build transaction | |
let tx_fee = ONE_CKB; | |
let total_output_capacity: u64 = [&rollup_output, &poa_setup_output, &poa_data_output] | |
.iter() | |
.map(|output| { | |
let value: u64 = CKBUnpack::unpack(&output.capacity()); | |
value | |
}) | |
.sum(); | |
let total_capacity = total_output_capacity + tx_fee; | |
let (inputs, total_input_capacity) = collect_live_cells( | |
rpc_client.url(), | |
owner_address_string.as_str(), | |
max_mature_number, | |
tip_number, | |
total_capacity, | |
)?; | |
if inputs[0].as_slice() != first_cell_input.as_slice() { | |
return Err(format!("first input cell changed")); | |
} | |
let mut raw_outputs_data = vec![rollup_data, poa_setup_data, poa_data_data]; | |
let mut outputs = vec![rollup_output, poa_setup_output, poa_data_output]; | |
// collect_live_cells will ensure `total_input_capacity >= total_capacity`. | |
let rest_capacity = total_input_capacity - total_capacity; | |
let max_tx_fee_str = if rest_capacity >= MIN_SECP_CELL_CAPACITY { | |
outputs.push( | |
ckb_packed::CellOutput::new_builder() | |
.lock(ckb_packed::Script::from(&owner_address_payload)) | |
.capacity(CKBPack::pack(&rest_capacity)) | |
.build(), | |
); | |
raw_outputs_data.push(Default::default()); | |
"1.0" | |
} else { | |
"62.0" | |
}; | |
let outputs_data: Vec<ckb_packed::Bytes> = raw_outputs_data | |
.iter() | |
.map(|data| CKBPack::pack(data)) | |
.collect(); | |
let tx: TransactionView = TransactionBuilder::default() | |
.cell_deps(vec![ | |
deployment_result.state_validator.cell_dep.into(), | |
genesis_info.sighash_dep(), | |
]) | |
.set_inputs(inputs) | |
.set_outputs(outputs) | |
.set_outputs_data(outputs_data) | |
.set_witnesses(vec![CKBPack::pack(&witness_0.as_bytes())]) | |
.build(); | |
// 7. build ckb-cli tx and sign | |
let tx_file = NamedTempFile::new().map_err(|err| err.to_string())?; | |
let tx_path_str = tx_file.path().to_str().unwrap(); | |
let _output = run_cmd(&[ | |
"--url", | |
rpc_client.url(), | |
"tx", | |
"init", | |
"--tx-file", | |
tx_path_str, | |
])?; | |
let tx_json = rpc_types::Transaction::from(tx.data()); | |
let tx_body: serde_json::Value = serde_json::to_value(&tx_json).unwrap(); | |
let cli_tx_content = std::fs::read_to_string(tx_path_str).unwrap(); | |
let mut cli_tx: serde_json::Value = serde_json::from_str(&cli_tx_content).unwrap(); | |
cli_tx["transaction"] = tx_body; | |
let cli_tx_content = serde_json::to_string_pretty(&cli_tx).unwrap(); | |
std::fs::write(tx_path_str, cli_tx_content.as_bytes()).map_err(|err| err.to_string())?; | |
let _otuput = run_cmd(&[ | |
"--url", | |
rpc_client.url(), | |
"tx", | |
"sign-inputs", | |
"--privkey-path", | |
privkey_path.to_str().expect("non-utf8 file path"), | |
"--tx-file", | |
tx_path_str, | |
"--add-signatures", | |
])?; | |
// 8. send and then wait for tx | |
let send_output = run_cmd(&[ | |
"--url", | |
rpc_client.url(), | |
"tx", | |
"send", | |
"--tx-file", | |
tx_path_str, | |
"--max-tx-fee", | |
max_tx_fee_str, | |
"--skip-check", | |
])?; | |
let tx_hash = H256::from_str(&send_output.trim()[2..]).map_err(|err| err.to_string())?; | |
wait_for_tx(&mut rpc_client, &tx_hash, 120)?; | |
// 9. write runner config | |
Ok(()) | |
} | |
fn calculate_type_id(first_cell_input: &ckb_packed::CellInput, first_output_index: u64) -> Bytes { | |
let mut blake2b = new_blake2b(); | |
blake2b.update(first_cell_input.as_slice()); | |
blake2b.update(&first_output_index.to_le_bytes()); | |
let mut ret = [0; 32]; | |
blake2b.finalize(&mut ret); | |
Bytes::from(ret.to_vec()) | |
} | |
fn fit_output_capacity(output: ckb_packed::CellOutput, data_size: usize) -> ckb_packed::CellOutput { | |
let data_capacity = Capacity::bytes(data_size).expect("data capacity"); | |
let capacity = output | |
.occupied_capacity(data_capacity) | |
.expect("occupied_capacity"); | |
output | |
.as_builder() | |
.capacity(CKBPack::pack(&capacity.as_u64())) | |
.build() | |
} | |
fn collect_live_cells( | |
rpc_client_url: &str, | |
owner_address_str: &str, | |
max_mature_number: u64, | |
tip_number: u64, | |
total_capacity: u64, | |
) -> Result<(Vec<ckb_packed::CellInput>, u64), String> { | |
let number_step = 10000; | |
let limit = Some(usize::max_value()); | |
let mut from_number = 0; | |
let mut to_number = from_number + number_step - 1; | |
let mut total_input_capacity = 0; | |
let mut inputs = Vec::new(); | |
while total_input_capacity < total_capacity { | |
if from_number > tip_number { | |
return Err(format!( | |
"not enough capacity from {}, expected: {}, found: {}", | |
owner_address_str, | |
HumanCapacity(total_capacity), | |
HumanCapacity(total_input_capacity), | |
)); | |
} | |
let new_cells = get_live_cells( | |
rpc_client_url, | |
owner_address_str, | |
max_mature_number, | |
Some(from_number), | |
Some(to_number), | |
limit, | |
)?; | |
for (new_input, new_capacity) in new_cells { | |
total_input_capacity += new_capacity; | |
inputs.push(new_input); | |
if total_input_capacity >= total_capacity { | |
break; | |
} | |
} | |
from_number += number_step; | |
to_number += number_step; | |
} | |
Ok((inputs, total_input_capacity)) | |
} | |
// NOTE: This is an inefficient way to collect cells | |
fn get_live_cells( | |
rpc_client_url: &str, | |
owner_address_str: &str, | |
max_mature_number: u64, | |
from_number: Option<u64>, | |
to_number: Option<u64>, | |
limit: Option<usize>, | |
) -> Result<Vec<(ckb_packed::CellInput, u64)>, String> { | |
let from_number_string = from_number.map(|value| value.to_string()); | |
let to_number_string = to_number.map(|value| value.to_string()); | |
let mut actual_limit = limit.unwrap_or(20); | |
let mut cells = Vec::new(); | |
while cells.is_empty() { | |
let limit_string = actual_limit.to_string(); | |
// wallet get-live-cells --address {address} --fast-mode --limit {limit} --from {from-number} --to {to-number} | |
let mut args: Vec<&str> = vec![ | |
"--output-format", | |
"json", | |
"--url", | |
rpc_client_url, | |
"wallet", | |
"get-live-cells", | |
"--address", | |
owner_address_str, | |
"--fast-mode", | |
]; | |
if let Some(from_number) = from_number_string.as_ref() { | |
args.push("--from"); | |
args.push(from_number.as_str()); | |
}; | |
if let Some(to_number) = to_number_string.as_ref() { | |
args.push("--to"); | |
args.push(to_number.as_str()); | |
}; | |
args.push("--limit"); | |
args.push(limit_string.as_str()); | |
let live_cells_output = run_cmd(args)?; | |
let live_cells: serde_json::Value = | |
serde_json::from_str(&live_cells_output).map_err(|err| err.to_string())?; | |
cells = live_cells["live_cells"] | |
.as_array() | |
.expect("josn live cells") | |
.iter() | |
.filter_map(|live_cell| { | |
/* | |
{ | |
"capacity": "1200.0 (CKB)", | |
"data_bytes": 968, | |
"index": { | |
"output_index": 0, | |
"tx_index": 1 | |
}, | |
"lock_hash": "0x1cdeae55a5768fe14b628001c6247ae84c70310a7ddcfdc73ac68494251e46ec", | |
"mature": true, | |
"number": 6617, | |
"output_index": 0, | |
"tx_hash": "0x0d0d63184973ccdaf2c972783e1ed5f984a3e31b971e3294b092e54fe1d86961", | |
"type_hashes": null | |
} | |
*/ | |
let tx_index = live_cell["index"]["tx_index"] | |
.as_u64() | |
.expect("live cell tx_index"); | |
let number = live_cell["number"].as_u64().expect("live cell number"); | |
let data_bytes = live_cell["data_bytes"] | |
.as_u64() | |
.expect("live cell data_bytes"); | |
let type_is_null = live_cell["type_hashes"].is_null(); | |
if !type_is_null | |
|| data_bytes > 0 | |
|| !is_mature(number, tx_index, max_mature_number) | |
{ | |
log::debug!( | |
"has type: {}, data not empty: {}, immature: {}, number: {}, tx_index: {}", | |
!type_is_null, | |
data_bytes > 0, | |
!is_mature(number, tx_index, max_mature_number), | |
number, | |
tx_index, | |
); | |
return None; | |
} | |
let input_tx_hash = | |
H256::from_str(&live_cell["tx_hash"].as_str().expect("live cell tx hash")[2..]) | |
.expect("convert to h256"); | |
let input_index = live_cell["output_index"] | |
.as_u64() | |
.expect("live cell output index") as u32; | |
let capacity = HumanCapacity::from_str( | |
live_cell["capacity"] | |
.as_str() | |
.expect("live cell capacity") | |
.split(' ') | |
.next() | |
.expect("capacity"), | |
) | |
.map(|human_capacity| human_capacity.0) | |
.expect("parse capacity"); | |
let out_point = | |
ckb_packed::OutPoint::new(CKBPack::pack(&input_tx_hash), input_index); | |
let input = ckb_packed::CellInput::new(out_point, 0); | |
Some((input, capacity)) | |
}) | |
.collect(); | |
if actual_limit > u32::max_value() as usize / 2 { | |
log::debug!("Can not find live cells for {}", owner_address_str); | |
break; | |
} | |
actual_limit *= 2; | |
} | |
Ok(cells) | |
} | |
// Get max mature block number | |
pub fn get_max_mature_number(rpc_client: &mut HttpRpcClient) -> Result<u64, String> { | |
let tip_epoch = rpc_client | |
.get_tip_header() | |
.map(|header| EpochNumberWithFraction::from_full_value(header.inner.epoch.0))?; | |
let tip_epoch_number = tip_epoch.number(); | |
if tip_epoch_number < 4 { | |
// No cellbase live cell is mature | |
Ok(0) | |
} else { | |
let max_mature_epoch = rpc_client | |
.get_epoch_by_number(tip_epoch_number - 4)? | |
.ok_or_else(|| "Can not get epoch less than current epoch number".to_string())?; | |
let start_number = max_mature_epoch.start_number; | |
let length = max_mature_epoch.length; | |
Ok(calc_max_mature_number( | |
tip_epoch, | |
Some((start_number, length)), | |
CELLBASE_MATURITY, | |
)) | |
} | |
} | |
pub fn is_mature(number: u64, tx_index: u64, max_mature_number: u64) -> bool { | |
// Not cellbase cell | |
tx_index > 0 | |
// Live cells in genesis are all mature | |
|| number == 0 | |
|| number <= max_mature_number | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment