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

stacks-network / stacks-core / 24347808425

13 Apr 2026 02:06PM UTC coverage: 85.715% (+0.003%) from 85.712%
24347808425

Pull #7065

github

e0e150
web-flow
Merge c9e6c27d2 into 3617e52ff
Pull Request #7065: chore: remove dangerous `From` implementation on guarded strings

406 of 421 new or added lines in 34 files covered. (96.44%)

1797 existing lines in 58 files now uncovered.

186876 of 218019 relevant lines covered (85.72%)

17157046.07 hits per line

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

82.95
/stackslib/src/net/api/poststackerdbchunk.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::{StackerDBChunkAckData, StackerDBChunkData};
20
use regex::{Captures, Regex};
21
use serde_json::{self, json};
22
use stacks_common::codec::MAX_MESSAGE_LEN;
23
use stacks_common::types::net::PeerHost;
24
use stacks_common::util::secp256k1::MessageSignature;
25

26
use crate::net::http::{
27
    parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble,
28
    HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble, HttpServerError,
29
};
30
use crate::net::httpcore::{request, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
31
use crate::net::{Error as NetError, StackerDBPushChunkData, StacksMessageType, StacksNodeState};
32

33
#[derive(Clone)]
34
pub struct RPCPostStackerDBChunkRequestHandler {
35
    pub contract_identifier: Option<QualifiedContractIdentifier>,
36
    pub chunk: Option<StackerDBChunkData>,
37
}
38
impl RPCPostStackerDBChunkRequestHandler {
39
    pub fn new() -> Self {
983,511✔
40
        Self {
983,511✔
41
            contract_identifier: None,
983,511✔
42
            chunk: None,
983,511✔
43
        }
983,511✔
44
    }
983,511✔
45
}
46

47
/// Decode the HTTP request
48
impl HttpRequest for RPCPostStackerDBChunkRequestHandler {
49
    fn verb(&self) -> &'static str {
983,510✔
50
        "POST"
983,510✔
51
    }
983,510✔
52

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

61
    fn metrics_identifier(&self) -> &str {
498,075✔
62
        "/v2/stackerdb/:principal/:contract_name/chunks"
498,075✔
63
    }
498,075✔
64

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

80
        if preamble.get_content_length() > MAX_MESSAGE_LEN {
498,076✔
UNCOV
81
            return Err(Error::DecodeError(
×
82
                "Invalid Http request: PostStackerDBChunk body is too big".to_string(),
×
83
            ));
×
84
        }
498,076✔
85

86
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
498,076✔
87
        let chunk: StackerDBChunkData = serde_json::from_slice(body).map_err(Error::JsonError)?;
498,076✔
88

89
        self.contract_identifier = Some(contract_identifier);
498,076✔
90
        self.chunk = Some(chunk);
498,076✔
91

92
        Ok(HttpRequestContents::new().query_string(query))
498,076✔
93
    }
498,076✔
94
}
95

96
#[derive(Debug, Clone, PartialEq)]
97
pub enum StackerDBErrorCodes {
98
    DataAlreadyExists,
99
    NoSuchSlot,
100
    BadSigner,
101
}
102

103
impl StackerDBErrorCodes {
104
    pub fn code(&self) -> u32 {
17,628✔
105
        match self {
17,628✔
106
            Self::DataAlreadyExists => 0,
17,444✔
107
            Self::NoSuchSlot => 1,
146✔
108
            Self::BadSigner => 2,
38✔
109
        }
110
    }
17,628✔
111

112
    #[cfg_attr(test, mutants::skip)]
113
    pub fn reason(&self) -> &'static str {
8,814✔
114
        match self {
8,814✔
115
            Self::DataAlreadyExists => "Data for this slot and version already exist",
8,722✔
116
            Self::NoSuchSlot => "No such StackerDB slot",
73✔
117
            Self::BadSigner => "Signature does not match slot signer",
19✔
118
        }
119
    }
8,814✔
120

121
    pub fn into_json(self) -> serde_json::Value {
8,814✔
122
        json!({
8,814✔
123
            "code": self.code(),
8,814✔
124
            "message": format!("{:?}", &self),
8,814✔
125
            "reason": self.reason()
8,814✔
126
        })
127
    }
8,814✔
128

129
    #[cfg_attr(test, mutants::skip)]
130
    pub fn from_code(code: u32) -> Option<Self> {
3,060✔
131
        match code {
3,060✔
132
            0 => Some(Self::DataAlreadyExists),
3,028✔
133
            1 => Some(Self::NoSuchSlot),
32✔
UNCOV
134
            2 => Some(Self::BadSigner),
×
135
            _ => None,
×
136
        }
137
    }
3,060✔
138
}
139

