Skip to content

Commit 38b7c30

Browse files
authored
Merge pull request #6230 from Jiloc/fix/v3-health-confirmed-tenures
fix: v3 health confirmed tenures
2 parents 8a1a6e8 + 27d044f commit 38b7c30

File tree

7 files changed

+377
-125
lines changed

7 files changed

+377
-125
lines changed

docs/rpc/openapi.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,19 @@ paths:
909909
tags:
910910
- Info
911911
operationId: get_health
912+
parameters:
913+
- in: query
914+
name: neighbors
915+
description: "Specifies the set of peers to use for health checks.
916+
- `initial` (default): Use only the initial bootstrap peers.
917+
- `all`: Use all connected peers."
918+
required: false
919+
schema:
920+
type: string
921+
enum:
922+
- initial
923+
- all
924+
default: initial
912925
responses:
913926
200:
914927
description: Success
@@ -918,6 +931,13 @@ paths:
918931
$ref: ./api/core-node/get-health.schema.json
919932
example:
920933
$ref: ./api/core-node/get-health.example.json
934+
400:
935+
description: Bad request, such as an invalid `neighbors` query parameter.
936+
content:
937+
application/json:
938+
schema:
939+
type: string
940+
example: "Invalid `neighbors` query parameter: allowed values are `initial` or `all`"
921941
500:
922942
description: |
923943
Failed to query for health (e.g., no data or no valid peers to query from).

stackslib/src/net/api/gethealth.rs

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17+
use std::fmt;
18+
use std::str::FromStr;
19+
1720
use regex::{Captures, Regex};
1821
use stacks_common::types::net::PeerHost;
1922
use stacks_common::types::StacksEpochId;
@@ -43,13 +46,53 @@ pub struct RPCGetHealthResponse {
4346
pub node_stacks_tip_height: u64,
4447
}
4548

49+
const NEIGHBORS_SCOPE_PARAM_NAME: &str = "neighbors";
50+
51+
#[derive(Clone, Debug, PartialEq)]
52+
pub enum NeighborsScope {
53+
Initial,
54+
All,
55+
}
56+
57+
impl FromStr for NeighborsScope {
58+
type Err = crate::net::http::Error;
59+
60+
fn from_str(s: &str) -> Result<Self, Self::Err> {
61+
match s {
62+
"initial" => Ok(NeighborsScope::Initial),
63+
"all" => Ok(NeighborsScope::All),
64+
_ => Err(crate::net::http::Error::Http(
65+
400,
66+
format!(
67+
"Invalid `neighbors` query parameter: `{}`, allowed values are `initial` or `all`",
68+
s
69+
),
70+
)),
71+
}
72+
}
73+
}
74+
75+
impl fmt::Display for NeighborsScope {
76+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77+
let s = match self {
78+
NeighborsScope::Initial => "initial",
79+
NeighborsScope::All => "all",
80+
};
81+
write!(f, "{s}")
82+
}
83+
}
84+
4685
#[derive(Clone)]
4786
/// Empty request handler for the GET /v3/health endpoint
48-
pub struct RPCGetHealthRequestHandler {}
87+
pub struct RPCGetHealthRequestHandler {
88+
neighbors_scope: Option<NeighborsScope>,
89+
}
4990

5091
impl RPCGetHealthRequestHandler {
5192
pub fn new() -> Self {
52-
Self {}
93+
Self {
94+
neighbors_scope: None,
95+
}
5396
}
5497
}
5598

@@ -82,7 +125,12 @@ impl HttpRequest for RPCGetHealthRequestHandler {
82125
));
83126
}
84127

85-
Ok(HttpRequestContents::new().query_string(query))
128+
let req_contents = HttpRequestContents::new().query_string(query);
129+
if let Some(scope) = req_contents.get_query_arg(NEIGHBORS_SCOPE_PARAM_NAME) {
130+
self.neighbors_scope = Some(scope.parse()?);
131+
}
132+
133+
Ok(req_contents)
86134
}
87135
}
88136

