-
Notifications
You must be signed in to change notification settings - Fork 4
/
service.rs
195 lines (173 loc) · 7.69 KB
/
service.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
use std::{format, fs::File, io::Read as _, mem};
use bitcoin::consensus::serialize;
use ckb_bitcoin_spv_verifier::types::{core, packed, prelude::*};
use crate::{tests, utilities, BlockProofGenerator, DummyService};
fn test_spv_client(
case_headers: &str,
case_txoutproofs: &str,
case_blocks: &str,
verify_tx_range: (u32, u32),
) {
tests::setup();
let headers_path = format!("main-chain/headers/continuous/{case_headers}");
let txoutproofs_path = format!("main-chain/txoutproof/{case_txoutproofs}");
let blocks_path = format!("main-chain/blocks/continuous/{case_blocks}");
// Bootstrap
let mut header_bins_iter = tests::data::find_bin_files(&headers_path, "").into_iter();
let mut service = {
let header_bin = header_bins_iter.next().unwrap();
let header: core::Header = utilities::decode_from_bin_file(&header_bin).unwrap();
let height: u32 = header_bin
.file_stem()
.unwrap()
.to_str()
.unwrap()
.parse()
.unwrap();
log::trace!("process header-{height} from file {}", header_bin.display());
let bootstrap = packed::SpvBootstrap::new_builder()
.height(height.pack())
.header(header.pack())
.build();
let expected_client = bootstrap
.initialize_spv_client()
.map_err(|err| err as i8)
.unwrap()
.pack();
let service = DummyService::bootstrap(height, header).unwrap();
let actual_client: packed::SpvClient = service.tip_client().pack();
assert_eq!(expected_client.as_slice(), actual_client.as_slice());
service
};
// Update
let mut old_client: packed::SpvClient = service.tip_client().pack();
let mut headers = Vec::new();
let mut headers_group_size = 1;
for header_bin in header_bins_iter {
let header: core::Header = utilities::decode_from_bin_file(&header_bin).unwrap();
let height: u32 = header_bin
.file_stem()
.unwrap()
.to_str()
.unwrap()
.parse()
.unwrap();
log::trace!("process header-{height} from file {}", header_bin.display());
headers.push(header);
if height + 1 != verify_tx_range.0 && headers.len() < headers_group_size {
continue;
}
log::trace!("process {} headers at one time", headers.len());
let update = if headers_group_size % 5 == 0 {
let tmp_headers = mem::take(&mut headers);
let _update = service.update(tmp_headers.clone()).unwrap();
log::trace!("rollback to previous client (for test): {old_client}");
service.rollback_to(old_client.unpack()).unwrap();
log::trace!("process {} headers again", headers.len());
service.update(tmp_headers)
} else {
service.update(mem::take(&mut headers))
}
.unwrap();
if verify_tx_range.0 <= height + 1 && height <= verify_tx_range.1 {
headers_group_size = 1;
} else {
headers_group_size += 1;
}
let new_client: packed::SpvClient = service.tip_client().pack();
old_client
.verify_new_client(&new_client, update)
.map_err(|err| err as i8)
.unwrap();
old_client = new_client;
// Verify Tx in different heights
if verify_tx_range.0 <= height && height <= verify_tx_range.1 {
let tip_client: packed::SpvClient = service.tip_client().pack();
let max_height = service.max_height();
for bin_file in tests::data::find_bin_files(&txoutproofs_path, "") {
log::trace!("process txoutproof from file {}", bin_file.display());
let actual = File::open(&bin_file)
.and_then(|mut file| {
let mut data = Vec::new();
file.read_to_end(&mut data).map(|_| data)
})
.unwrap();
let _: core::MerkleBlock =
utilities::decode_from_slice(&actual).expect("check binary data");
let file_stem = bin_file.file_stem().unwrap().to_str().unwrap();
let (height, tx_index) =
if let Some((height_str, indexes_str)) = file_stem.split_once('-') {
let height: u32 = height_str.parse().unwrap();
let indexes = indexes_str
.split('_')
.filter(|s| !s.is_empty())
.map(|s| {
s.parse()
.map_err(|err| format!("failed to parse \"{s}\" since {err}"))
})
.collect::<Result<Vec<u32>, _>>()
.unwrap();
if indexes.len() > 1 {
log::warn!("TODO with current APIs, only ONE tx is allowed each time");
continue;
}
(height, indexes[0])
} else {
panic!("invalid txoutproof file stem \"{file_stem}\"");
};
let header_proof = service
.generate_header_proof(height)
.unwrap()
.unwrap_or_default();
let tx_proof = packed::TransactionProof::new_builder()
.tx_index(tx_index.pack())
.height(height.pack())
.transaction_proof(core::Bytes::from(actual).pack())
.header_proof(header_proof.pack())
.build();
let block_filename = format!("{height:07}.bin");
let block_file = tests::data::find_bin_file(&blocks_path, &block_filename);
let bpg = BlockProofGenerator::from_bin_file(&block_file).unwrap();
let tx = bpg.get_transaction(tx_index as usize).unwrap();
let txid = tx.txid();
let tx_bytes = serialize(tx);
log::debug!("client-tip {max_height}, tx-height {height}, no confirmations");
let verify_result = tip_client
.verify_transaction_data(&tx_bytes, tx_proof.as_reader(), 0)
.map_err(|err| err as i8);
if height <= max_height {
assert!(verify_result.is_ok());
} else {
assert!(verify_result.is_err());
}
if height + 2 > max_height {
continue;
}
let confirmations = max_height - height;
log::debug!(">>> with confirmations {confirmations}");
let txid_array = txid.as_ref();
let verify_result = tip_client
.verify_transaction(txid_array, tx_proof.as_reader(), confirmations - 1)
.map_err(|err| err as i8);
assert!(verify_result.is_ok());
let verify_result = tip_client
.verify_transaction(txid_array, tx_proof.as_reader(), confirmations)
.map_err(|err| err as i8);
assert!(verify_result.is_ok());
let verify_result = tip_client
.verify_transaction(txid_array, tx_proof.as_reader(), confirmations + 1)
.map_err(|err| err as i8);
assert!(verify_result.is_err());
}
}
}
}
#[test]
fn spv_client_case_1() {
test_spv_client(
"case-0822528_0830592",
"case-0830000",
"case-0830000_0830000",
(829995, 830005),
);
}