• 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

57.34
/stackslib/src/net/api/postmicroblock.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::BlockHeaderHash;
20
use stacks_common::types::net::PeerHost;
21

22
use crate::burnchains::Txid;
23
use crate::chainstate::burn::db::sortdb::SortitionDB;
24
use crate::chainstate::stacks::db::StacksChainState;
25
use crate::chainstate::stacks::{Error as ChainError, StacksBlockHeader, StacksMicroblock};
26
use crate::net::http::{
27
    parse_json, Error, HttpBadRequest, HttpContentType, HttpNotFound, HttpRequest,
28
    HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents,
29
    HttpResponsePayload, HttpResponsePreamble, HttpServerError,
30
};
31
use crate::net::httpcore::{
32
    HttpRequestContentsExtensions as _, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse,
33
};
34
use crate::net::relay::Relayer;
35
use crate::net::{
36
    Error as NetError, MicroblocksData, StacksMessageType, StacksNodeState, TipRequest,
37
};
38

39
#[derive(Clone)]
40
pub struct RPCPostMicroblockRequestHandler {
41
    pub microblock: Option<StacksMicroblock>,
42
}
43

44
impl RPCPostMicroblockRequestHandler {
45
    pub fn new() -> Self {
1,059,005✔
46
        Self { microblock: None }
1,059,005✔
47
    }
1,059,005✔
48

49
    /// Decode a bare block from the body
50
    fn parse_postmicroblock_octets(mut body: &[u8]) -> Result<StacksMicroblock, Error> {
9✔
51
        let mblock = StacksMicroblock::consensus_deserialize(&mut body).map_err(|e| {
9✔
52
            if let CodecError::DeserializeError(msg) = e {
×
53
                Error::DecodeError(format!("Failed to deserialize posted microblock: {}", msg))
×
54
            } else {
55
                e.into()
×
56
            }
57
        })?;
×
58
        Ok(mblock)
9✔
59
    }
9✔
60
}
61

62
/// Decode the HTTP request
63
impl HttpRequest for RPCPostMicroblockRequestHandler {
64
    fn verb(&self) -> &'static str {
1,059,005✔
65
        "POST"
1,059,005✔
66
    }
1,059,005✔
67

68
    fn path_regex(&self) -> Regex {
2,118,010✔
69
        Regex::new(r#"^/v2/microblocks$"#).unwrap()
2,118,010✔
70
    }
2,118,010✔
71

72
    fn metrics_identifier(&self) -> &str {
9✔
73
        "/v2/microblocks"
9✔
74
    }
9✔
75

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

92
        if preamble.get_content_length() > MAX_PAYLOAD_LEN {
9✔
93
            return Err(Error::DecodeError(
×
94
                "Invalid Http request: PostMicroblock body is too big".to_string(),
×
95
            ));
×
96
        }
9✔
97

98
        if Some(HttpContentType::Bytes) != preamble.content_type || preamble.content_type.is_none()
9✔
99
        {
100
            return Err(Error::DecodeError(
×
101
                "Invalid Http request: PostMicroblock takes application/octet-stream".to_string(),
×
102
            ));
×
103
        }
9✔
104

105
        let microblock = Self::parse_postmicroblock_octets(body)?;
9✔
106
        self.microblock = Some(microblock);
9✔
107

108
        Ok(HttpRequestContents::new().query_string(query))
9✔
109
    }
9✔
110
}
111