140
impl RPCRequestHandler for RPCPostStackerDBChunkRequestHandler {
141
    /// Reset internal state
142
    fn restart(&mut self) {
498,076✔
143
        self.contract_identifier = None;
498,076✔
144
        self.chunk = None;
498,076✔
145
    }
498,076✔
146

147
    /// Make the response.
148
    fn try_handle_request(
498,075✔
149
        &mut self,
498,075✔
150
        preamble: HttpRequestPreamble,
498,075✔
151
        _contents: HttpRequestContents,
498,075✔
152
        node: &mut StacksNodeState,
498,075✔
153
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
498,075✔
154
        let contract_identifier = self
498,075✔
155
            .contract_identifier
498,075✔
156
            .take()
498,075✔
157
            .ok_or(NetError::SendError("`contract_identifier` not set".into()))?;
498,075✔
158
        let stackerdb_chunk = self
498,075✔
159
            .chunk
498,075✔
160
            .take()
498,075✔
161
            .ok_or(NetError::SendError("`chunk` not set".into()))?;
498,075✔
162

163
        let ack_resp =
498,075✔
164
            node.with_node_state(|network, _sortdb, _chainstate, _mempool, _rpc_args| {
498,075✔
165
                let tx = if let Ok(tx) = network.stackerdbs_tx_begin(&contract_identifier) {
498,075✔
166
                    tx
498,074✔
167
                } else {
168
                    return Err(StacksHttpResponse::new_error(
1✔
169
                        &preamble,
1✔
170
                        &HttpNotFound::new("StackerDB not found".to_string()),
1✔
171
                    ));
1✔
172
                };
173
                if let Err(_e) = tx.get_stackerdb_id(&contract_identifier) {
498,074✔
174
                    // shouldn't be necessary (this is checked against the peer network's configured DBs),
175
                    // but you never know.
UNCOV
176
                    return Err(StacksHttpResponse::new_error(
×
177
                        &preamble,
×
178
                        &HttpNotFound::new("StackerDB not found".to_string()),
×
179
                    ));
×
180
                }
498,074✔
181
                if let Err(e) = tx.try_replace_chunk(
498,074✔
182
                    &contract_identifier,
498,074✔
183
                    &stackerdb_chunk.get_slot_metadata(),
498,074✔
184
                    &stackerdb_chunk.data,
498,074✔
185
                ) {
498,074✔
186
                    test_debug!(
8,814✔
187
                        "Failed to replace chunk {}.{} in {}: {:?}",
188
                        stackerdb_chunk.slot_id,
189
                        stackerdb_chunk.slot_version,
UNCOV
190
                        &contract_identifier,
×
191
                        &e
×
192
                    );
193
                    let slot_metadata_opt =
8,814✔
194
                        match tx.get_slot_metadata(&contract_identifier, stackerdb_chunk.slot_id) {
8,814✔
195
                            Ok(slot_opt) => slot_opt,
8,814✔
UNCOV
196
                            Err(e) => {
×
197
                                // some other error
UNCOV
198
                                error!("Failed to load replaced StackerDB chunk metadata";
×
199
                                       "smart_contract_id" => contract_identifier.to_string(),
×
200
                                       "error" => format!("{:?}", &e)
×
201
                                );
UNCOV
202
                                return Err(StacksHttpResponse::new_error(
×
203
                                    &preamble,
×
204
                                    &HttpServerError::new(format!(
×
205
                                        "Failed to load StackerDB chunk for {}: {:?}",
×
206
                                        &contract_identifier, &e
×
207
                                    )),
×
208
                                ));
×
209
                            }
210
                        };
211

212
                    let err_code = if slot_metadata_opt.is_some() {
8,814✔
213
                        if let NetError::BadSlotSigner(..) = e {
8,741✔
214
                            StackerDBErrorCodes::BadSigner
19✔
215
                        } else {
216
                            StackerDBErrorCodes::DataAlreadyExists
8,722✔
217
                        }
218
                    } else {
219
                        StackerDBErrorCodes::NoSuchSlot
73✔
220
                    };
221
                    let reason = serde_json::to_string(&err_code.clone().into_json())
8,814✔
222
                        .unwrap_or("(unable to encode JSON)".to_string());
8,814✔
223

224
                    let ack = StackerDBChunkAckData {
8,814✔
225
                        accepted: false,
8,814✔
226
                        reason: Some(reason),
8,814✔
227
                        metadata: slot_metadata_opt,
8,814✔
228
                        code: Some(err_code.code()),
8,814✔
229
                    };
8,814✔
230
                    return Ok(ack);
8,814✔
231
                }
489,260✔
232

233
                let slot_metadata = if let Ok(Some(md)) =
489,260✔
234
                    tx.get_slot_metadata(&contract_identifier, stackerdb_chunk.slot_id)
489,260✔
235
                {
236
                    md
489,260✔
237
                } else {
UNCOV
238
                    return Err(StacksHttpResponse::new_error(
×
239
                        &preamble,
×
240
                        &HttpServerError::new(
×
241
                            "Failed to load slot metadata after storing chunk".to_string(),
×
242
                        ),
×
243
                    ));
×
244
                };
245

246
                if let Err(e) = tx.commit() {
489,260✔
UNCOV
247
                    return Err(StacksHttpResponse::new_error(
×
248
                        &preamble,
×
249
                        &HttpServerError::new(format!("Failed to commit StackerDB tx: {:?}", &e)),
×
250
                    ));
×
251
                }
489,260✔
252

253
                debug!(
489,260✔
254
                    "Wrote {}-byte chunk to {} slot {} version {}",
UNCOV
255
                    &stackerdb_chunk.data.len(),
×
256
                    &contract_identifier,
×
257
                    stackerdb_chunk.slot_id,
258
                    stackerdb_chunk.slot_version
259
                );
260

261
                // success!
262
                let ack = StackerDBChunkAckData {
489,260✔
263
                    accepted: true,
489,260✔
264
                    reason: None,
489,260✔
265
                    metadata: Some(slot_metadata),
489,260✔
266
                    code: None,
489,260✔
267
                };
489,260✔
268

269
                return Ok(ack);
489,260✔
270
            });
498,075✔
271

272
        let ack_resp = match ack_resp {
498,075✔
273
            Ok(ack) => ack,
498,074✔
274
            Err(response) => {
1✔
275
                return response.try_into_contents().map_err(NetError::from);
1✔
276
            }
277
        };
278

279
        if ack_resp.accepted {
498,074✔
280
            let push_chunk_data = StackerDBPushChunkData {
489,260✔
281
                contract_id: contract_identifier,
489,260✔
282
                rc_consensus_hash: node.with_node_state(|network, _, _, _, _| {
489,260✔
283
                    network.get_chain_view().rc_consensus_hash.clone()
489,260✔
284
                }),
489,260✔
285
                chunk_data: stackerdb_chunk,
489,260✔
286
            };
287
            node.set_relay_message(StacksMessageType::StackerDBPushChunk(push_chunk_data));
489,260✔
288
        }
8,814✔
289

290
        let preamble = HttpResponsePreamble::ok_json(&preamble);
498,074✔
291
        let body = HttpResponseContents::try_from_json(&ack_resp)?;
498,074✔
292
        Ok((preamble, body))
498,074✔
293
    }
498,075✔
294
}
295

