1use std::{convert::TryInto, ptr, sync::atomic};
2
3use ckb_dao_utils::extract_dao_data;
4use ckb_types::{
5 core::{Capacity, EpochNumber, EpochNumberWithFraction, HeaderView},
6 packed::CellOutput,
7 prelude::*,
8 H160, H256, U256,
9};
10use sha3::{Digest, Keccak256};
11
12#[cfg(not(target_arch = "wasm32"))]
13use crate::rpc::CkbRpcClient;
14
15use crate::rpc::CkbRpcAsyncClient;
16use crate::traits::LiveCell;
17
18use secp256k1::ffi::CPtr;
19
20pub fn zeroize_privkey(key: &mut secp256k1::SecretKey) {
21 let key_ptr = key.as_mut_c_ptr();
22 for i in 0..key.as_ref().len() as isize {
23 unsafe { ptr::write_volatile(key_ptr.offset(i), Default::default()) }
24 atomic::compiler_fence(atomic::Ordering::SeqCst);
25 }
26}
27
28pub fn zeroize_slice(data: &mut [u8]) {
29 for elem in data {
30 unsafe { ptr::write_volatile(elem, Default::default()) }
31 atomic::compiler_fence(atomic::Ordering::SeqCst);
32 }
33}
34#[cfg(not(target_arch = "wasm32"))]
35pub fn get_max_mature_number(rpc_client: &CkbRpcClient) -> Result<u64, String> {
36 crate::rpc::block_on(get_max_mature_number_async(&rpc_client.into()))
37}
38
39pub async fn get_max_mature_number_async(rpc_client: &CkbRpcAsyncClient) -> Result<u64, String> {
40 let cellbase_maturity = EpochNumberWithFraction::from_full_value(
41 rpc_client
42 .get_consensus()
43 .await
44 .map_err(|err| err.to_string())?
45 .cellbase_maturity
46 .value(),
47 );
48 let tip_epoch = rpc_client
49 .get_tip_header()
50 .await
51 .map(|header| EpochNumberWithFraction::from_full_value(header.inner.epoch.value()))
52 .map_err(|err| err.to_string())?;
53
54 let tip_epoch_rational = tip_epoch.to_rational();
55 let cellbase_maturity_rational = cellbase_maturity.to_rational();
56
57 if tip_epoch_rational < cellbase_maturity_rational {
58 Ok(0)
60 } else {
61 let difference = tip_epoch_rational - cellbase_maturity_rational;
62 let rounds_down_difference = difference.clone().into_u256();
63 let difference_delta = difference - rounds_down_difference.clone();
64
65 let epoch_number = u64::from_le_bytes(
66 rounds_down_difference.to_le_bytes()[..8]
67 .try_into()
68 .expect("should be u64"),
69 )
70 .into();
71 let max_mature_epoch = rpc_client
72 .get_epoch_by_number(epoch_number)
73 .await
74 .map_err(|err| err.to_string())?
75 .ok_or_else(|| "Can not get epoch less than current epoch number".to_string())?;
76
77 let max_mature_block_number = (difference_delta
78 * U256::from(max_mature_epoch.length.value())
79 + U256::from(max_mature_epoch.start_number.value()))
80 .into_u256();
81
82 Ok(u64::from_le_bytes(
83 max_mature_block_number.to_le_bytes()[..8]
84 .try_into()
85 .expect("should be u64"),
86 ))
87 }
88}
89
90pub fn is_mature(info: &LiveCell, max_mature_number: u64) -> bool {
91 info.tx_index > 0
93 || info.block_number == 0
95 || info.block_number <= max_mature_number
96}
97
98pub fn minimal_unlock_point(
99 deposit_header: &HeaderView,
100 prepare_header: &HeaderView,
101) -> EpochNumberWithFraction {
102 const LOCK_PERIOD_EPOCHES: EpochNumber = 180;
103
104 let deposit_point = deposit_header.epoch();
106 let prepare_point = prepare_header.epoch();
107 let prepare_fraction = prepare_point.index() * deposit_point.length();
108 let deposit_fraction = deposit_point.index() * prepare_point.length();
109 let passed_epoch_cnt = if prepare_fraction > deposit_fraction {
110 prepare_point.number() - deposit_point.number() + 1
111 } else {
112 prepare_point.number() - deposit_point.number()
113 };
114 let rest_epoch_cnt = passed_epoch_cnt.div_ceil(LOCK_PERIOD_EPOCHES) * LOCK_PERIOD_EPOCHES;
115 EpochNumberWithFraction::new(
116 deposit_point.number() + rest_epoch_cnt,
117 deposit_point.index(),
118 deposit_point.length(),
119 )
120}
121
122pub fn calculate_dao_maximum_withdraw4(
123 deposit_header: &HeaderView,
124 prepare_header: &HeaderView,
125 output: &CellOutput,
126 occupied_capacity: u64,
127) -> u64 {
128 let (deposit_ar, _, _, _) = extract_dao_data(deposit_header.dao());
129 let (prepare_ar, _, _, _) = extract_dao_data(prepare_header.dao());
130 let output_capacity: Capacity = output.capacity().unpack();
131 let counted_capacity = output_capacity.as_u64() - occupied_capacity;
132 let withdraw_counted_capacity =
133 u128::from(counted_capacity) * u128::from(prepare_ar) / u128::from(deposit_ar);
134 occupied_capacity + withdraw_counted_capacity as u64
135}
136
137pub fn serialize_signature(signature: &secp256k1::ecdsa::RecoverableSignature) -> [u8; 65] {
138 let (recov_id, data) = signature.serialize_compact();
139 let mut signature_bytes = [0u8; 65];
140 signature_bytes[0..64].copy_from_slice(&data[0..64]);
141 signature_bytes[64] = i32::from(recov_id) as u8;
142 signature_bytes
143}
144
145pub fn blake160(message: &[u8]) -> H160 {
146 let r = ckb_hash::blake2b_256(message);
147 H160::from_slice(&r[..20]).unwrap()
148}
149
150pub fn keccak160(message: &[u8]) -> H160 {
152 let mut hasher = Keccak256::new();
153 hasher.update(message);
154 let r = hasher.finalize();
155 H160::from_slice(&r[12..]).unwrap()
156}
157
158pub fn convert_keccak256_hash(message: &[u8]) -> H256 {
160 let eth_prefix: &[u8; 28] = b"\x19Ethereum Signed Message:\n32";
161 let mut hasher = Keccak256::new();
162 hasher.update(eth_prefix);
163 hasher.update(message);
164 let r = hasher.finalize();
165 H256::from_slice(&r).expect("convert_keccak256_hash")
166}
167
168#[cfg(all(test, feature = "test"))]
169mod tests {
170 use super::*;
171 use crate::test_util::MockRpcResult;
172 use ckb_chain_spec::consensus::ConsensusBuilder;
173 use ckb_dao_utils::pack_dao_data;
174 use ckb_jsonrpc_types::{Consensus, EpochView, HeaderView};
175 use ckb_types::{
176 bytes::Bytes,
177 core::{capacity_bytes, EpochNumberWithFraction, HeaderBuilder},
178 };
179 use httpmock::prelude::*;
180
181 #[test]
182 fn test_minimal_unlock_point() {
183 let cases = vec![
184 ((5, 5, 1000), (184, 4, 1000), (5 + 180, 5, 1000)),
185 ((5, 5, 1000), (184, 5, 1000), (5 + 180, 5, 1000)),
186 ((5, 5, 1000), (184, 6, 1000), (5 + 180, 5, 1000)),
187 ((5, 5, 1000), (185, 4, 1000), (5 + 180, 5, 1000)),
188 ((5, 5, 1000), (185, 5, 1000), (5 + 180, 5, 1000)),
189 ((5, 5, 1000), (185, 6, 1000), (5 + 180 * 2, 5, 1000)), ((5, 5, 1000), (186, 4, 1000), (5 + 180 * 2, 5, 1000)),
191 ((5, 5, 1000), (186, 5, 1000), (5 + 180 * 2, 5, 1000)),
192 ((5, 5, 1000), (186, 6, 1000), (5 + 180 * 2, 5, 1000)),
193 ((5, 5, 1000), (364, 4, 1000), (5 + 180 * 2, 5, 1000)),
194 ((5, 5, 1000), (364, 5, 1000), (5 + 180 * 2, 5, 1000)),
195 ((5, 5, 1000), (364, 6, 1000), (5 + 180 * 2, 5, 1000)),
196 ((5, 5, 1000), (365, 4, 1000), (5 + 180 * 2, 5, 1000)),
197 ((5, 5, 1000), (365, 5, 1000), (5 + 180 * 2, 5, 1000)),
198 ((5, 5, 1000), (365, 6, 1000), (5 + 180 * 3, 5, 1000)),
199 ((5, 5, 1000), (366, 4, 1000), (5 + 180 * 3, 5, 1000)),
200 ((5, 5, 1000), (366, 5, 1000), (5 + 180 * 3, 5, 1000)),
201 ((5, 5, 1000), (366, 6, 1000), (5 + 180 * 3, 5, 1000)),
202 ];
203 for (deposit_point, prepare_point, expected) in cases {
204 let deposit_point =
205 EpochNumberWithFraction::new(deposit_point.0, deposit_point.1, deposit_point.2);
206 let prepare_point =
207 EpochNumberWithFraction::new(prepare_point.0, prepare_point.1, prepare_point.2);
208 let expected = EpochNumberWithFraction::new(expected.0, expected.1, expected.2);
209 let deposit_header = HeaderBuilder::default()
210 .epoch(deposit_point.full_value())
211 .build();
212 let prepare_header = HeaderBuilder::default()
213 .epoch(prepare_point.full_value())
214 .build();
215 let actual = minimal_unlock_point(&deposit_header, &prepare_header);
216 assert_eq!(
217 expected, actual,
218 "minimal_unlock_point deposit_point: {}, prepare_point: {}, expected: {}, actual: {}",
219 deposit_point, prepare_point, expected, actual,
220 );
221 }
222 }
223
224 #[test]
225 fn check_withdraw_calculation() {
226 let data = Bytes::from(vec![1; 10]);
227 let output = CellOutput::new_builder()
228 .capacity(capacity_bytes!(1000000).pack())
229 .build();
230
231 let (deposit_point, prepare_point) = ((5, 5, 1000), (184, 4, 1000));
232 let deposit_number = deposit_point.0 * deposit_point.2 + deposit_point.1;
233 let prepare_number = prepare_point.0 * prepare_point.2 + prepare_point.1;
234 let deposit_point =
235 EpochNumberWithFraction::new(deposit_point.0, deposit_point.1, deposit_point.2);
236 let prepare_point =
237 EpochNumberWithFraction::new(prepare_point.0, prepare_point.1, prepare_point.2);
238 let deposit_header = HeaderBuilder::default()
239 .epoch(deposit_point.full_value())
240 .number(deposit_number)
241 .dao(pack_dao_data(
242 10_000_000_000_123_456,
243 Default::default(),
244 Default::default(),
245 Default::default(),
246 ))
247 .build();
248 let prepare_header = HeaderBuilder::default()
249 .epoch(prepare_point.full_value())
250 .number(prepare_number)
251 .dao(pack_dao_data(
252 10_000_000_001_123_456,
253 Default::default(),
254 Default::default(),
255 Default::default(),
256 ))
257 .build();
258
259 let result = calculate_dao_maximum_withdraw4(
260 &deposit_header,
261 &prepare_header,
262 &output,
263 Capacity::bytes(data.len()).unwrap().as_u64(),
264 );
265 assert_eq!(result, 100_000_000_009_999);
266 }
267
268 #[test]
269 fn test_get_max_mature_number() {
270 {
271 let server = MockServer::start();
273 let consensus: Consensus = ConsensusBuilder::default()
274 .cellbase_maturity(EpochNumberWithFraction::new(4, 0, 1))
275 .build()
276 .into();
277 server.mock(|when, then| {
278 when.method(POST).path("/").body_contains("get_consensus");
279 then.status(200)
280 .body(MockRpcResult::new(consensus).to_json());
281 });
282
283 let tip_header: HeaderView = HeaderBuilder::default()
284 .epoch(EpochNumberWithFraction::new(3, 200, 400).full_value())
285 .build()
286 .into();
287 server.mock(|when, then| {
288 when.method(POST).path("/").body_contains("get_tip_header");
289 then.status(200)
290 .body(MockRpcResult::new(tip_header).to_json());
291 });
292
293 let rpc_client = CkbRpcClient::new(server.base_url().as_str());
294 assert_eq!(0, get_max_mature_number(&rpc_client).unwrap());
295 }
296
297 {
298 let server = MockServer::start();
301 let consensus: Consensus = ConsensusBuilder::default()
302 .cellbase_maturity(EpochNumberWithFraction::new(3, 1, 3))
303 .build()
304 .into();
305 server.mock(|when, then| {
306 when.method(POST).path("/").body_contains("get_consensus");
307 then.status(200)
308 .body(MockRpcResult::new(consensus).to_json());
309 });
310
311 let tip_header: HeaderView = HeaderBuilder::default()
312 .epoch(EpochNumberWithFraction::new(3, 300, 600).full_value())
313 .build()
314 .into();
315 server.mock(|when, then| {
316 when.method(POST).path("/").body_contains("get_tip_header");
317 then.status(200)
318 .body(MockRpcResult::new(tip_header).to_json());
319 });
320
321 let epoch3: EpochView = EpochView {
322 number: 3.into(),
323 start_number: 1800.into(),
324 length: 600.into(),
325 compact_target: 0.into(),
326 };
327
328 server.mock(|when, then| {
329 when.method(POST)
330 .path("/")
331 .body_contains("get_epoch_by_number");
332 then.status(200).body(MockRpcResult::new(epoch3).to_json());
333 });
334
335 let rpc_client = CkbRpcClient::new(server.base_url().as_str());
336 assert_eq!(1900, get_max_mature_number(&rpc_client).unwrap());
337 }
338
339 {
340 let server = MockServer::start();
343 let consensus: Consensus = ConsensusBuilder::default()
344 .cellbase_maturity(EpochNumberWithFraction::new(3, 2, 3))
345 .build()
346 .into();
347 server.mock(|when, then| {
348 when.method(POST).path("/").body_contains("get_consensus");
349 then.status(200)
350 .body(MockRpcResult::new(consensus).to_json());
351 });
352
353 let tip_header: HeaderView = HeaderBuilder::default()
354 .epoch(EpochNumberWithFraction::new(105, 300, 600).full_value())
355 .build()
356 .into();
357 server.mock(|when, then| {
358 when.method(POST).path("/").body_contains("get_tip_header");
359 then.status(200)
360 .body(MockRpcResult::new(tip_header).to_json());
361 });
362
363 let epoch3: EpochView = EpochView {
364 number: 101.into(),
365 start_number: 150000.into(),
366 length: 1800.into(),
367 compact_target: 0.into(),
368 };
369
370 server.mock(|when, then| {
371 when.method(POST)
372 .path("/")
373 .body_contains("get_epoch_by_number");
374 then.status(200).body(MockRpcResult::new(epoch3).to_json());
375 });
376
377 let rpc_client = CkbRpcClient::new(server.base_url().as_str());
378 assert_eq!(151500, get_max_mature_number(&rpc_client).unwrap());
379 }
380 }
381}