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

stacks-network / stacks-core / 24140301216

08 Apr 2026 02:18PM UTC coverage: 46.817% (-38.9%) from 85.712%
24140301216

Pull #6959

github

279acf
web-flow
Merge efbee1783 into 882e27245
Pull Request #6959: Perf/cache epoch version in ClarityDatabase

66 of 149 new or added lines in 8 files covered. (44.3%)

85999 existing lines in 334 files now uncovered.

102056 of 217989 relevant lines covered (46.82%)

12315981.16 hits per line

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

66.47
/stackslib/src/net/api/getblock.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 std::io::{Read, Seek, SeekFrom};
18
use std::{fs, io};
19

20
use regex::{Captures, Regex};
21
use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN};
22
use stacks_common::types::chainstate::StacksBlockId;
23
use stacks_common::types::net::PeerHost;
24

25
use crate::chainstate::stacks::db::StacksChainState;
26
use crate::chainstate::stacks::{Error as ChainError, StacksBlock};
27
use crate::net::http::{
28
    parse_bytes, Error, HttpChunkGenerator, HttpContentType, HttpNotFound, HttpRequest,
29
    HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents,
30
    HttpResponsePayload, HttpResponsePreamble, HttpServerError,
31
};
32
use crate::net::httpcore::{RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
33
use crate::net::{Error as NetError, StacksNodeState};
34

35
#[derive(Clone)]
36
pub struct RPCBlocksRequestHandler {
37
    pub block_id: Option<StacksBlockId>,
38
}
39

40
impl RPCBlocksRequestHandler {
41
    pub fn new() -> Self {
970,322✔
42
        Self { block_id: None }
970,322✔
43
    }
970,322✔
44
}
45

46
#[derive(Debug, PartialEq, Clone)]
47
pub struct StacksBlockStream {
48
    /// index block hash of the block to download
49
    pub index_block_hash: StacksBlockId,
50
    /// offset into whatever is being read (the blob, or the file in the chunk store)
51
    pub offset: u64,
52
    /// total number of bytes read.
53
    pub total_bytes: u64,
54

55
    /// connection to the underlying chainstate
56
    blocks_path: String,
57
}
58

59
impl StacksBlockStream {
60
    pub fn new(chainstate: &StacksChainState, block: &StacksBlockId) -> Result<Self, ChainError> {
6,103✔
61
        let _ = StacksChainState::load_staging_block_info(chainstate.db(), block)?
6,103✔
62
            .ok_or(ChainError::NoSuchBlockError)?;
6,103✔
63

64
        let blocks_path = chainstate.blocks_path.clone();
6,103✔
65

66
        Ok(StacksBlockStream {
6,103✔
67
            index_block_hash: block.clone(),
6,103✔
68
            offset: 0,
6,103✔
69
            total_bytes: 0,
6,103✔
70
            blocks_path,
6,103✔
71
        })
6,103✔
72
    }
6,103✔
73
}
74

75
/// Decode the HTTP request
76
impl HttpRequest for RPCBlocksRequestHandler {
77
    fn verb(&self) -> &'static str {
970,322✔
78
        "GET"
970,322✔
79
    }
970,322✔
80

81
    fn path_regex(&self) -> Regex {
1,940,644✔
82
        Regex::new(r#"^/v2/blocks/(?P<block_id>[0-9a-f]{64})$"#).unwrap()
1,940,644✔
83
    }
1,940,644✔
84

85
    fn metrics_identifier(&self) -> &str {
6,103✔
86
        "/v2/blocks/:block_id"
6,103✔
87
    }
6,103✔
88

89
    /// Try to decode this request.
90
    /// There's nothing to load here, so just make sure the request is well-formed.
91
    fn try_parse_request(
6,103✔
92
        &mut self,
6,103✔
93
        preamble: &HttpRequestPreamble,
6,103✔
94
        captures: &Captures,
6,103✔
95
        query: Option<&str>,
6,103✔
96
        _body: &[u8],
6,103✔
97
    ) -> Result<HttpRequestContents, Error> {
6,103✔
98
        if preamble.get_content_length() != 0 {
6,103✔
UNCOV
99
            return Err(Error::DecodeError(
×
UNCOV
100
                "Invalid Http request: expected 0-length body".to_string(),
×
UNCOV
101
            ));
×
102
        }
6,103✔
103

104
        let block_id_str = captures
6,103✔
105
            .name("block_id")
6,103✔
106
            .ok_or(Error::DecodeError(
6,103✔
107
                "Failed to match path to block ID group".to_string(),
6,103✔
108
            ))?
6,103✔
109
            .as_str();
6,103✔
110

111
        let block_id = StacksBlockId::from_hex(block_id_str)
6,103✔
112
            .map_err(|_| Error::DecodeError("Invalid path: unparseable block ID".to_string()))?;
6,103✔
113
        self.block_id = Some(block_id);
6,103✔
114

115
        Ok(HttpRequestContents::new().query_string(query))
6,103✔
116
    }
6,103✔
117
}
118

119
impl RPCRequestHandler for RPCBlocksRequestHandler {
120
    /// Reset internal state
121
    fn restart(&mut self) {
6,103✔
122
        self.block_id = None;
6,103✔
123
    }
6,103✔
124

125
    /// Make the response
126
    fn try_handle_request(
6,103✔
127
        &mut self,
6,103✔
128
        preamble: HttpRequestPreamble,
6,103✔
129
        _contents: HttpRequestContents,
6,103✔
130
        node: &mut StacksNodeState,
6,103✔
131
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
6,103✔
132
        let block_id = self
6,103✔
133
            .block_id
6,103✔
134
            .take()
6,103✔
135
            .ok_or(NetError::SendError("Missing `block_id`".into()))?;
6,103✔
136

137
        let stream_res =
6,103✔
138
            node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
6,103✔
139
                StacksBlockStream::new(chainstate, &block_id)
6,103✔
140
            });
6,103✔
141

142
        // start loading up the block
143
        let stream = match stream_res {
6,103✔
144
            Ok(stream) => stream,
6,103✔
145
            Err(ChainError::NoSuchBlockError) => {
UNCOV
146
                return StacksHttpResponse::new_error(
×
UNCOV
147
                    &preamble,
×
UNCOV
148
                    &HttpNotFound::new(format!("No such block {:?}\n", &block_id)),
×
149
                )
UNCOV
150
                .try_into_contents()
×
UNCOV
151
                .map_err(NetError::from)
×
152
            }
153
            Err(e) => {
×
154
                // nope -- error trying to check
155
                let msg = format!("Failed to load block: {:?}\n", &e);
×
156
                warn!("{}", &msg);
×
157
                return StacksHttpResponse::new_error(&preamble, &HttpServerError::new(msg))
×
158
                    .try_into_contents()
×
159
                    .map_err(NetError::from);
×
160
            }
161
        };
162

163
        let resp_preamble = HttpResponsePreamble::from_http_request_preamble(
6,103✔
164
            &preamble,
6,103✔
165
            200,
166
            "OK",
6,103✔
167
            None,
6,103✔
168
            HttpContentType::Bytes,
6,103✔
169
        );
170
        Ok((
6,103✔
171
            resp_preamble,
6,103✔
172
            HttpResponseContents::from_stream(Box::new(stream)),
6,103✔
173
        ))
6,103✔
174
    }