296
/// Decode the HTTP response
297
impl HttpResponse for RPCPostStackerDBChunkRequestHandler {
298
    /// Decode this response from a byte stream.  This is called by the client to decode this
299
    /// message
300
    fn try_parse_response(
5✔
301
        &self,
5✔
302
        preamble: &HttpResponsePreamble,
5✔
303
        body: &[u8],
5✔
304
    ) -> Result<HttpResponsePayload, Error> {
5✔
305
        let ack: StackerDBChunkAckData = parse_json(preamble, body)?;
5✔
306
        Ok(HttpResponsePayload::try_from_json(ack)?)
5✔
307
    }
5✔
308
}
309

310
impl StacksHttpRequest {
311
    pub fn new_post_stackerdb_chunk(
7✔
312
        host: PeerHost,
7✔
313
        stackerdb_contract_id: QualifiedContractIdentifier,
7✔
314
        slot_id: u32,
7✔
315
        slot_version: u32,
7✔
316
        sig: MessageSignature,
7✔
317
        data: Vec<u8>,
7✔
318
    ) -> StacksHttpRequest {
7✔
319
        StacksHttpRequest::new_for_peer(
7✔
320
            host,
7✔
321
            "POST".into(),
7✔
322
            format!(
7✔
323
                "/v2/stackerdb/{}/{}/chunks",
324
                &stackerdb_contract_id.issuer, &stackerdb_contract_id.name
7✔
325
            ),
326
            HttpRequestContents::new().payload_json(
7✔
327
                serde_json::to_value(StackerDBChunkData {
7✔
328
                    slot_id,
7✔
329
                    slot_version,
7✔
330
                    sig,
7✔
331
                    data,
7✔
332
                })
7✔
333
                .expect("FATAL: failed to construct JSON from infallible structure"),
7✔
334
            ),
335
        )
336
        .expect("FATAL: failed to construct request from infallible data")
7✔
337
    }
7✔
338
}
339

340
impl StacksHttpResponse {
341
    /// Decode an HTTP response into a chunk
342
    /// If it fails, return Self::Error(..)
343
    pub fn decode_stackerdb_chunk_ack(self) -> Result<StackerDBChunkAckData, NetError> {
5✔
344
        let contents = self.get_http_payload_ok()?;
5✔
345
        let response_json: serde_json::Value = contents.try_into()?;
5✔
346
        let data: StackerDBChunkAckData = serde_json::from_value(response_json)
5✔
347
            .map_err(|_e| Error::DecodeError("Failed to decode JSON".to_string()))?;
5✔
348
        Ok(data)
5✔
349
    }
5✔
350
}
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