• 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

7.69
/stackslib/src/net/api/postblock.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 regex::{Captures, Regex};
18
use stacks_common::codec::{Error as CodecError, StacksMessageCodec, MAX_PAYLOAD_LEN};
19
use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId};
20
use stacks_common::types::net::PeerHost;
21

22
use crate::chainstate::burn::db::sortdb::SortitionDB;
23
use crate::chainstate::stacks::{StacksBlock, StacksBlockHeader};
24
use crate::net::http::{
25
    parse_json, Error, HttpContentType, HttpNotFound, HttpRequest, HttpRequestContents,
26
    HttpRequestPreamble, HttpResponse, HttpResponseContents, HttpResponsePayload,
27
    HttpResponsePreamble, HttpServerError,
28
};
29
use crate::net::httpcore::{request, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
30
use crate::net::relay::{BlockAcceptResponse, Relayer};
31
use crate::net::{BlocksData, BlocksDatum, Error as NetError, StacksMessageType, StacksNodeState};
32

33
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34
pub struct StacksBlockAcceptedData {
35
    pub stacks_block_id: StacksBlockId,
36
    pub accepted: bool,
37
}
38

39
#[derive(Clone)]
40
pub struct RPCPostBlockRequestHandler {
41
    pub block: Option<StacksBlock>,
42
    pub consensus_hash: Option<ConsensusHash>,
43
}
44

45
impl RPCPostBlockRequestHandler {
46
    pub fn new() -> Self {
1,059,005✔
47
        Self {
1,059,005✔
48
            block: None,
1,059,005✔
49
            consensus_hash: None,
1,059,005✔
50
        }
1,059,005✔
51
    }
1,059,005✔
52

53
    /// Decode a bare block from the body
54
    fn parse_postblock_octets(mut body: &[u8]) -> Result<StacksBlock, Error> {
×
55
        let block = StacksBlock::consensus_deserialize(&mut body).map_err(|e| {
×
56
            if let CodecError::DeserializeError(msg) = e {
×
57
                Error::DecodeError(format!("Failed to deserialize posted transaction: {}", msg))
×
58
            } else {
59
                e.into()
×
60
            }
61
        })?;
×
62
        Ok(block)
×
63
    }
×
64
}
65

66
/// Decode the HTTP request
67
impl HttpRequest for RPCPostBlockRequestHandler {
68
    fn verb(&self) -> &'static str {
1,059,005✔
69
        "POST"
1,059,005✔
70
    }
1,059,005✔
71

72
    fn path_regex(&self) -> Regex {
2,118,010✔
73
        Regex::new(r#"^/v2/blocks/upload/(?P<consensus_hash>[0-9a-f]{40})$"#).unwrap()
2,118,010✔
74
    }
2,118,010✔
75

76
    fn metrics_identifier(&self) -> &str {
×
77
        "/v2/blocks/upload/:block"
×
78
    }
×
79

80
    /// Try to decode this request.
81
    /// There's nothing to load here, so just make sure the request is well-formed.
82
    fn try_parse_request(
×
83
        &mut self,
×
84
        preamble: &HttpRequestPreamble,
×
85
        captures: &Captures,
×
86
        query: Option<&str>,
×
87
        body: &[u8],
×
88
    ) -> Result<HttpRequestContents, Error> {
×
89
        if preamble.get_content_length() == 0 {
×
90
            return Err(Error::DecodeError(
×
91
                "Invalid Http request: expected non-zero-length body for PostBlock".to_string(),
×
92
            ));
×
93
        }
×
94

95
        if preamble.get_content_length() > MAX_PAYLOAD_LEN {
×
96
            return Err(Error::DecodeError(
×
97
                "Invalid Http request: PostBlock body is too big".to_string(),
×
98
            ));
×
99
        }
×
100

101
        if Some(HttpContentType::Bytes) != preamble.content_type || preamble.content_type.is_none()
×
102
        {
103
            return Err(Error::DecodeError(
×
104
                "Invalid Http request: PostBlock takes application/octet-stream".to_string(),
×
105
            ));
×
106
        }
×
107

108
        let consensus_hash = request::get_consensus_hash(captures, "consensus_hash")?;
×
109
        let block = Self::parse_postblock_octets(body)?;
×
110

111
        self.consensus_hash = Some(consensus_hash);
×
112
        self.block = Some(block);
×
113
        Ok(HttpRequestContents::new().query_string(query))
×
114
    }
×
115
}
116

117
impl RPCRequestHandler for RPCPostBlockRequestHandler {
118
    /// Reset internal state
119
    fn restart(&mut self) {
×
120
        self.consensus_hash = None;
×
121
        self.block = None;
×
122
    }
×
123

124
    /// Make the response
125
    fn try_handle_request(
×
126
        &mut self,
×
127
        preamble: HttpRequestPreamble,
×
128
        _contents: HttpRequestContents,
×
129
        node: &mut StacksNodeState,
×
130
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
×
131
        // get out the request body
132
        let block = self
×
133
            .block
×
134
            .take()
×
135
            .ok_or(NetError::SendError("`block` not set".into()))?;
×
136
        let consensus_hash = self
×
137
            .consensus_hash
×
138
            .take()
×
139
            .ok_or(NetError::SendError("`consensus_hash` not set".into()))?;
×
140

141
        let block_hash = block.block_hash();
×
142

143
        let data_resp =
×
144
            node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
×
145
                match SortitionDB::get_sortition_id_by_consensus(sortdb.conn(), &consensus_hash) {
×
146
                    Ok(Some(_)) => {
147
                        // we recognize this consensus hash
148
                        let ic = sortdb.index_conn();
×
149
                        match Relayer::process_new_anchored_block(
×
150
                            &ic,
×
151
                            chainstate,
×
152
                            &consensus_hash,
×
153
                            &block,
×
154
                            0,
×
155
                        ) {
×
156
                            Ok(accepted) => {
×
157
                                debug!(
×
158
                                    "Received POSTed Stacks block {}/{}: {:?}",
159
                                    &consensus_hash, &block_hash, &accepted
×
160
                                );
161
                                return Ok(BlockAcceptResponse::Accepted == accepted);
×
162
                            }
163
                            Err(e) => {
×
164
                                let msg = format!(
×
165
                                    "Failed to process anchored block {}/{}: {:?}",
166
                                    consensus_hash,
167
                                    &block.block_hash(),
×
168
                                    &e
×
169
                                );
170
                                error!("{}", &msg);
×
171
                                return Err(StacksHttpResponse::new_error(
×
172
                                    &preamble,
×
173
                                    &HttpServerError::new(msg),
×
174
                                ));
×
175
                            }
176
                        }
177
                    }
178
                    Ok(None) => {
179
                        let msg = format!(
×
180
                            "Unrecognized consensus hash {} for block {}",
181
                            consensus_hash,
182
                            &block.block_hash()
×
183
                        );
184
                        debug!("{}", &msg);
×
185
                        return Err(StacksHttpResponse::new_error(
×
186
                            &preamble,
×
187
                            &HttpNotFound::new(msg),
×
188
                        ));
×
189
                    }
190
                    Err(e) => {
×
191
                        let msg = format!(
×
192
                            "Failed to query sortition ID by consensus '{}': {:?}",
193
                            consensus_hash, &e
×
194
                        );
195
                        error!("{}", &msg);
×
196
                        return Err(StacksHttpResponse::new_error(
×
197
                            &preamble,
×
198
                            &HttpServerError::new(msg),
×
199
                        ));
×
200
                    }
201
                }
202
            });
×
203

204
        let data_resp = match data_resp {
×
205
            Ok(accepted) => StacksBlockAcceptedData {
×
206
                accepted,
×
207
                stacks_block_id: StacksBlockHeader::make_index_block_hash(
×
208
                    &consensus_hash,
×
209
                    &block_hash,
×
210
                ),
×
211
            },
×
212
            Err(response) => {
×
213
                return response.try_into_contents().map_err(NetError::from);
×
214
            }
215
        };
216

217
        // don't forget to forward this to the p2p network!
218
        if data_resp.accepted {
×
219
            node.set_relay_message(StacksMessageType::Blocks(BlocksData {
×
220
                blocks: vec![BlocksDatum(consensus_hash, block)],
×
221
            }));
×
222
        }
×
223

224
        let preamble = HttpResponsePreamble::ok_json(&preamble);
×
225
        let body = HttpResponseContents::try_from_json(&data_resp)?;
×
226
        Ok((preamble, body))
×
227
    }