@@ -97,7 +145,9 @@ fn create_error_response(
97145

98146
impl RPCRequestHandler for RPCGetHealthRequestHandler {
99147
/// Reset internal state
100-
fn restart(&mut self) {}
148+
fn restart(&mut self) {
149+
self.neighbors_scope = None;
150+
}
101151

102152
/// Make the response
103153
fn try_handle_request(
@@ -106,47 +156,51 @@ impl RPCRequestHandler for RPCGetHealthRequestHandler {
106156
_contents: HttpRequestContents,
107157
node: &mut StacksNodeState,
108158
) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
159+
let neighbors_scope = self
160+
.neighbors_scope
161+
.take()
162+
.unwrap_or(NeighborsScope::Initial);
163+
let use_all_neighbors = neighbors_scope == NeighborsScope::All;
164+
109165
node.with_node_state(|network, _sortdb, _chainstate, _mempool, _rpc_args| {
110166
let current_epoch = network.get_current_epoch();
111167

112-
let initial_neighbors = PeerDB::get_valid_initial_neighbors(
113-
network.peerdb.conn(),
114-
network.local_peer.network_id,
115-
current_epoch.network_epoch,
116-
network.peer_version,
117-
network.chain_view.burn_block_height,
118-
)
119-
.map_err(NetError::from)?;
120-
121-
let node_stacks_tip_height = network.stacks_tip.height;
122-
let bitcoin_tip_height = network.chain_view.burn_block_height;
123-
let bitcoin_last_processed_height = network.burnchain_tip.block_height;
124-
// no bootstrap nodes found, unable to determine health.
125-
if initial_neighbors.len() == 0 {
126-
return StacksHttpResponse::new_error(
127-
&preamble,
128-
&HttpServerError::new(
129-
"No viable bootstrap peers found, unable to determine health".into(),
130-
),
168+
let neighbors_arg = if use_all_neighbors {
169+
None
170+
} else {
171+
let initial_neighbors = PeerDB::get_valid_initial_neighbors(
172+
network.peerdb.conn(),
173+
network.local_peer.network_id,
174+
current_epoch.network_epoch,
175+
network.peer_version,
176+
network.chain_view.burn_block_height,
131177
)
132-
.try_into_contents()
133-
.map_err(NetError::from);
134-
}
178+
.map_err(NetError::from)?;
135179

136-
let peer_max_stacks_height_opt = {
180+
if initial_neighbors.is_empty() {
181+
return create_error_response(
182+
&preamble,
183+
"No viable bootstrap peers found, unable to determine health",
184+
);
185+
}
186+
Some(initial_neighbors)
187+
};
188+
189+
let peer_max_stacks_height_opt = {
137190
if current_epoch.epoch_id < StacksEpochId::Epoch30 {
138191
// When the node enters Epoch 3.0, ibd is not accurate. In nakamoto it's always set to false.
139192
// See the implementation of `RunLoop::start` in `testnet/stacks-node/src/run_loop/nakamoto.rs`,
140193
// specifically the section and comment where `let ibd = false`, for details.
141194
let ibd = infer_initial_burnchain_block_download(
142195
&network.burnchain,
143-
bitcoin_last_processed_height,
144-
bitcoin_tip_height,
196+
network.burnchain_tip.block_height,
197+
network.chain_view.burn_block_height,
145198
);
199+
146200
// get max block height amongst bootstrap nodes
147201
match network.inv_state.as_ref() {
148202
Some(inv_state) => {
149-
inv_state.get_max_stacks_height_of_neighbors(&initial_neighbors, ibd)
203+
inv_state.get_max_stacks_height_of_neighbors(neighbors_arg.as_deref(), ibd)
150204
}
151205
None => {
152206
return create_error_response(
@@ -156,9 +210,11 @@ impl RPCRequestHandler for RPCGetHealthRequestHandler {
156210
}
157211
}
158212
} else {
159-
let initial_neighbours_addresses: Vec<NeighborAddress> = initial_neighbors.iter().map(NeighborAddress::from_neighbor).collect();
213+
let neighbors_arg: Option<Vec<NeighborAddress>> = neighbors_arg.as_ref().map(|v| v.iter().map(NeighborAddress::from_neighbor).collect());
160214
match network.block_downloader_nakamoto.as_ref() {
161-
Some(block_downloader_nakamoto) => block_downloader_nakamoto.get_max_stacks_height_of_neighbors(&initial_neighbours_addresses),
215+
Some(block_downloader_nakamoto) => {
216+
block_downloader_nakamoto.get_max_stacks_height_of_neighbors(neighbors_arg.as_deref())
217+
}
162218
None => {
163219
return create_error_response(
164220
&preamble,
@@ -169,10 +225,12 @@ impl RPCRequestHandler for RPCGetHealthRequestHandler {
169225
}
170226
};
171227

172-
match peer_max_stacks_height_opt {
228+
match peer_max_stacks_height_opt {
173229
Some(max_stacks_height_of_neighbors) => {
174230
// There could be a edge case where our node is ahead of all peers.
175-
let difference_from_max_peer = max_stacks_height_of_neighbors.saturating_sub(node_stacks_tip_height);
231+
let node_stacks_tip_height = network.stacks_tip.height;
232+
let difference_from_max_peer =
233+
max_stacks_height_of_neighbors.saturating_sub(node_stacks_tip_height);
176234

177235
let preamble = HttpResponsePreamble::ok_json(&preamble);
178236
let data = RPCGetHealthResponse {
@@ -205,13 +263,15 @@ impl HttpResponse for RPCGetHealthRequestHandler {
205263
}
206264

207265
impl StacksHttpRequest {
208-
/// Make a new get-unconfirmed-tx request
209-
pub fn new_gethealth(host: PeerHost) -> StacksHttpRequest {
266+
pub fn new_gethealth(host: PeerHost, neighbors_scope: NeighborsScope) -> StacksHttpRequest {
210267
StacksHttpRequest::new_for_peer(
211268
host,
212269
"GET".into(),
213-
format!("/v3/health"),
214-
HttpRequestContents::new(),
270+
"/v3/health".into(),
271+
HttpRequestContents::new().query_arg(
272+
NEIGHBORS_SCOPE_PARAM_NAME.into(),
273+
neighbors_scope.to_string(),
274+
),
215275
)
216276
.expect("FATAL: failed to construct request from infallible data")
217277
}

0 commit comments

Comments
 (0)