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

stacks-network / stacks-core / 24078323220

07 Apr 2026 11:10AM UTC coverage: 77.398% (-8.3%) from 85.712%
24078323220

Pull #7046

github

35558f
web-flow
Merge 4fca9f140 into 5242f5457
Pull Request #7046: feat: add alerting for unreachable errors

134 of 142 new or added lines in 4 files covered. (94.37%)

19497 existing lines in 183 files now uncovered.

168614 of 217854 relevant lines covered (77.4%)

7582881.09 hits per line

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

86.06
/stackslib/src/net/api/getmapentry.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::ast::parser::v1::CLARITY_NAME_REGEX;
18
use clarity::vm::clarity::ClarityConnection;
19
use clarity::vm::database::ClarityDatabase;
20
use clarity::vm::representations::{CONTRACT_NAME_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING};
21
use clarity::vm::types::{QualifiedContractIdentifier, BOUND_VALUE_SERIALIZATION_HEX};
22
use clarity::vm::{ClarityName, ContractName, Value};
23
use regex::{Captures, Regex};
24
use stacks_common::types::chainstate::StacksAddress;
25
use stacks_common::types::net::PeerHost;
26
use stacks_common::util::hash::to_hex;
27

28
use crate::net::http::{
29
    parse_json, Error, HttpContentType, HttpNotFound, HttpRequest, HttpRequestContents,
30
    HttpRequestPreamble, HttpResponse, HttpResponseContents, HttpResponsePayload,
31
    HttpResponsePreamble,
32
};
33
use crate::net::httpcore::{
34
    request, HttpRequestContentsExtensions as _, RPCRequestHandler, StacksHttpRequest,
35
    StacksHttpResponse,
36
};
37
use crate::net::{Error as NetError, StacksNodeState, TipRequest};
38

39
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40
pub struct MapEntryResponse {
41
    pub data: String,
42
    #[serde(rename = "proof")]
43
    #[serde(default)]
44
    #[serde(skip_serializing_if = "Option::is_none")]
45
    pub marf_proof: Option<String>,
46
}
47

48
#[derive(Clone)]
49
pub struct RPCGetMapEntryRequestHandler {
50
    pub contract_identifier: Option<QualifiedContractIdentifier>,
51
    pub map_name: Option<ClarityName>,
52
    pub key: Option<Value>,
53
}
54
impl RPCGetMapEntryRequestHandler {
55
    pub fn new() -> Self {
1,661✔
56
        Self {
1,661✔
57
            contract_identifier: None,
1,661✔
58
            map_name: None,
1,661✔
59
            key: None,
1,661✔
60
        }
1,661✔
61
    }
1,661✔
62
}
63

64
/// Decode the HTTP request
65
impl HttpRequest for RPCGetMapEntryRequestHandler {
66
    fn verb(&self) -> &'static str {
1,660✔
67
        "POST"
1,660✔
68
    }
1,660✔
69

70
    fn path_regex(&self) -> Regex {
3,321✔
71
        Regex::new(&format!(
3,321✔
72
            "^/v2/map_entry/(?P<address>{})/(?P<contract>{})/(?P<map>{})$",
3,321✔
73
            *STANDARD_PRINCIPAL_REGEX_STRING, *CONTRACT_NAME_REGEX_STRING, *CLARITY_NAME_REGEX
3,321✔
74
        ))
3,321✔
75
        .unwrap()
3,321✔
76
    }
3,321✔
77

78
    fn metrics_identifier(&self) -> &str {
4✔
79
        "/v2/map_entry/:principal/:contract_name/:map_name"
4✔
80
    }
4✔
81

82
    /// Try to decode this request.
83
    /// The body must be a hex string, encoded as a JSON string.
84
    /// So, something like `"123abc"`.  It encodes the map key as a serialized Clarity value.