×
228
}
229

230
/// Decode the HTTP response
231
impl HttpResponse for RPCPostBlockRequestHandler {
232
    fn try_parse_response(
×
233
        &self,
×
234
        preamble: &HttpResponsePreamble,
×
235
        body: &[u8],
×
236
    ) -> Result<HttpResponsePayload, Error> {
×
237
        let accepted: StacksBlockAcceptedData = parse_json(preamble, body)?;
×
238
        Ok(HttpResponsePayload::try_from_json(accepted)?)
×
239
    }
×
240
}
241

242
impl StacksHttpRequest {
243
    /// Make a new post-block request
244
    pub fn new_post_block(
×
245
        host: PeerHost,
×
246
        ch: ConsensusHash,
×
247
        block: StacksBlock,
×
248
    ) -> StacksHttpRequest {
×
249
        StacksHttpRequest::new_for_peer(
×
250
            host,
×
251
            "POST".into(),
×
252
            format!("/v2/blocks/upload/{}", &ch),
×
253
            HttpRequestContents::new().payload_stacks(&block),
×
254
        )
255
        .expect("FATAL: failed to construct request from infallible data")
×
256
    }
×
257
}
258

259
impl StacksHttpResponse {
260
    pub fn decode_stacks_block_accepted(self) -> Result<StacksBlockAcceptedData, NetError> {
×
261
        let contents = self.get_http_payload_ok()?;
×
262
        let response_json: serde_json::Value = contents.try_into()?;
×
263
        let result: StacksBlockAcceptedData = serde_json::from_value(response_json)
×
264
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
×
265
        Ok(result)
×
266
    }
×
267
}
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