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

stacks-network / stacks-core / 25903914664-1

15 May 2026 06:28AM UTC coverage: 47.122% (-38.8%) from 85.959%
25903914664-1

Pull #7199

github

94e391
web-flow
Merge 109f2828c into 1c7b8e6ac
Pull Request #7199: Feat: L1 and L2 early unlocks, updating signer

103343 of 219309 relevant lines covered (47.12%)

12880462.62 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,059,032✔
39
        Self {
1,059,032✔
40
            contract_identifier: None,
1,059,032✔
41
            slot_id: None,
1,059,032✔
42
            slot_version: None,
1,059,032✔
43
        }
1,059,032✔
44
    }
1,059,032✔
45
}
46

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

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

60
    fn metrics_identifier(&self) -> &str {
136,350✔
61
        "/v2/stackerdb/:principal/:contract_name/:slot_id/:slot_version"
136,350✔
62
    }
136,350✔
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(
136,350✔
67
        &mut self,
136,350✔
68
        preamble: &HttpRequestPreamble,
136,350✔
69
        captures: &Captures,
136,350✔
70
        query: Option<&str>,
136,350✔
71
        _body: &[u8],
136,350✔
72
    ) -> Result<HttpRequestContents, Error> {
136,350✔
73
        if preamble.get_content_length() != 0 {
136,350✔
74
            return Err(Error::DecodeError(
×
75
                "Invalid Http request: expected 0-length body".to_string(),
×
76
            ));
×
77
        }
136,350✔
78

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

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

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

95
impl RPCRequestHandler for RPCGetStackerDBChunkRequestHandler {
96
    /// Reset internal state
97
    fn restart(&mut self) {
136,350✔
98
        self.contract_identifier = None;
136,350✔
99
        self.slot_id = None;
136,350✔
100
        self.slot_version = None;
136,350✔
101
    }
136,350✔
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(
136,350✔
111
        &mut self,
136,350✔
112
        preamble: HttpRequestPreamble,
136,350✔
113
        _contents: HttpRequestContents,
136,350✔
114
        node: &mut StacksNodeState,
136,350✔
115
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
136,350✔
116
        let contract_identifier = self
136,350✔
117
            .contract_identifier
136,350✔
118
            .take()
136,350✔
119
            .ok_or(NetError::SendError("`contract_identifier` not set".into()))?;
136,350✔
120
        let slot_id = self
136,350✔
121
            .slot_id
136,350✔
122
            .take()
136,350✔
123
            .ok_or(NetError::SendError("`slot_id` not set".into()))?;
136,350✔
124
        let slot_version = self.slot_version.take();
136,350✔
125

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

139
                match chunk_res {
136,350✔
140
                    Ok(Some(chunk)) => {
136,341✔
141
                        debug!(
136,341✔
142
                            "Loaded {}-byte chunk for {} slot {} version {:?}",
143
                            chunk.len(),
×
144
                            &contract_identifier,
×
145
                            slot_id,
146
                            &slot_version
×
147
                        );
148
                        Ok(chunk)
136,341✔
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
            });
136,350✔
172

173
        let chunk_resp = match chunk_resp {
136,350✔
174
            Ok(chunk) => chunk,
136,341✔
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(
136,341✔
181
            &preamble,
136,341✔
182
            200,
183
            "OK",
136,341✔
184
            None,
136,341✔
185
            HttpContentType::Bytes,
136,341✔
186
        );
187
        let body = HttpResponseContents::from_ram(chunk_resp);
136,341✔
188
        Ok((preamble, body))
136,341✔
189
    }
136,350✔
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
196
    fn try_parse_response(
×
197
        &self,
×
198
        preamble: &HttpResponsePreamble,
×
199
        body: &[u8],
×
200
    ) -> Result<HttpResponsePayload, Error> {
×
201
        let data: Vec<u8> = parse_bytes(preamble, body, STACKERDB_MAX_CHUNK_SIZE.into())?;
×
202
        Ok(HttpResponsePayload::Bytes(data))
×
203
    }
×
204
}
205

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

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