Skip to content

Commit d6b8816

Browse files
authored
fix: improve error handling, and more! (#524)
This PR introduces a few fixes in an effort to improve reliability and debugging problems when running Chainhook as a service: - Revisits log levels throughout the tool (fixes #498, fixes #521). The general approach for the logs were: - `crit` - fatal errors that will crash mission critical component of Chainhook. In these cases, Chainhook should automatically kill all main threads (not individual scanning threads, which is tracked by #404) to crash the service. - `erro` - something went wrong the could lead to a critical error, or that could impact all users - `warn` - something went wrong that could impact an end user (usually due to user error) - `info` - control flow logging and updates on the state of _all_ registered predicates - `debug` - updates on the state of _a_ predicate - Crash the service if a mission critical thread fails (see #517 (comment) for a list of these threads). Previously, if one of these threads failed, the remaining services would keep running. For example, if the event observer handler crashed, the event observer API would keep running. This means that the stacks node is successfully emitting blocks that Chainhook is acknowledging but not ingesting. This causes gaps in our database Fixes #517 - Removes an infinite loop with bitcoin ingestion, crashing the service instead: Fixes #506 - Fixes how we delete predicates from our db when one is deregistered. This should reduce the number of logs we have on startup. Fixes #510 - Warns on all reorgs. Fixes #519
1 parent a2865b7 commit d6b8816

File tree

19 files changed

+461
-246
lines changed

19 files changed

+461
-246
lines changed

components/chainhook-cli/src/archive/mod.rs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub async fn download_tsv_file(config: &Config) -> Result<(), String> {
2121
println!("{}", e.to_string());
2222
});
2323

24-
let remote_sha_url = config.expected_remote_stacks_tsv_sha256();
24+
let remote_sha_url = config.expected_remote_stacks_tsv_sha256()?;
2525
let res = reqwest::get(&remote_sha_url)
2626
.await
2727
.or(Err(format!("Failed to GET from '{}'", &remote_sha_url)))?
@@ -34,7 +34,7 @@ pub async fn download_tsv_file(config: &Config) -> Result<(), String> {
3434

3535
write_file_content_at_path(&local_sha_file_path, &res.to_vec())?;
3636

37-
let file_url = config.expected_remote_stacks_tsv_url();
37+
let file_url = config.expected_remote_stacks_tsv_url()?;
3838
let res = reqwest::get(&file_url)
3939
.await
4040
.or(Err(format!("Failed to GET from '{}'", &file_url)))?;
@@ -55,14 +55,17 @@ pub async fn download_tsv_file(config: &Config) -> Result<(), String> {
5555
Ok(0) => break,
5656
Ok(n) => {
5757
if let Err(e) = file.write_all(&buffer[..n]) {
58-
let err =
59-
format!("unable to update compressed archive: {}", e.to_string());
60-
return Err(err);
58+
return Err(format!(
59+
"unable to update compressed archive: {}",
60+
e.to_string()
61+
));
6162
}
6263
}
6364
Err(e) => {
64-
let err = format!("unable to write compressed archive: {}", e.to_string());
65-
return Err(err);
65+
return Err(format!(
66+
"unable to write compressed archive: {}",
67+
e.to_string()
68+
));
6669
}
6770
}
6871
}
@@ -83,12 +86,11 @@ pub async fn download_tsv_file(config: &Config) -> Result<(), String> {
8386
.map_err(|e| format!("unable to download stacks archive: {}", e.to_string()))?;
8487
}
8588
drop(tx);
86-
8789
tokio::task::spawn_blocking(|| decoder_thread.join())
8890
.await
89-
.unwrap()
90-
.unwrap()
91-
.unwrap();
91+
.map_err(|e| format!("failed to spawn thread: {e}"))?
92+
.map_err(|e| format!("decoder thread failed when downloading tsv: {:?}", e))?
93+
.map_err(|e| format!("failed to download tsv: {}", e))?;
9294
}
9395

9496
Ok(())
@@ -124,11 +126,14 @@ impl Read for ChannelRead {
124126
}
125127
}
126128