6,103✔
175
}
176

177
/// Decode the HTTP response
178
impl HttpResponse for RPCBlocksRequestHandler {
179
    /// Decode this response from a byte stream.  This is called by the client to decode this
180
    /// message
181
    fn try_parse_response(
6,084✔
182
        &self,
6,084✔
183
        preamble: &HttpResponsePreamble,
6,084✔
184
        body: &[u8],
6,084✔
185
    ) -> Result<HttpResponsePayload, Error> {
6,084✔
186
        let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?;
6,084✔
187
        Ok(HttpResponsePayload::Bytes(bytes))
6,084✔
188
    }
6,084✔
189
}
190

191
/// Stream implementation for HeaderStreamData
192
impl HttpChunkGenerator for StacksBlockStream {
193
    #[cfg(test)]
194
    fn hint_chunk_size(&self) -> usize {
2,070✔
195
        // make this hurt
196
        32
2,070✔
197
    }
2,070✔
198

199
    #[cfg(not(test))]
200
    fn hint_chunk_size(&self) -> usize {
24,372✔
201
        4096
24,372✔
202
    }
24,372✔
203

204
    #[cfg_attr(test, mutants::skip)]
205
    fn generate_next_chunk(&mut self) -> Result<Vec<u8>, String> {
20,339✔
206
        let block_path =
20,339✔
207
            StacksChainState::get_index_block_path(&self.blocks_path, &self.index_block_hash)
20,339✔
208
                .map_err(|e| {
20,339✔
209
                    let msg = format!(
×
210
                        "Failed to load block path for {}: {:?}",
211
                        &self.index_block_hash, &e
×
212
                    );
213
                    warn!("{}", &msg);
×
214
                    msg
×
215
                })?;
×
216

217
        // The reason we open a file on each call to stream data is because we don't want to
218
        // exhaust the supply of file descriptors.  Maybe a future version of this code will do
219
        // something like cache the set of open files so we don't have to keep re-opening them.
220
        let mut file_fd = fs::OpenOptions::new()
20,339✔
221
            .read(true)
20,339✔
222
            .write(false)
20,339✔
223
            .create(false)
20,339✔
224
            .truncate(false)
20,339✔
225
            .open(&block_path)
20,339✔
226
            .map_err(|e| {
20,339✔
227
                if e.kind() == io::ErrorKind::NotFound {
×
228
                    let msg = format!("Blook file not found for {}", &self.index_block_hash);
×
229
                    warn!("{}", &msg);
×
230
                    msg
×
231
                } else {
232
                    let msg = format!("Failed to open block {}: {:?}", &self.index_block_hash, &e);
×
233
                    warn!("{}", &msg);
×
234
                    msg
×
235
                }
236
            })?;
×
237

238
        file_fd.seek(SeekFrom::Start(self.offset)).map_err(|e| {
20,339✔
239
            let msg = format!("Failed to read block {}: {:?}", &self.index_block_hash, &e);
×
240
            warn!("{}", &msg);
×
241
            msg
×
242
        })?;
×
243

244
        let mut buf = vec![0u8; self.hint_chunk_size()];
20,339✔
245
        let num_read = file_fd.read(&mut buf).map_err(|e| {
20,339✔
246
            let msg = format!("Failed to read block {}: {:?}", &self.index_block_hash, &e);
×
247
            warn!("{}", &msg);
×
248
            msg
×
249
        })?;
×
250

251
        buf.truncate(num_read);
20,339✔
252

253
        self.offset += num_read as u64;
20,339✔
254
        self.total_bytes += num_read as u64;
20,339✔
255

256
        Ok(buf)
20,339✔
257
    }
20,339✔
258
}
259

