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

stacks-network / stacks-core / 23943270448

03 Apr 2026 10:32AM UTC coverage: 77.559% (-8.2%) from 85.712%
23943270448

Pull #7077

github

52f01d
web-flow
Merge fa3f939ed into c529ad924
Pull Request #7077: feat: add burnchain DB copy and validation

3654 of 4220 new or added lines in 18 files covered. (86.59%)

19324 existing lines in 182 files now uncovered.

171991 of 221755 relevant lines covered (77.56%)

7658447.9 hits per line

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

88.89
/stackslib/src/net/api/getcontractsrc.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::clarity::ClarityConnection;
18
use clarity::vm::database::clarity_store::{make_contract_hash_key, ContractCommitment};
19
use clarity::vm::representations::{CONTRACT_NAME_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING};
20
use clarity::vm::types::QualifiedContractIdentifier;
21
use clarity::vm::ContractName;
22
use regex::{Captures, Regex};
23
use stacks_common::types::chainstate::StacksAddress;
24
use stacks_common::types::net::PeerHost;
25
use stacks_common::util::hash::to_hex;
26

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

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

47
#[derive(Clone)]
48
pub struct RPCGetContractSrcRequestHandler {
49
    pub contract_identifier: Option<QualifiedContractIdentifier>,
50
}
51

52
impl RPCGetContractSrcRequestHandler {
53
    pub fn new() -> Self {
1,625✔
54
        Self {
1,625✔
55
            contract_identifier: None,
1,625✔
56
        }
1,625✔
57
    }
1,625✔
58
}
59

60
/// Decode the HTTP request
61
impl HttpRequest for RPCGetContractSrcRequestHandler {
62
    fn verb(&self) -> &'static str {
1,624✔
63
        "GET"
1,624✔
64
    }
1,624✔
65

66
    fn path_regex(&self) -> Regex {
3,249✔
67
        Regex::new(&format!(
3,249✔
68
            "^/v2/contracts/source/(?P<address>{})/(?P<contract>{})$",
3,249✔
69
            *STANDARD_PRINCIPAL_REGEX_STRING, *CONTRACT_NAME_REGEX_STRING
3,249✔
70
        ))
3,249✔
71
        .unwrap()
3,249✔
72
    }
3,249✔
73

74
    fn metrics_identifier(&self) -> &str {
3✔
75
        "/v2/contracts/source/:principal/:contract_name"
3✔
76
    }
3✔
77

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

93
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
4✔
94
        self.contract_identifier = Some(contract_identifier);
4✔
95

96
        let contents = HttpRequestContents::new().query_string(query);
4✔
97
        Ok(contents)
4✔
98
    }
4✔
99
}
100

101
/// Handle the HTTP request
102
impl RPCRequestHandler for RPCGetContractSrcRequestHandler {
103
    /// Reset internal state
104
    fn restart(&mut self) {
4✔
105
        self.contract_identifier = None;
4✔
106
    }
4✔
107

108
    /// Make the response
109
    fn try_handle_request(
3✔
110
        &mut self,
3✔
111
        preamble: HttpRequestPreamble,
3✔
112
        contents: HttpRequestContents,
3✔
113
        node: &mut StacksNodeState,
3✔
114
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
3✔
115
        let contract_identifier = self.contract_identifier.take().ok_or(NetError::SendError(
3✔
116
            "`contract_identifier` not set".to_string(),
3✔
117
        ))?;
3✔
118
        let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
3✔
119
            Ok(tip) => tip,
3✔
120
            Err(error_resp) => {
×
121
                return error_resp.try_into_contents().map_err(NetError::from);
×
122
            }
123
        };
124
        let with_proof = contents.get_with_proof();
3✔
125

126
        let data_resp =
3✔
127
            node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
3✔
128
                chainstate.maybe_read_only_clarity_tx(
3✔
129
                    &sortdb.index_handle_at_block(chainstate, &tip)?,
3✔
130
                    &tip,
3✔
131
                    |clarity_tx| {
3✔
132
                        clarity_tx.with_clarity_db_readonly(|db| {
3✔
133
                            let source = db.get_contract_src(&contract_identifier)?;
3✔
134
                            let contract_commit_key = make_contract_hash_key(&contract_identifier);
2✔
135
                            let (contract_commit, proof) = if with_proof {
2✔
136
                                db.get_data_with_proof::<ContractCommitment>(&contract_commit_key)
2✔
137
                                    .ok()
2✔
138
                                    .flatten()
2✔
139
                                    .map(|(a, b)| (a, Some(format!("0x{}", to_hex(&b)))))?
2✔
140
                            } else {
UNCOV
141
                                db.get_data::<ContractCommitment>(&contract_commit_key)
×
UNCOV
142
                                    .ok()
×
UNCOV
143
                                    .flatten()
×
UNCOV
144
                                    .map(|a| (a, None))?
×
145
                            };
146

147
                            let publish_height = contract_commit.block_height;
2✔
148
                            Some(ContractSrcResponse {
2✔
149
                                source,
2✔
150
                                publish_height,
2✔
151
                                marf_proof: proof,
2✔
152
                            })
2✔
153
                        })
3✔
154
                    },
3✔
155
                )
156
            });
