• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 25904007932-1

15 May 2026 06:31AM UTC coverage: 47.459% (-38.5%) from 85.959%
25904007932-1

Pull #7210

github

869a54
web-flow
Merge 27877974d into 1c7b8e6ac
Pull Request #7210: [wip] epoch 4 release branch

36 of 53 new or added lines in 1 file covered. (67.92%)

88645 existing lines in 346 files now uncovered.

104136 of 219422 relevant lines covered (47.46%)

12897381.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.71
/stackslib/src/net/api/liststackerdbreplicas.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use clarity::vm::representations::{CONTRACT_NAME_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING};
18
use clarity::vm::types::QualifiedContractIdentifier;
19
use regex::{Captures, Regex};
20
use serde_json;
21
use stacks_common::types::net::PeerHost;
22
use stacks_common::util::get_epoch_time_secs;
23

24
use crate::net::db::PeerDB;
25
use crate::net::http::{
26
    parse_json, Error, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse,
27
    HttpResponseContents, HttpResponsePayload, HttpResponsePreamble, HttpServerError,
28
};
29
use crate::net::httpcore::{request, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
30
use crate::net::{Error as NetError, NeighborAddress, StacksNodeState};
31

32
/// Largest number of replicas returned
33
pub const MAX_LIST_REPLICAS: usize = 64;
34

35
#[derive(Clone)]
36
pub struct RPCListStackerDBReplicasRequestHandler {
37
    pub contract_identifier: Option<QualifiedContractIdentifier>,
38
}
39

40
impl RPCListStackerDBReplicasRequestHandler {
41
    pub fn new() -> Self {
1,079,714✔
42
        Self {
1,079,714✔
43
            contract_identifier: None,
1,079,714✔
44
        }
1,079,714✔
45
    }
1,079,714✔
46
}
47

48
/// Decode the HTTP request
49
impl HttpRequest for RPCListStackerDBReplicasRequestHandler {
50
    fn verb(&self) -> &'static str {
1,079,714✔
51
        "GET"
1,079,714✔
52
    }
1,079,714✔
53

54
    fn path_regex(&self) -> Regex {
2,159,428✔
55
        Regex::new(&format!(
2,159,428✔
56
            r#"^/v2/stackerdb/(?P<address>{})/(?P<contract>{})/replicas$"#,
2,159,428✔
57
            *STANDARD_PRINCIPAL_REGEX_STRING, *CONTRACT_NAME_REGEX_STRING
2,159,428✔
58
        ))
2,159,428✔
59
        .unwrap()
2,159,428✔
60
    }
2,159,428✔
61

UNCOV
62
    fn metrics_identifier(&self) -> &str {
×
UNCOV
63
        "/v2/stackerdb/:principal/:contract_name/replicas"
×
UNCOV
64
    }
×
65

66
    /// Try to decode this request.
67
    /// There's nothing to load here, so just make sure the request is well-formed.
UNCOV
68
    fn try_parse_request(
×
UNCOV
69
        &mut self,
×
UNCOV
70
        preamble: &HttpRequestPreamble,
×
UNCOV
71
        captures: &Captures,
×
UNCOV
72
        query: Option<&str>,
×
UNCOV
73
        _body: &[u8],
×
UNCOV
74
    ) -> Result<HttpRequestContents, Error> {
×
UNCOV
75
        if preamble.get_content_length() != 0 {
×
76
            return Err(Error::DecodeError(
×
77
                "Invalid Http request: expected 0-length body".to_string(),
×
78
            ));
×
UNCOV
79
        }
×
80

UNCOV
81
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
×
UNCOV
82
        self.contract_identifier = Some(contract_identifier);
×
83

UNCOV
84
        Ok(HttpRequestContents::new().query_string(query))
×
UNCOV
85
    }
×
86
}
87