127-
pub async fn download_stacks_dataset_if_required(config: &mut Config, ctx: &Context) -> bool {
129+
pub async fn download_stacks_dataset_if_required(
130+
config: &mut Config,
131+
ctx: &Context,
132+
) -> Result<bool, String> {
128133
if config.is_initial_ingestion_required() {
129134
// Download default tsv.
130135
if config.rely_on_remote_stacks_tsv() && config.should_download_remote_stacks_tsv() {
131-
let url = config.expected_remote_stacks_tsv_url();
136+
let url = config.expected_remote_stacks_tsv_url()?;
132137
let mut tsv_file_path = config.expected_cache_path();
133138
tsv_file_path.push(default_tsv_file_path(&config.network.stacks_network));
134139
let mut tsv_sha_file_path = config.expected_cache_path();
@@ -137,7 +142,7 @@ pub async fn download_stacks_dataset_if_required(config: &mut Config, ctx: &Cont
137142
// Download archive if not already present in cache
138143
// Load the local
139144
let local_sha_file = read_file_content_at_path(&tsv_sha_file_path);
140-
let sha_url = config.expected_remote_stacks_tsv_sha256();
145+
let sha_url = config.expected_remote_stacks_tsv_sha256()?;
141146

142147
let remote_sha_file = match reqwest::get(&sha_url).await {
143148
Ok(response) => response.bytes().await,
@@ -164,28 +169,25 @@ pub async fn download_stacks_dataset_if_required(config: &mut Config, ctx: &Cont
164169
"Stacks archive file already up to date"
165170
);
166171
config.add_local_stacks_tsv_source(&tsv_file_path);
167-
return false;
172+
return Ok(false);
168173
}
169174

170175
info!(ctx.expect_logger(), "Downloading {}", url);
171176
match download_tsv_file(&config).await {
172177
Ok(_) => {}
173-
Err(e) => {
174-
error!(ctx.expect_logger(), "{}", e);
175-
std::process::exit(1);
176-
}
178+
Err(e) => return Err(e),
177179
}
178180
info!(ctx.expect_logger(), "Successfully downloaded tsv file");
179181
config.add_local_stacks_tsv_source(&tsv_file_path);
180182
}
181-
true
183+
Ok(true)
182184
} else {
183185
info!(
184186
ctx.expect_logger(),
185187
"Streaming blocks from stacks-node {}",
186188
config.network.get_stacks_node_config().rpc_url
187189
);
188-
false
190+
Ok(false)
189191
}
190192
}
191193

components/chainhook-cli/src/archive/tests/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,14 @@ async fn it_downloads_stacks_dataset_if_required() {
7272
tracer: false,
7373
};
7474
let mut config_clone = config.clone();
75-
assert!(download_stacks_dataset_if_required(&mut config, &ctx).await);
76-
assert!(!download_stacks_dataset_if_required(&mut config_clone, &ctx).await);
75+
assert!(download_stacks_dataset_if_required(&mut config, &ctx)
76+
.await
77+
.unwrap());
78+
assert!(
79+
!download_stacks_dataset_if_required(&mut config_clone, &ctx)
80+
.await
81+
.unwrap()
82+
);
7783

7884
let mut tsv_file_path = config.expected_cache_path();
7985
tsv_file_path.push(default_tsv_file_path(&config.network.stacks_network));

components/chainhook-cli/src/cli/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,14 @@ pub fn main() {
277277
let opts: Opts = match Opts::try_parse() {
278278
Ok(opts) => opts,
279279
Err(e) => {
280-
error!(ctx.expect_logger(), "{e}");
280+
crit!(ctx.expect_logger(), "{e}");
281281
process::exit(1);
282282
}
283283
};
284284

285285
match hiro_system_kit::nestable_block_on(handle_command(opts, ctx.clone())) {
286286
Err(e) => {
287-
error!(ctx.expect_logger(), "{e}");
287+
crit!(ctx.expect_logger(), "{e}");
288288
process::exit(1);
289289
}
290290
Ok(_) => {}

components/chainhook-cli/src/config/mod.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,13 @@ impl Config {
256256
}
257257
}
258258

259-
pub fn expected_local_stacks_tsv_file(&self) -> &PathBuf {
259+
pub fn expected_local_stacks_tsv_file(&self) -> Result<&PathBuf, String> {
260260
for source in self.event_sources.iter() {
261261
if let EventSourceConfig::StacksTsvPath(config) = source {
262-
return &config.file_path;
262+
return Ok(&config.file_path);
263263
}
264264
}
265-
panic!("expected local-tsv source")
265+
Err("could not find expected local tsv source")?
266266
}
267267

268268
pub fn expected_cache_path(&self) -> PathBuf {
@@ -271,21 +271,23 @@ impl Config {
271271
destination_path
272272
}
273273

274-
fn expected_remote_stacks_tsv_base_url(&self) -> &String {
274+
fn expected_remote_stacks_tsv_base_url(&self) -> Result<&String, String> {
275275
for source in self.event_sources.iter() {
276276
if let EventSourceConfig::StacksTsvUrl(config) = source {
277-
return &config.file_url;
277+
return Ok(&config.file_url);
278278
}
279279
}
280-
panic!("expected remote-tsv source")
280+
Err("could not find expected remote tsv source")?
281281
}
282282

283-
pub fn expected_remote_stacks_tsv_sha256(&self) -> String {
284-
format!("{}.sha256", self.expected_remote_stacks_tsv_base_url())
283+
pub fn expected_remote_stacks_tsv_sha256(&self) -> Result<String, String> {
284+
self.expected_remote_stacks_tsv_base_url()
285+
.map(|url| format!("{}.sha256", url))
285286
}
286287

287-
pub fn expected_remote_stacks_tsv_url(&self) -> String {
288-
format!("{}.gz", self.expected_remote_stacks_tsv_base_url())
288+
pub fn expected_remote_stacks_tsv_url(&self) -> Result<String, String> {
289+
self.expected_remote_stacks_tsv_base_url()
290+
.map(|url| format!("{}.gz", url))
289291
}
290292

291293
pub fn rely_on_remote_stacks_tsv(&self) -> bool {

components/chainhook-cli/src/config/tests/mod.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,34 +108,48 @@ fn should_download_remote_stacks_tsv_handles_both_modes() {
108108
}
109109

110110
#[test]
111-
#[should_panic(expected = "expected remote-tsv source")]
112111
fn expected_remote_stacks_tsv_base_url_panics_if_missing() {
113112
let url_src = EventSourceConfig::StacksTsvUrl(super::UrlConfig {
114113
file_url: format!("test"),
115114
});
116115
let mut config = Config::default(true, false, false, &None).unwrap();
117116

118117
config.event_sources = vec![url_src.clone()];
119-
assert_eq!(config.expected_remote_stacks_tsv_base_url(), "test");
118+
match config.expected_remote_stacks_tsv_base_url() {
119+
Ok(tsv_url) => assert_eq!(tsv_url, "test"),
120+
Err(e) => {
121+
panic!("expected tsv file: {e}")
122+
}
123+
}
120124

121125
config.event_sources = vec![];
122-
config.expected_remote_stacks_tsv_base_url();
126+
match config.expected_remote_stacks_tsv_base_url() {
127+
Ok(tsv_url) => panic!("expected no tsv file, found {}", tsv_url),
128+
Err(e) => assert_eq!(e, "could not find expected remote tsv source".to_string()),
129+
};
123130
}
124131

125132
#[test]
126-
#[should_panic(expected = "expected local-tsv source")]
127-
fn expected_local_stacks_tsv_base_url_panics_if_missing() {
133+
fn expected_local_stacks_tsv_base_url_errors_if_missing() {
128134
let path = PathBuf::from("test");
129135
let path_src = EventSourceConfig::StacksTsvPath(PathConfig {
130136
file_path: path.clone(),
131137
});
132138
let mut config = Config::default(true, false, false, &None).unwrap();
133139

134140
config.event_sources = vec![path_src.clone()];
135-
assert_eq!(config.expected_local_stacks_tsv_file(), &path);
141+
match config.expected_local_stacks_tsv_file() {
142+
Ok(tsv_path) => assert_eq!(tsv_path, &path),
143+
Err(e) => {
144+
panic!("expected tsv file: {e}")
145+
}
146+
}
136147

137148
config.event_sources = vec![];
138-
config.expected_local_stacks_tsv_file();
149+
match config.expected_local_stacks_tsv_file() {
150+
Ok(tsv_path) => panic!("expected no tsv file, found {}", tsv_path.to_string_lossy()),
151+
Err(e) => assert_eq!(e, "could not find expected local tsv source".to_string()),
152+
};
139153
}
140154

141155
#[test]

components/chainhook-cli/src/scan/bitcoin.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate(
2929
config: &Config,
3030
ctx: &Context,
3131
) -> Result<bool, String> {
32+
let predicate_uuid = &predicate_spec.uuid;
3233
let auth = Auth::UserPass(
3334
config.network.bitcoind_rpc_username.clone(),
3435
config.network.bitcoind_rpc_password.clone(),
@@ -71,9 +72,9 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate(
7172
PredicatesApi::Off => None,
7273
};
7374

74-
info!(
75+
debug!(
7576
ctx.expect_logger(),
76-
"Starting predicate evaluation on Bitcoin blocks",
77+
"Starting predicate evaluation on Bitcoin blocks for predicate {predicate_uuid}",
7778
);
7879

7980
let mut last_block_scanned = BlockIdentifier::default();
@@ -200,7 +201,7 @@ pub async fn scan_bitcoin_chainstate_via_rpc_using_predicate(
200201

201202
info!(
202203
ctx.expect_logger(),
203-
"{number_of_blocks_scanned} blocks scanned, {actions_triggered} actions triggered"
204+
"Predicate {predicate_uuid} scan completed. {number_of_blocks_scanned} blocks scanned, {actions_triggered} actions triggered."
204205
);
205206

206207
if let Some(ref mut predicates_db_conn) = predicates_db_conn {
@@ -269,9 +270,13 @@ pub async fn execute_predicates_action<'a>(
269270
if trigger.chainhook.include_proof {
270271
gather_proofs(&trigger, &mut proofs, &config, &ctx);
271272
}
273+
let predicate_uuid = &trigger.chainhook.uuid;
272274
match handle_bitcoin_hook_action(trigger, &proofs) {
273275
Err(e) => {
274-
error!(ctx.expect_logger(), "unable to handle action {}", e);
276+
warn!(
277+
ctx.expect_logger(),
278+
"unable to handle action for predicate {}: {}", predicate_uuid, e
279+
);
275280
}
276281
Ok(action) => {
277282
actions_triggered += 1;

0 commit comments

Comments
 (0)