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

stacks-network / stacks-core / 23943169302

03 Apr 2026 10:28AM UTC coverage: 77.573% (-8.1%) from 85.712%
23943169302

Pull #7076

github

7f2377
web-flow
Merge bb87ecec2 into c529ad924
Pull Request #7076: feat: sortition side-table copy and validation

3743 of 4318 new or added lines in 19 files covered. (86.68%)

19304 existing lines in 182 files now uncovered.

172097 of 221852 relevant lines covered (77.57%)

7722182.76 hits per line

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

82.04
/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 {
1,735✔
42
        Self { block_id: None }
1,735✔
43
    }
1,735✔
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> {
112✔
61
        let _ = StacksChainState::load_staging_block_info(chainstate.db(), block)?
112✔
62
            .ok_or(ChainError::NoSuchBlockError)?;
112✔
63

64
        let blocks_path = chainstate.blocks_path.clone();
110✔
65

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

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

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

85
    fn metrics_identifier(&self) -> &str {
110✔
86
        "/v2/blocks/:block_id"
110✔
87
    }
110✔
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(
111✔
92
        &mut self,
111✔
93
        preamble: &HttpRequestPreamble,
111✔
94
        captures: &Captures,
111✔
95
        query: Option<&str>,
111✔
96
        _body: &[u8],
111✔
97
    ) -> Result<HttpRequestContents, Error> {
111✔
98
        if preamble.get_content_length() != 0 {
111✔
99
            return Err(Error::DecodeError(
1✔
100
                "Invalid Http request: expected 0-length body".to_string(),
1✔
101
            ));
1✔
102
        }
110✔
103

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

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

115
        Ok(HttpRequestContents::new().query_string(query))
110✔
116
    }
111✔
117
}
118

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

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

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

142
        // start loading up the block
143
        let stream = match stream_res {
108✔
144
            Ok(stream) => stream,
108✔
145
            Err(ChainError::NoSuchBlockError) => {
146
                return StacksHttpResponse::new_error(
1✔
147
                    &preamble,
1✔
148
                    &HttpNotFound::new(format!("No such block {:?}\n", &block_id)),
1✔
149
                )
150
                .try_into_contents()
1✔
151
                .map_err(NetError::from)
1✔
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(
108✔
164
            &preamble,
108✔
165
            200,
166
            "OK",
108✔
167
            None,
108✔
168
            HttpContentType::Bytes,
108✔
169
        );
170
        Ok((
108✔
171
            resp_preamble,
108✔
172
            HttpResponseContents::from_stream(Box::new(stream)),
108✔
173
        ))
108✔
174
    }
109✔
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(
100✔
182
        &self,
100✔
183
        preamble: &HttpResponsePreamble,
100✔
184
        body: &[u8],
100✔
185
    ) -> Result<HttpResponsePayload, Error> {
100✔
186
        let bytes = parse_bytes(preamble, body, MAX_MESSAGE_LEN.into())?;
100✔
187
        Ok(HttpResponsePayload::Bytes(bytes))
99✔
188
    }
100✔
189
}
190

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

199
    #[cfg(not(test))]
UNCOV
200
    fn hint_chunk_size(&self) -> usize {
×
UNCOV
201
        4096
×
UNCOV
202
    }
×
203

204
    #[cfg_attr(test, mutants::skip)]
205
    fn generate_next_chunk(&mut self) -> Result<Vec<u8>, String> {
6,653✔
206
        let block_path =
6,653✔
207
            StacksChainState::get_index_block_path(&self.blocks_path, &self.index_block_hash)
6,653✔
208
                .map_err(|e| {
6,653✔
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()
6,653✔
221
            .read(true)
6,653✔
222
            .write(false)
6,653✔
223
            .create(false)
6,653✔
224
            .truncate(false)
6,653✔
225
            .open(&block_path)
6,653✔
226
            .map_err(|e| {
6,653✔
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| {
6,653✔
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()];
6,653✔
245
        let num_read = file_fd.read(&mut buf).map_err(|e| {
6,653✔
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);
6,653✔
252

253
        self.offset += num_read as u64;
6,653✔
254
        self.total_bytes += num_read as u64;
6,653✔
255

256
        Ok(buf)
6,653✔
257
    }
6,653✔
258
}
259

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

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

277
        let value = block.serialize_to_vec();
2✔
278
        let length = value.len();
2✔
279
        let preamble = HttpResponsePreamble::new(
2✔
280
            HttpVersion::Http11,
2✔
281
            200,
282
            "OK".to_string(),
2✔
283
            if with_content_length {
2✔
284
                Some(length as u32)
1✔
285
            } else {
286
                None
1✔
287
            },
288
            HttpContentType::Bytes,
2✔
289
            true,
290
        );
291
        let body = HttpResponsePayload::Bytes(value);
2✔
292
        StacksHttpResponse::new(preamble, body)
2✔
293
    }
2✔
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> {
108✔
298
        let contents = self.get_http_payload_ok()?;
108✔
299

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

304
        Ok(block)
108✔
305
    }
108✔
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