112
impl RPCRequestHandler for RPCPostMicroblockRequestHandler {
113
    /// Reset internal state
114
    fn restart(&mut self) {
9✔
115
        self.microblock = None;
9✔
116
    }
9✔
117

118
    /// Make the response
119
    fn try_handle_request(
9✔
120
        &mut self,
9✔
121
        preamble: HttpRequestPreamble,
9✔
122
        contents: HttpRequestContents,
9✔
123
        node: &mut StacksNodeState,
9✔
124
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
9✔
125
        let microblock = self
9✔
126
            .microblock
9✔
127
            .take()
9✔
128
            .ok_or(NetError::SendError("`microblock` not set".into()))?;
9✔
129
        let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
9✔
130
            Ok(tip) => tip,
9✔
131
            Err(error_resp) => {
×
132
                return error_resp.try_into_contents().map_err(NetError::from);
×
133
            }
134
        };
135
        let data_resp = node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
9✔
136
            let stacks_tip = match StacksChainState::load_staging_block_info(chainstate.db(), &tip) {
9✔
137
                Ok(Some(tip_info)) => tip_info,
9✔
138
                Ok(None) => {
139
                    return Err(StacksHttpResponse::new_error(&preamble, &HttpNotFound::new("No such stacks tip".into())));
×
140
                },
141
                Err(e) => {
×
142
                    return Err(StacksHttpResponse::new_error(&preamble, &HttpServerError::new(format!("Failed to load chain tip: {:?}", &e))));
×
143
                }
144
            };
145

146
            let consensus_hash = &stacks_tip.consensus_hash;
9✔
147
            let block_hash = &stacks_tip.anchored_block_hash;
9✔
148

149
            // make sure we can accept this
150
            let ch_sn = match SortitionDB::get_block_snapshot_consensus(sortdb.conn(), consensus_hash) {
9✔
151
                Ok(Some(sn)) => sn,
9✔
152
                Ok(None) => {
153
                    return Err(StacksHttpResponse::new_error(&preamble, &HttpNotFound::new("No such snapshot for Stacks tip consensus hash".to_string())));
×
154
                }
155
                Err(e) => {
×
156
                    debug!("No block snapshot for consensus hash {}", &consensus_hash);
×
157
                    return Err(StacksHttpResponse::new_error(&preamble, &HttpBadRequest::new_json(ChainError::DBError(e).into_json())));
×
158
                }
159
            };
160

161
            let sort_handle = sortdb.index_handle(&ch_sn.sortition_id);
9✔
162
            let parent_block_snapshot = Relayer::get_parent_stacks_block_snapshot(&sort_handle, consensus_hash, block_hash)
9✔
163
                .map_err(|e| StacksHttpResponse::new_error(&preamble, &HttpServerError::new(format!("Failed to load parent block for Stacks tip: {:?}", &e))))?;
9✔
164

165
            let epoch_id = self.get_stacks_epoch(&preamble, sortdb, parent_block_snapshot.block_height)?.epoch_id;
9✔
166

167
            if !Relayer::static_check_problematic_relayed_microblock(
9✔
168
                chainstate.mainnet,
9✔
169
                epoch_id,
9✔
170
                &microblock
9✔
171
            ) {
9✔
172
                info!("Microblock {} from {}/{} is problematic; will not store or relay it, nor its descendants", &microblock.block_hash(), consensus_hash, &block_hash);
×
173

174
                // NOTE: txid is ignored in chainstate error .into_json()
175
                return Err(StacksHttpResponse::new_error(&preamble, &HttpBadRequest::new_json(ChainError::ProblematicTransaction(Txid([0x00; 32])).into_json())));
×
176
            }
9✔
177

178
            match chainstate.preprocess_streamed_microblock(consensus_hash, block_hash, &microblock) {
9✔
179
                Ok(accepted) => {
9✔
180
                    debug!("{} uploaded microblock {consensus_hash}/{block_hash}-{}",
9✔
181
                           if accepted { "Accepted" } else { "Did not accept" },
×
182
                           &microblock.block_hash()
×
183
                    );
184
                    Ok((accepted, StacksBlockHeader::make_index_block_hash(consensus_hash, block_hash)))
9✔
185
                },
186
                Err(e) => {
×
187
                    debug!("Failed to process microblock {}/{}-{}: {:?}", &consensus_hash, &block_hash, &microblock.block_hash(), &e);
×
188
                    Err(StacksHttpResponse::new_error(&preamble, &HttpBadRequest::new_json(e.into_json())))
×
189
                }
190
            }
191
        });
9✔
192

193
        let (accepted, parent_block_id, data_resp) = match data_resp {
9✔
194
            Ok((accepted, parent_block_id)) => (accepted, parent_block_id, microblock.block_hash()),
9✔
195
            Err(response) => {
×
196
                return response.try_into_contents().map_err(NetError::from);
×
197
            }
198
        };
199

200
        // don't forget to forward this to the p2p network!
201
        if accepted {
9✔
202
            node.set_relay_message(StacksMessageType::Microblocks(MicroblocksData {
9✔
203
                index_anchor_block: parent_block_id,
9✔
204
                microblocks: vec![microblock],
9✔
205
            }));
9✔
206
        }
9✔
207

208
        let preamble = HttpResponsePreamble::ok_json(&preamble);
9✔
209
        let body = HttpResponseContents::try_from_json(&data_resp)?;
9✔
210
        Ok((preamble, body))
9✔
211
    }
9✔
212
}
213

214
/// Decode the HTTP response
215
impl HttpResponse for RPCPostMicroblockRequestHandler {
216
    fn try_parse_response(
×
217
        &self,
×
218
        preamble: &HttpResponsePreamble,
×
219
        body: &[u8],
×
220
    ) -> Result<HttpResponsePayload, Error> {
×
221
        let mblock_hash: BlockHeaderHash = parse_json(preamble, body)?;
×
222
        Ok(HttpResponsePayload::try_from_json(mblock_hash)?)
×
223
    }
×
224
}
225

226
impl StacksHttpRequest {
227
    /// Make a new post-microblock request
228
    pub fn new_post_microblock(
×
229
        host: PeerHost,
×
230
        mblock: StacksMicroblock,
×
231
        tip_req: TipRequest,
×
232
    ) -> StacksHttpRequest {
×
233
        StacksHttpRequest::new_for_peer(
×
234
            host,
×
235
            "POST".into(),
×
236
            "/v2/microblocks".into(),
×
237
            HttpRequestContents::new()
×
238
                .payload_stacks(&mblock)
×
239
                .for_tip(tip_req),
×
240
        )
241
        .expect("FATAL: failed to construct request from infallible data")
×
242
    }
×
243
}
244

245
impl StacksHttpResponse {
246
    pub fn decode_stacks_microblock_response(self) -> Result<BlockHeaderHash, NetError> {
×
247
        let contents = self.get_http_payload_ok()?;
×
248
        let response_json: serde_json::Value = contents.try_into()?;
×
249
        let result: BlockHeaderHash = serde_json::from_value(response_json)
×
250
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
×
251
        Ok(result)
×
252
    }
×
253
}
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