-
Notifications
You must be signed in to change notification settings - Fork 168
Exposed getblocktemplate rpc via REST endpoint. #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: new-index
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ use crate::chain::{ | |
| }; | ||
| use crate::config::Config; | ||
| use crate::errors; | ||
| use crate::new_index::{compute_script_hash, Query, SpendingInput, Utxo}; | ||
| use crate::new_index::{compute_script_hash, Query, SpendingInput, Utxo, GETBLOCKTEMPLATE_TTL}; | ||
| #[cfg(feature = "liquid")] | ||
| use crate::util::optional_value_for_newer_blocks; | ||
| use crate::util::{ | ||
|
|
@@ -38,6 +38,7 @@ use { | |
| }; | ||
|
|
||
| use serde::Serialize; | ||
| use serde_json::Value as JsonValue; | ||
| use std::collections::HashMap; | ||
| use std::num::ParseIntError; | ||
| use std::os::unix::fs::FileTypeExt; | ||
|
|
@@ -1150,6 +1151,15 @@ fn handle_request( | |
| json_response(query.estimate_fee_map(), TTL_SHORT) | ||
| } | ||
|
|
||
| (&Method::GET, Some(&"block-template"), None, None, None, None) => { | ||
| if !config.enable_mining_rest { | ||
| return Err(HttpError::forbidden( | ||
| "mining REST endpoints are disabled".to_string(), | ||
| )); | ||
| } | ||
| getblocktemplate_response(query.getblocktemplate()) | ||
| } | ||
|
|
||
| #[cfg(feature = "liquid")] | ||
| (&Method::GET, Some(&"assets"), Some(&"registry"), None, None, None) => { | ||
| let start_index: usize = query_params | ||
|
|
@@ -1291,14 +1301,58 @@ where | |
| } | ||
|
|
||
| fn json_response<T: Serialize>(value: T, ttl: u32) -> Result<Response<Full<Bytes>>, HttpError> { | ||
| json_response_with_status(value, StatusCode::OK, ttl) | ||
| } | ||
|
|
||
| fn json_response_with_status<T: Serialize>( | ||
| value: T, | ||
| status: StatusCode, | ||
| ttl: u32, | ||
| ) -> Result<Response<Full<Bytes>>, HttpError> { | ||
| let value = serde_json::to_string(&value)?; | ||
| Ok(Response::builder() | ||
| .status(status) | ||
| .header("Content-Type", "application/json") | ||
| .header("Cache-Control", format!("public, max-age={:}", ttl)) | ||
| .body(Full::new(Bytes::from(value))) | ||
| .unwrap()) | ||
| } | ||
|
|
||
| fn getblocktemplate_response( | ||
| result: errors::Result<JsonValue>, | ||
| ) -> Result<Response<Full<Bytes>>, HttpError> { | ||
| match result { | ||
| Ok(template) => json_response(template, GETBLOCKTEMPLATE_TTL as u32), | ||
| Err(err) => { | ||
| if let Some((code, message)) = getblocktemplate_rpc_error(&err) { | ||
| return json_response_with_status( | ||
| json!({ "error": { "code": code, "message": message } }), | ||
| StatusCode::BAD_GATEWAY, | ||
| 0, | ||
| ); | ||
| } | ||
| Err(HttpError::from(err)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn getblocktemplate_rpc_error(err: &errors::Error) -> Option<(i64, String)> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codex review finding: The Connection error handling here probably will not behave as intended. For example, bitcoind warmup error -28 is converted into ErrorKind::Connection, which means /getblocktemplate would keep retrying instead of returning the intended 502 JSON response. One related concern: Query::getblocktemplate() holds the block template cache mutex while making the daemon RPC call. If that RPC gets stuck retrying, later /getblocktemplate requests will block behind the same mutex. |
||
| match err.kind() { | ||
| errors::ErrorKind::RpcError(code, message, method) if method == "getblocktemplate" => { | ||
| Some((*code, message.clone())) | ||
| } | ||
| errors::ErrorKind::Connection(message) => parse_rpc_error_json(message), | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| fn parse_rpc_error_json(message: &str) -> Option<(i64, String)> { | ||
| let value: JsonValue = serde_json::from_str(message).ok()?; | ||
| let code = value.get("code")?.as_i64()?; | ||
| let message = value.get("message")?.as_str()?.to_string(); | ||
| Some((code, message)) | ||
| } | ||
|
|
||
| #[trace] | ||
| fn blocks(query: &Query, start_height: Option<usize>) -> Result<Response<Full<Bytes>>, HttpError> { | ||
| let mut values = Vec::new(); | ||
|
|
@@ -1381,6 +1435,10 @@ impl HttpError { | |
| fn not_found(msg: String) -> Self { | ||
| HttpError(StatusCode::NOT_FOUND, msg) | ||
| } | ||
|
|
||
| fn forbidden(msg: String) -> Self { | ||
| HttpError(StatusCode::FORBIDDEN, msg) | ||
| } | ||
| } | ||
|
|
||
| impl From<String> for HttpError { | ||
|
|
@@ -1455,7 +1513,7 @@ impl From<address::AddressError> for HttpError { | |
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::rest::HttpError; | ||
| use crate::{errors, rest::HttpError}; | ||
| use serde_json::Value; | ||
| use std::collections::HashMap; | ||
|
|
||
|
|
@@ -1520,4 +1578,35 @@ mod tests { | |
|
|
||
| assert!(err.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_rpc_error_json() { | ||
| assert_eq!( | ||
| super::parse_rpc_error_json(r#"{"code":-28,"message":"warming up"}"#), | ||
| Some((-28, "warming up".to_string())) | ||
| ); | ||
| assert_eq!(super::parse_rpc_error_json("not json"), None); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_getblocktemplate_rpc_error() { | ||
| let err: errors::Error = errors::ErrorKind::RpcError( | ||
| -8, | ||
| "getblocktemplate must be called with the segwit rule set".to_string(), | ||
| "getblocktemplate".to_string(), | ||
| ) | ||
| .into(); | ||
| assert_eq!( | ||
| super::getblocktemplate_rpc_error(&err), | ||
| Some(( | ||
| -8, | ||
| "getblocktemplate must be called with the segwit rule set".to_string() | ||
| )) | ||
| ); | ||
|
|
||
| let other_method: errors::Error = | ||
| errors::ErrorKind::RpcError(-5, "Block not found".to_string(), "getblock".to_string()) | ||
| .into(); | ||
| assert_eq!(super::getblocktemplate_rpc_error(&other_method), None); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably want to document that the endpoint is for block explorers and not mining work as the 15 second staleness could be an issue there.