• 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

11.96
/stackslib/src/net/api/getattachment.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::types::net::PeerHost;
19
use stacks_common::util::hash::Hash160;
20

21
use crate::net::atlas::GetAttachmentResponse;
22
use crate::net::http::{
23
    parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble,
24
    HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble,
25
};
26
use crate::net::httpcore::{RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
27
use crate::net::{Error as NetError, StacksNodeState};
28

29
#[derive(Clone)]
30
pub struct RPCGetAttachmentRequestHandler {
31
    pub attachment_hash: Option<Hash160>,
32
}
33

34
impl RPCGetAttachmentRequestHandler {
35
    pub fn new() -> Self {
1,059,041✔
36
        Self {
1,059,041✔
37
            attachment_hash: None,
1,059,041✔
38
        }
1,059,041✔
39
    }
1,059,041✔
40
}
41

42
/// Decode the HTTP request
43
impl HttpRequest for RPCGetAttachmentRequestHandler {
44
    fn verb(&self) -> &'static str {
1,059,041✔
45
        "GET"
1,059,041✔
46
    }
1,059,041✔
47

48
    fn path_regex(&self) -> Regex {
2,118,082✔
49
        Regex::new(r#"^/v2/attachments/(?P<attachment_hash>[0-9a-f]{40})$"#).unwrap()
2,118,082✔
50
    }
2,118,082✔
51

52
    fn metrics_identifier(&self) -> &str {
×
53
        "/v2/attachments/:hash"
×
54
    }
×
55

56
    /// Try to decode this request.
57
    /// There's nothing to load here, so just make sure the request is well-formed.
58
    fn try_parse_request(
×
59
        &mut self,
×
60
        preamble: &HttpRequestPreamble,
×
61
        captures: &Captures,
×
62
        query: Option<&str>,
×
63
        _body: &[u8],
×
64
    ) -> Result<HttpRequestContents, Error> {
×
65
        if preamble.get_content_length() != 0 {
×
66
            return Err(Error::DecodeError(
×
67
                "Invalid Http request: expected 0-length body".to_string(),
×
68
            ));
×
69
        }
×
70

71
        let attachment_hash_str = captures
×
72
            .name("attachment_hash")
×
73
            .ok_or(Error::DecodeError(
×
74
                "Failed to match path to attachment_hash group".to_string(),
×
75
            ))?
×
76
            .as_str();
×
77

78
        self.attachment_hash = Some(
79
            Hash160::from_hex(attachment_hash_str)
×
80
                .map_err(|_| Error::DecodeError("Failed to decode `attachment_hash`".into()))?,
×
81
        );
82

83
        Ok(HttpRequestContents::new().query_string(query))
×
84
    }
×
85
}
86

87
impl RPCRequestHandler for RPCGetAttachmentRequestHandler {
88
    /// Reset internal state
89
    fn restart(&mut self) {
×
90
        self.attachment_hash = None;
×
91
    }
×
92

93
    fn try_handle_request(
×
94
        &mut self,
×
95
        preamble: HttpRequestPreamble,
×
96
        _contents: HttpRequestContents,
×
97
        node: &mut StacksNodeState,
×
98
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
×
99
        let attachment_hash = self
×
100
            .attachment_hash
×
101
            .take()
×
102
            .ok_or(NetError::SendError("Missing `attachment_hash`".into()))?;
×
103

104
        let attachment_res = node.with_node_state(
×
105
            |network, _sortdb, _chainstate, _mempool, _rpc_args| match network
×
106
                .get_atlasdb()
×
107
                .find_attachment(&attachment_hash)
×
108
            {
109
                Ok(Some(attachment)) => Ok(GetAttachmentResponse { attachment }),
×
110
                _ => {
111
                    let msg = "Unable to find attachment".to_string();
×
112
                    warn!("{msg}");
×
113
                    Err(StacksHttpResponse::new_error(
×
114
                        &preamble,
×
115
                        &HttpNotFound::new(msg),
×
116
                    ))
×
117
                }
118
            },
×
119
        );
120
        let attachment = match attachment_res {
×
121
            Ok(attachment) => attachment,
×
122
            Err(response) => {
×
123
                return response.try_into_contents().map_err(NetError::from);
×
124
            }
125
        };
126

127
        let preamble = HttpResponsePreamble::ok_json(&preamble);
×
128
        let body = HttpResponseContents::try_from_json(&attachment)?;
×
129
        Ok((preamble, body))
×
130
    }
×
131
}
132

133
/// Decode the HTTP response
134
impl HttpResponse for RPCGetAttachmentRequestHandler {
135
    fn try_parse_response(
×
136
        &self,
×
137
        preamble: &HttpResponsePreamble,
×
138
        body: &[u8],
×
139
    ) -> Result<HttpResponsePayload, Error> {
×
140
        let pages: GetAttachmentResponse = parse_json(preamble, body)?;
×
141
        Ok(HttpResponsePayload::try_from_json(pages)?)
×
142
    }
×
143
}
144

145
impl StacksHttpRequest {
146
    /// Make a new request for an attachment
147
    pub fn new_getattachment(host: PeerHost, attachment_id: Hash160) -> StacksHttpRequest {
×
148
        StacksHttpRequest::new_for_peer(
×
149
            host,
×
150
            "GET".into(),
×
151
            format!("/v2/attachments/{}", &attachment_id),
×
152
            HttpRequestContents::new(),
×
153
        )
154
        .expect("FATAL: failed to construct request from infallible data")
×
155
    }
×
156
}
157

158
impl StacksHttpResponse {
159
    pub fn decode_atlas_get_attachment(self) -> Result<GetAttachmentResponse, NetError> {
×
160
        let contents = self.get_http_payload_ok()?;
×
161
        let contents_json: serde_json::Value = contents.try_into()?;
×
162
        let resp: GetAttachmentResponse = serde_json::from_value(contents_json)
×
163
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
×
164
        Ok(resp)
×
165
    }
×
166
}
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