88
impl RPCRequestHandler for RPCListStackerDBReplicasRequestHandler {
89
    /// Reset internal state
UNCOV
90
    fn restart(&mut self) {
×
UNCOV
91
        self.contract_identifier = None;
×
UNCOV
92
    }
×
93

94
    /// Make the response
UNCOV
95
    fn try_handle_request(
×
UNCOV
96
        &mut self,
×
UNCOV
97
        preamble: HttpRequestPreamble,
×
UNCOV
98
        _contents: HttpRequestContents,
×
UNCOV
99
        node: &mut StacksNodeState,
×
UNCOV
100
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
×
UNCOV
101
        let contract_identifier = self
×
UNCOV
102
            .contract_identifier
×
UNCOV
103
            .take()
×
UNCOV
104
            .ok_or(NetError::SendError("`contract_identifier` not set".into()))?;
×
105

UNCOV
106
        let (replicas_resp, local_peer, allow_private) =
×
UNCOV
107
            node.with_node_state(|network, _sortdb, _chainstate, _mempool, _rpc_args| {
×
UNCOV
108
                let replicas_resp = PeerDB::find_stacker_db_replicas(
×
UNCOV
109
                    network.peerdb_conn(),
×
UNCOV
110
                    network.bound_neighbor_key().network_id,
×
UNCOV
111
                    &contract_identifier,
×
UNCOV
112
                    get_epoch_time_secs().saturating_sub(network.get_connection_opts().max_neighbor_age),
×
113
                    MAX_LIST_REPLICAS
114
                )
UNCOV
115
                .map_err(|e| {
×
116
                    warn!("Failed to find stackerdb replicas"; "contract_id" => %contract_identifier, "error" => %e);
×
117
                    StacksHttpResponse::new_error(
×
118
                        &preamble,
×
119
                        &HttpServerError::new("Unable to list replicas of StackerDB".to_string())
×
120
                    )
121
                });
×
UNCOV
122
                let local_peer_resp = network.get_local_peer().clone();
×
UNCOV
123
                (replicas_resp, local_peer_resp, network.get_connection_opts().private_neighbors)
×
UNCOV
124
            });
×
125

UNCOV
126
        let mut naddrs = match replicas_resp {
×
UNCOV
127
            Ok(neighbors) => neighbors
×
UNCOV
128
                .into_iter()
×
UNCOV
129
                .map(|neighbor| NeighborAddress::from_neighbor(&neighbor))
×
UNCOV
130
                .filter(|naddr| {
×
UNCOV
131
                    if naddr.addrbytes.is_anynet() {
×
132
                        // don't expose 0.0.0.0 or ::1
133
                        return false;
×
UNCOV
134
                    }
×
UNCOV
135
                    if !allow_private && naddr.addrbytes.is_in_private_range() {
×
136
                        // filter unroutable network addresses
137
                        return false;
×
UNCOV
138
                    }
×
UNCOV
139
                    true
×
UNCOV
140
                })
×
UNCOV
141
                .collect::<Vec<_>>(),
×
142
            Err(response) => {
×
143
                return response.try_into_contents().map_err(NetError::from);
×
144
            }
145
        };
146

UNCOV
147
        if local_peer
×
UNCOV
148
            .stacker_dbs
×
UNCOV
149
            .iter()
×
UNCOV
150
            .find(|contract_id| contract_id == &&contract_identifier)
×
UNCOV
151
            .is_some()
×
UNCOV
152
        {
×
UNCOV
153
            naddrs.insert(0, local_peer.to_public_neighbor_addr());
×
UNCOV
154
        }
×
155

UNCOV
156
        let preamble = HttpResponsePreamble::ok_json(&preamble);
×
UNCOV
157
        let body = HttpResponseContents::try_from_json(&naddrs)?;
×
UNCOV
158
        Ok((preamble, body))
×
UNCOV
159
    }
×
160
}
161

162
/// Decode the HTTP response
163
impl HttpResponse for RPCListStackerDBReplicasRequestHandler {
164
    /// Decode this response from a byte stream.  This is called by the client to decode this
165
    /// message
UNCOV
166
    fn try_parse_response(
×
UNCOV
167
        &self,
×
UNCOV
168
        preamble: &HttpResponsePreamble,
×
UNCOV
169
        body: &[u8],
×
UNCOV
170
    ) -> Result<HttpResponsePayload, Error> {
×
UNCOV
171
        let metadata: Vec<NeighborAddress> = parse_json(preamble, body)?;
×
UNCOV
172
        Ok(HttpResponsePayload::try_from_json(metadata)?)
×
UNCOV
173
    }
×
174
}
175

176
impl StacksHttpRequest {
UNCOV
177
    pub fn new_list_stackerdb_replicas(
×
UNCOV
178
        host: PeerHost,
×
UNCOV
179
        stackerdb_contract_id: QualifiedContractIdentifier,
×
UNCOV
180
    ) -> StacksHttpRequest {
×
UNCOV
181
        StacksHttpRequest::new_for_peer(
×
UNCOV
182
            host,
×
UNCOV
183
            "GET".into(),
×
UNCOV
184
            format!(
×
185
                "/v2/stackerdb/{}/{}/replicas",
UNCOV
186
                &stackerdb_contract_id.issuer, &stackerdb_contract_id.name
×
187
            ),
UNCOV
188
            HttpRequestContents::new(),
×
189
        )
UNCOV
190
        .expect("FATAL: failed to construct request from infallible data")
×
UNCOV
191
    }
×
192
}
193

194
impl StacksHttpResponse {
195
    /// Decode an HTTP response into a list of replicas
196
    /// If it fails, return Self::Error(..)
UNCOV
197
    pub fn decode_stackerdb_replicas(self) -> Result<Vec<NeighborAddress>, NetError> {
×
UNCOV
198
        let contents = self.get_http_payload_ok()?;
×
UNCOV
199
        let contents_json: serde_json::Value = contents.try_into()?;
×
UNCOV
200
        let resp: Vec<NeighborAddress> = serde_json::from_value(contents_json)
×
UNCOV
201
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
×
UNCOV
202
        Ok(resp)
×
UNCOV
203
    }
×
204
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc