• 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

62.22
/stackslib/src/net/api/getstackerdbchunk.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2023 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 libstackerdb::STACKERDB_MAX_CHUNK_SIZE;
20
use regex::{Captures, Regex};
21
use stacks_common::types::net::PeerHost;
22

23
use crate::net::http::{
24
    parse_bytes, Error, HttpContentType, HttpNotFound, HttpRequest, HttpRequestContents,
25
    HttpRequestPreamble, HttpResponse, HttpResponseContents, HttpResponsePayload,
26
    HttpResponsePreamble, HttpServerError,
27
};
28
use crate::net::httpcore::{request, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
29
use crate::net::{Error as NetError, StacksNodeState};
30

31
#[derive(Clone)]
32
pub struct RPCGetStackerDBChunkRequestHandler {
33
    pub contract_identifier: Option<QualifiedContractIdentifier>,
34
    pub slot_id: Option<u32>,
35
    pub slot_version: Option<u32>,
36
}
37
impl RPCGetStackerDBChunkRequestHandler {
38
    pub fn new() -> Self {
1,079,714✔
39
        Self {
1,079,714✔
40
            contract_identifier: None,
1,079,714✔
41
            slot_id: None,
1,079,714✔
42
            slot_version: None,
1,079,714✔
43
        }
1,079,714✔
44
    }
1,079,714✔
45
}
46

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

53
    fn path_regex(&self) -> Regex {
2,159,428✔
54
        Regex::new(&format!(
2,159,428✔
55
            r#"^/v2/stackerdb/(?P<address>{})/(?P<contract>{})/(?P<slot_id>[0-9]+)(/(?P<slot_version>[0-9]+)){{0,1}}$"#,
2,159,428✔
56
            *STANDARD_PRINCIPAL_REGEX_STRING, *CONTRACT_NAME_REGEX_STRING
2,159,428✔
57
        )).unwrap()
2,159,428✔
58
    }
2,159,428✔
59

60
    fn metrics_identifier(&self) -> &str {
137,898✔
61
        "/v2/stackerdb/:principal/:contract_name/:slot_id/:slot_version"
137,898✔
62
    }
137,898✔
63

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

79
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
137,898✔
80
        let slot_id = request::get_u32(captures, "slot_id")?;
137,898✔
81
        let slot_version = if captures.name("slot_version").is_some() {
137,898✔
UNCOV
82
            Some(request::get_u32(captures, "slot_version")?)
×
83
        } else {
84
            None
137,898✔
85
        };
86

87
        self.contract_identifier = Some(contract_identifier);
137,898✔
88
        self.slot_id = Some(slot_id);
137,898✔
89
        self.slot_version = slot_version;
137,898✔
90

91
        Ok(HttpRequestContents::new().query_string(query))
137,898✔
92
    }
137,898✔
93
}
94

95
impl RPCRequestHandler for RPCGetStackerDBChunkRequestHandler {
96
    /// Reset internal state
97
    fn restart(&mut self) {
137,898✔
98
        self.contract_identifier = None;
137,898✔
99
        self.slot_id = None;
137,898✔
100
        self.slot_version = None;
137,898✔
101
    }
137,898✔
102

103
    /// Make the response.
104
    /// NOTE: it's not safe to stream chunks; they have to be sent all at once.
105
    /// This is because any streaming algorithm that does not lock the chunk row is at risk of
106
    /// racing a chunk-download or a chunk-push, which would atomically overwrite the data being
107
    /// streamed (and lead to corrupt data being sent).  As a result, StackerDB chunks are capped
108
    /// at 1MB, and StackerDB replication is always an opt-in protocol.  Node operators subscribe
109
    /// to StackerDB replicas at their own risk.
110
    fn try_handle_request(
137,898✔
111
        &mut self,
137,898✔
112
        preamble: HttpRequestPreamble,
137,898✔
113
        _contents: HttpRequestContents,
137,898✔
114
        node: &mut StacksNodeState,
137,898✔
115
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
137,898✔
116
        let contract_identifier = self
137,898✔
117
            .contract_identifier
137,898✔
118
            .take()
137,898✔
119
            .ok_or(NetError::SendError("`contract_identifier` not set".into()))?;
137,898✔
120
        let slot_id = self
137,898✔
121
            .slot_id
137,898✔
122
            .take()
137,898✔
123
            .ok_or(NetError::SendError("`slot_id` not set".into()))?;
137,898✔
124
        let slot_version = self.slot_version.take();
137,898✔
125

126
        let chunk_resp =
137,898✔
127
            node.with_node_state(|network, _sortdb, _chainstate, _mempool, _rpc_args| {
137,898✔
128
                let chunk_res = if let Some(version) = slot_version.as_ref() {
137,898✔
UNCOV
129
                    network
×
UNCOV
130
                        .get_stackerdbs()
×
UNCOV
131
                        .get_chunk(&contract_identifier, slot_id, *version)
×
UNCOV
132
                        .map(|chunk_data| chunk_data.map(|chunk_data| chunk_data.data))
×
133
                } else {
134
                    network
137,898✔
135
                        .get_stackerdbs()
137,898✔
136
                        .get_latest_chunk(&contract_identifier, slot_id)
137,898✔
137
                };
138

139
                match chunk_res {
137,898✔
140
                    Ok(Some(chunk)) => {
137,889✔
141
                        debug!(
137,889✔
142
                            "Loaded {}-byte chunk for {} slot {} version {:?}",
143
                            chunk.len(),
×
144
                            &contract_identifier,
×
145
                            slot_id,
146
                            &slot_version
×
147
                        );
148
                        Ok(chunk)
137,889✔
149
                    }
150
                    Ok(None) | Err(NetError::NoSuchStackerDB(..)) => {
151
                        // not found
152
                        Err(StacksHttpResponse::new_error(
9✔
153
                            &preamble,
9✔
154
                            &HttpNotFound::new("StackerDB contract or chunk not found".to_string()),
9✔
155
                        ))
9✔
156
                    }
157
                    Err(e) => {
×
158
                        // some other error
159
                        error!("Failed to load StackerDB chunk";
×
160
                               "smart_contract_id" => contract_identifier.to_string(),
×
161
                               "slot_id" => slot_id,
×
162
                               "slot_version" => slot_version,
×
163
                               "error" => format!("{:?}", &e)
×
164
                        );
165
                        Err(StacksHttpResponse::new_error(
×
166
                            &preamble,
×
167
                            &HttpServerError::new("Failed to load StackerDB chunk".to_string()),
×
168
                        ))
×
169
                    }
170
                }
171
            });
137,898✔
172

173
        let chunk_resp = match chunk_resp {
137,898✔
174
            Ok(chunk) => chunk,
137,889✔
175
            Err(response) => {
9✔
176
                return response.try_into_contents().map_err(NetError::from);
9✔
177
            }
178
        };
179

180
        let preamble = HttpResponsePreamble::from_http_request_preamble(
137,889✔
181
            &preamble,
137,889✔
182
            200,
183
            "OK",
137,889✔
184
            None,
137,889✔
185
            HttpContentType::Bytes,
137,889✔
186
        );
187
        let body = HttpResponseContents::from_ram(chunk_resp);
137,889✔
188
        Ok((preamble, body))
137,889✔
189
    }
137,898✔
190
}
191

192
/// Decode the HTTP response
193
impl HttpResponse for RPCGetStackerDBChunkRequestHandler {
194
    /// Decode this response from a byte stream.  This is called by the client to decode this
195
    /// message
UNCOV
196
    fn try_parse_response(
×
UNCOV
197
        &self,
×
UNCOV
198
        preamble: &HttpResponsePreamble,
×
UNCOV
199
        body: &[u8],
×
UNCOV
200
    ) -> Result<HttpResponsePayload, Error> {
×
UNCOV
201
        let data: Vec<u8> = parse_bytes(preamble, body, STACKERDB_MAX_CHUNK_SIZE.into())?;
×
UNCOV
202
        Ok(HttpResponsePayload::Bytes(data))
×
UNCOV
203
    }
×
204
}
205

206
impl StacksHttpRequest {
207
    /// Make a request for a stackerDB's chunk
UNCOV
208
    pub fn new_get_stackerdb_chunk(
×
UNCOV
209
        host: PeerHost,
×
UNCOV
210
        stackerdb_contract_id: QualifiedContractIdentifier,
×
UNCOV
211
        slot_id: u32,
×
UNCOV
212
        slot_version: Option<u32>,
×
UNCOV
213
    ) -> StacksHttpRequest {
×
UNCOV
214
        StacksHttpRequest::new_for_peer(
×
UNCOV
215
            host,
×
UNCOV
216
            "GET".into(),
×
UNCOV
217
            if let Some(version) = slot_version {
×
UNCOV
218
                format!(
×
219
                    "/v2/stackerdb/{}/{}/{}/{}",
UNCOV
220
                    &stackerdb_contract_id.issuer, &stackerdb_contract_id.name, slot_id, version
×
221
                )
222
            } else {
UNCOV
223
                format!(
×
224
                    "/v2/stackerdb/{}/{}/{}",
UNCOV
225
                    &stackerdb_contract_id.issuer, &stackerdb_contract_id.name, slot_id
×
226
                )
227
            },
UNCOV
228
            HttpRequestContents::new(),
×
229
        )
UNCOV
230
        .expect("FATAL: failed to construct request from infallible data")
×
UNCOV
231
    }
×
232
}
233

234
impl StacksHttpResponse {
235
    /// Decode an HTTP response into a chunk
236
    /// If it fails, return Self::Error(..)
UNCOV
237
    pub fn decode_stackerdb_chunk(self) -> Result<Vec<u8>, NetError> {
×
UNCOV
238
        let contents = self.get_http_payload_ok()?;
×
UNCOV
239
        let chunk_bytes: Vec<u8> = contents.try_into()?;
×
UNCOV
240
        Ok(chunk_bytes)
×
UNCOV
241
    }
×
242
}
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