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

stacks-network / stacks-core / 23757460166

30 Mar 2026 05:08PM UTC coverage: 46.858% (-38.9%) from 85.712%
23757460166

Pull #7058

github

cb13d9
web-flow
Merge 5b10bfbb9 into 7a9774e50
Pull Request #7058: test: fix flakiness in `check_capitulate_miner_view`

101943 of 217556 relevant lines covered (46.86%)

12736976.58 hits per line

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

66.67
/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 {
985,120✔
56
        Self {
985,120✔
57
            contract_identifier: None,
985,120✔
58
            map_name: None,
985,120✔
59
            key: None,
985,120✔
60
        }
985,120✔
61
    }
985,120✔
62
}
63

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

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

78
    fn metrics_identifier(&self) -> &str {
36✔
79
        "/v2/map_entry/:principal/:contract_name/:map_name"
36✔
80
    }
36✔
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(
36✔
86
        &mut self,
36✔
87
        preamble: &HttpRequestPreamble,
36✔
88
        captures: &Captures,
36✔
89
        query: Option<&str>,
36✔
90
        body: &[u8],
36✔
91
    ) -> Result<HttpRequestContents, Error> {
36✔
92
        let content_len = preamble.get_content_length();
36✔
93
        if !(content_len > 0 && content_len < BOUND_VALUE_SERIALIZATION_HEX) {
36✔
94
            return Err(Error::DecodeError(format!(
×
95
                "Invalid Http request: invalid body length for GetMapEntry ({})",
×
96
                content_len
×
97
            )));
×
98
        }
36✔
99

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

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

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

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

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

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

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

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

153
        let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
36✔
154
            Ok(tip) => tip,
36✔
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();
36✔
160
        let key =
36✔
161
            ClarityDatabase::make_key_for_data_map_entry(&contract_identifier, &map_name, &key)
36✔
162
                .map_err(|e| NetError::SerializeError(format!("{:?}", &e)))?;
36✔
163
        let none_response = Value::none()
36✔
164
            .serialize_to_hex()
36✔
165
            .map_err(|e| NetError::SerializeError(format!("{:?}", &e)))?;
36✔
166

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

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

203
        let data_resp = match data_resp {
36✔
204
            Ok(Some(data)) => data,
36✔
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);
36✔
216
        let body = HttpResponseContents::try_from_json(&data_resp)?;
36✔
217
        Ok((preamble, body))
36✔
218
    }
36✔
219
}
220

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

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

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