85
    fn try_parse_request(
5✔
86
        &mut self,
5✔
87
        preamble: &HttpRequestPreamble,
5✔
88
        captures: &Captures,
5✔
89
        query: Option<&str>,
5✔
90
        body: &[u8],
5✔
91
    ) -> Result<HttpRequestContents, Error> {
5✔
92
        let content_len = preamble.get_content_length();
5✔
93
        if !(content_len > 0 && content_len < BOUND_VALUE_SERIALIZATION_HEX) {
5✔
94
            return Err(Error::DecodeError(format!(
×
95
                "Invalid Http request: invalid body length for GetMapEntry ({})",
×
96
                content_len
×
97
            )));
×
98
        }
5✔
99

100
        if preamble.content_type != Some(HttpContentType::JSON) {
5✔
101
            return Err(Error::DecodeError(
×
102
                "Invalid content-type: expected application/json".into(),
×
103
            ));
×
104
        }
5✔
105

106
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
5✔
107
        let map_name = request::get_clarity_name(captures, "map")?;
5✔
108

109
        let mut body_ptr = body;
5✔
110
        let value_hex: String = serde_json::from_reader(&mut body_ptr)
5✔
111
            .map_err(|_e| Error::DecodeError("Failed to parse JSON body".into()))?;
5✔
112

113
        let value = Value::try_deserialize_hex_untyped(&value_hex)
5✔
114
            .map_err(|_e| Error::DecodeError("Failed to deserialize key value".into()))?;
5✔
115

116
        self.contract_identifier = Some(contract_identifier);
5✔
117
        self.map_name = Some(map_name);
5✔
118
        self.key = Some(value);
5✔
119

120
        Ok(HttpRequestContents::new().query_string(query))
5✔
121
    }
5✔
122
}
123

124
/// Handle the HTTP request
125
impl RPCRequestHandler for RPCGetMapEntryRequestHandler {
126
    /// Reset internal state
127
    fn restart(&mut self) {
5✔
128
        self.contract_identifier = None;
5✔
129
        self.map_name = None;
5✔
130
        self.key = None;
5✔
131
    }
5✔
132

133
    /// Make the response
134
    fn try_handle_request(
4✔
135
        &mut self,
4✔
136
        preamble: HttpRequestPreamble,
4✔
137
        contents: HttpRequestContents,
4✔
138
        node: &mut StacksNodeState,
4✔
139
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
4✔
140
        let contract_identifier = self
4✔
141
            .contract_identifier
4✔
142
            .take()
4✔
143
            .ok_or(NetError::SendError("`contract_identifier` not set".into()))?;
4✔
144
        let map_name = self
4✔
145
            .map_name
4✔
146
            .take()
4✔
147
            .ok_or(NetError::SendError("`map_name` not set".into()))?;
4✔
148
        let key = self
4✔
149
            .key
4✔
150
            .take()
4✔
151
            .ok_or(NetError::SendError("`key` not set".into()))?;
4✔
152

153
        let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
4✔
154
            Ok(tip) => tip,
4✔
155
            Err(error_resp) => {
×
156
                return error_resp.try_into_contents().map_err(NetError::from);
×
157
            }
158
        };
159
        let with_proof = contents.get_with_proof();
4✔
160
        let key =
4✔
161
            ClarityDatabase::make_key_for_data_map_entry(&contract_identifier, &map_name, &key)
4✔
162
                .map_err(|e| NetError::SerializeError(format!("{:?}", &e)))?;
4✔
163
        let none_response = Value::none()
4✔
164
            .serialize_to_hex()
4✔
165
            .map_err(|e| NetError::SerializeError(format!("{:?}", &e)))?;
4✔
166

167
        let data_resp =
4✔
168
            node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
4✔
169
                chainstate.maybe_read_only_clarity_tx(
4✔
170
                    &sortdb.index_handle_at_block(chainstate, &tip)?,
4✔
171
                    &tip,
4✔
172
                    |clarity_tx| {
4✔
173
                        clarity_tx.with_clarity_db_readonly(|clarity_db| {
4✔
174
                            let (value_hex, marf_proof): (String, _) = if with_proof {
4✔
175
                                clarity_db
4✔
176
                                    .get_data_with_proof(&key)
4✔
177
                                    .ok()
4✔
178
                                    .flatten()
4✔
179
                                    .map(|(a, b)| (a, Some(format!("0x{}", to_hex(&b)))))
4✔
180
                                    .unwrap_or_else(|| {
4✔
181
                                        test_debug!("No value for '{}' in {}", &key, tip);
2✔
182
                                        (none_response, Some("".into()))
2✔
183
                                    })
2✔
184
                            } else {
UNCOV
185
                                clarity_db
×
UNCOV
186
                                    .get_data(&key)
×
UNCOV
187
                                    .ok()
×
UNCOV
188
                                    .flatten()
×
UNCOV
189
                                    .map(|a| (a, None))
×
UNCOV
190
                                    .unwrap_or_else(|| {
×
191
                                        test_debug!("No value for '{}' in {}", &key, tip);
×
192
                                        (none_response, None)
×
193
                                    })
×
194
                            };
195

196
                            let data = format!("0x{}", value_hex);
4✔
197
                            MapEntryResponse { data, marf_proof }
4✔
198
                        })
4✔
199
                    },
4✔
200
                )
201
            });