260
impl StacksHttpRequest {
UNCOV
261
    pub fn new_getblock(host: PeerHost, index_block_hash: StacksBlockId) -> StacksHttpRequest {
×
UNCOV
262
        StacksHttpRequest::new_for_peer(
×
UNCOV
263
            host,
×
UNCOV
264
            "GET".into(),
×
UNCOV
265
            format!("/v2/blocks/{}", &index_block_hash),
×
UNCOV
266
            HttpRequestContents::new(),
×
267
        )
UNCOV
268
        .expect("FATAL: failed to construct request from infallible data")
×
UNCOV
269
    }
×
270
}
271

272
impl StacksHttpResponse {
273
    #[cfg(test)]
UNCOV
274
    pub fn new_getblock(block: StacksBlock, with_content_length: bool) -> StacksHttpResponse {
×
275
        use crate::net::http::HttpVersion;
276

UNCOV
277
        let value = block.serialize_to_vec();
×
UNCOV
278
        let length = value.len();
×
UNCOV
279
        let preamble = HttpResponsePreamble::new(
×
UNCOV
280
            HttpVersion::Http11,
×
281
            200,
UNCOV
282
            "OK".to_string(),
×
UNCOV
283
            if with_content_length {
×
UNCOV
284
                Some(length as u32)
×
285
            } else {
UNCOV
286
                None
×
287
            },
UNCOV
288
            HttpContentType::Bytes,
×
289
            true,
290
        );
UNCOV
291
        let body = HttpResponsePayload::Bytes(value);
×
UNCOV
292
        StacksHttpResponse::new(preamble, body)
×
UNCOV
293
    }
×
294

295
    /// Decode an HTTP response into a block.
296
    /// If it fails, return Self::Error(..)
297
    pub fn decode_block(self) -> Result<StacksBlock, NetError> {
6,094✔
298
        let contents = self.get_http_payload_ok()?;
6,094✔
299

300
        // contents will be raw bytes
301
        let block_bytes: Vec<u8> = contents.try_into()?;
6,094✔
302
        let block = StacksBlock::consensus_deserialize(&mut &block_bytes[..])?;
6,094✔
303

304
        Ok(block)
6,094✔
305
    }
6,094✔
306
}
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