3✔
157

158
        let data_resp = match data_resp {
3✔
159
            Ok(Some(Some(data))) => data,
2✔
160
            Ok(Some(None)) => {
161
                return StacksHttpResponse::new_error(
1✔
162
                    &preamble,
1✔
163
                    &HttpNotFound::new("No contract source data found".to_string()),
1✔
164
                )
165
                .try_into_contents()
1✔
166
                .map_err(NetError::from);
1✔
167
            }
168
            Ok(None) | Err(_) => {
169
                return StacksHttpResponse::new_error(
×
170
                    &preamble,
×
171
                    &HttpNotFound::new("Chain tip not found".to_string()),
×
172
                )
173
                .try_into_contents()
×
174
                .map_err(NetError::from);
×
175
            }
176
        };
177

178
        let preamble = HttpResponsePreamble::ok_json(&preamble);
2✔
179
        let body = HttpResponseContents::try_from_json(&data_resp)?;
2✔
180
        Ok((preamble, body))
2✔
181
    }
3✔
182
}
183

184
/// Decode the HTTP response
185
impl HttpResponse for RPCGetContractSrcRequestHandler {
186
    fn try_parse_response(
2✔
187
        &self,
2✔
188
        preamble: &HttpResponsePreamble,
2✔
189
        body: &[u8],
2✔
190
    ) -> Result<HttpResponsePayload, Error> {
2✔
191
        let contract_src: ContractSrcResponse = parse_json(preamble, body)?;
2✔
192
        Ok(HttpResponsePayload::try_from_json(contract_src)?)
2✔
193
    }
2✔
194
}
195

196
impl StacksHttpRequest {
197
    /// Make a new request for a contract's source code
198
    pub fn new_getcontractsrc(
4✔
199
        host: PeerHost,
4✔
200
        contract_addr: StacksAddress,
4✔
201
        contract_name: ContractName,
4✔
202
        tip_req: TipRequest,
4✔
203
        with_proof: bool,
4✔
204
    ) -> StacksHttpRequest {
4✔
205
        StacksHttpRequest::new_for_peer(
4✔
206
            host,
4✔
207
            "GET".into(),
4✔
208
            format!("/v2/contracts/source/{}/{}", &contract_addr, &contract_name),
4✔
209
            HttpRequestContents::new()
4✔
210
                .for_tip(tip_req)
4✔
211
                .query_arg("proof".into(), if with_proof { "1" } else { "0" }.into()),
4✔
212
        )
213
        .expect("FATAL: failed to construct request from infallible data")
4✔
214
    }
4✔
215
}
216

217
impl StacksHttpResponse {
218
    pub fn decode_contract_src_response(self) -> Result<ContractSrcResponse, NetError> {
2✔
219
        let contents = self.get_http_payload_ok()?;
2✔
220
        let contents_json: serde_json::Value = contents.try_into()?;
2✔
221
        let resp: ContractSrcResponse = serde_json::from_value(contents_json)
2✔
222
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
2✔
223
        Ok(resp)
2✔
224
    }
2✔
225
}
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