4✔
202

203
        let data_resp = match data_resp {
4✔
204
            Ok(Some(data)) => data,
4✔
205
            Ok(None) | Err(_) => {
206
                return StacksHttpResponse::new_error(
×
207
                    &preamble,
×
208
                    &HttpNotFound::new("Chain tip not found".to_string()),
×
209
                )
210
                .try_into_contents()
×
211
                .map_err(NetError::from);
×
212
            }
213
        };
214

215
        let preamble = HttpResponsePreamble::ok_json(&preamble);
4✔
216
        let body = HttpResponseContents::try_from_json(&data_resp)?;
4✔
217
        Ok((preamble, body))
4✔
218
    }
4✔
219
}
220

221
/// Decode the HTTP response
222
impl HttpResponse for RPCGetMapEntryRequestHandler {
223
    fn try_parse_response(
4✔
224
        &self,
4✔
225
        preamble: &HttpResponsePreamble,
4✔
226
        body: &[u8],
4✔
227
    ) -> Result<HttpResponsePayload, Error> {
4✔
228
        let map_entry: MapEntryResponse = parse_json(preamble, body)?;
4✔
229
        Ok(HttpResponsePayload::try_from_json(map_entry)?)
4✔
230
    }
4✔
231
}
232

233
impl StacksHttpRequest {
234
    /// Make a new request for a data map
235
    pub fn new_getmapentry(
5✔
236
        host: PeerHost,
5✔
237
        contract_addr: StacksAddress,
5✔
238
        contract_name: ContractName,
5✔
239
        map_name: ClarityName,
5✔
240
        key: Value,
5✔
241
        tip_req: TipRequest,
5✔
242
        with_proof: bool,
5✔
243
    ) -> StacksHttpRequest {
5✔
244
        StacksHttpRequest::new_for_peer(
5✔
245
            host,
5✔
246
            "POST".into(),
5✔
247
            format!(
5✔
248
                "/v2/map_entry/{}/{}/{}",
249
                &contract_addr, &contract_name, &map_name
5✔
250
            ),
251
            HttpRequestContents::new()
5✔
252
                .for_tip(tip_req)
5✔
253
                .query_arg("proof".into(), if with_proof { "1" } else { "0" }.into())
5✔
254
                .payload_json(serde_json::Value::String(
5✔
255
                    key.serialize_to_hex()
5✔
256
                        .expect("FATAL: invalid key could not be serialized"),
5✔
257
                )),
5✔
258
        )
259
        .expect("FATAL: failed to construct request from infallible data")
5✔
260
    }
5✔
261
}
262

263
impl StacksHttpResponse {
264
    pub fn decode_map_entry_response(self) -> Result<MapEntryResponse, NetError> {
4✔
265
        let contents = self.get_http_payload_ok()?;
4✔
266
        let contents_json: serde_json::Value = contents.try_into()?;
4✔
267
        let resp: MapEntryResponse = serde_json::from_value(contents_json)
4✔
268
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
4✔
269
        Ok(resp)
4✔
270
    }
4✔
271
}
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