Skip to content

Commit 26cf0da

Browse files
lgalabruhugoclrd
authored andcommitted
feat: given a MARF key constructed client side, retrieve clarity value
1 parent f160aaf commit 26cf0da

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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 std::io::{Read, Write};
18+
19+
use clarity::vm::ast::parser::v1::CLARITY_NAME_REGEX;
20+
use clarity::vm::clarity::ClarityConnection;
21+
use clarity::vm::costs::LimitedCostTracker;
22+
use clarity::vm::database::{ClarityDatabase, STXBalance, StoreType};
23+
use clarity::vm::representations::{
24+
CONTRACT_NAME_REGEX_STRING, PRINCIPAL_DATA_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING,
25+
};
26+
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StandardPrincipalData};
27+
use clarity::vm::{ClarityName, ClarityVersion, ContractName};
28+
use regex::{Captures, Regex};
29+
use stacks_common::types::chainstate::{StacksAddress, StacksBlockId};
30+
use stacks_common::types::net::PeerHost;
31+
use stacks_common::types::Address;
32+
use stacks_common::util::hash::{to_hex, Sha256Sum};
33+
34+
use crate::burnchains::Burnchain;
35+
use crate::chainstate::burn::db::sortdb::SortitionDB;
36+
use crate::chainstate::stacks::db::StacksChainState;
37+
use crate::chainstate::stacks::Error as ChainError;
38+
use crate::core::mempool::MemPoolDB;
39+
use crate::net::http::{
40+
parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble,
41+
HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble, HttpServerError,
42+
};
43+
use crate::net::httpcore::{
44+
request, HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp,
45+
StacksHttpRequest, StacksHttpResponse,
46+
};
47+
use crate::net::p2p::PeerNetwork;
48+
use crate::net::{Error as NetError, StacksNodeState, TipRequest};
49+
use crate::util_lib::boot::boot_code_id;
50+
use crate::util_lib::db::Error as DBError;
51+
52+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53+
pub struct ClarityMarfValueResponse {
54+
pub data: String,
55+
#[serde(rename = "proof")]
56+
#[serde(default)]
57+
#[serde(skip_serializing_if = "Option::is_none")]
58+
pub marf_proof: Option<String>,
59+
}
60+
61+
#[derive(Clone)]
62+
pub struct RPCGetClarityMarfValueRequestHandler {
63+
pub clarity_marf_key: Option<String>,
64+
}
65+
impl RPCGetClarityMarfValueRequestHandler {
66+
pub fn new() -> Self {
67+
Self {
68+
clarity_marf_key: None,
69+
}
70+
}
71+
}
72+
73+
/// Decode the HTTP request
74+
impl HttpRequest for RPCGetClarityMarfValueRequestHandler {
75+
fn verb(&self) -> &'static str {
76+
"GET"
77+
}
78+
79+
fn path_regex(&self) -> Regex {
80+
// TODO: write regex validating the following patterns
81+
// format!("vm::{}::{}::{}", contract_identifier, data as u8, var_name)
82+
// format!("vm::{}::{}::{}::{}", contract_identifier, data as u8, var_name, key_value)
83+
// format!("vm-metadata::{}::{}", data as u8, var_name)
84+
// format!("vm-epoch::epoch-version")
85+
86+
Regex::new(&format!("^/v2/clarity_marf_value/(?<clarity_marf_key>.*)$",)).unwrap()
87+
}
88+
89+
fn metrics_identifier(&self) -> &str {
90+
"/v2/clarity_marf_value/:clarity_marf_key"
91+
}
92+
93+
/// Try to decode this request.
94+
/// There's nothing to load here, so just make sure the request is well-formed.
95+
fn try_parse_request(
96+
&mut self,
97+
preamble: &HttpRequestPreamble,
98+
captures: &Captures,
99+
query: Option<&str>,
100+
_body: &[u8],
101+
) -> Result<HttpRequestContents, Error> {
102+
if preamble.get_content_length() != 0 {
103+
return Err(Error::DecodeError(
104+
"Invalid Http request: expected 0-length body".to_string(),
105+
));
106+
}
107+
108+
let marf_key = request::get_marf_key(captures, "clarity_marf_key")?;
109+
110+
self.clarity_marf_key = Some(marf_key);
111+
112+
let contents = HttpRequestContents::new().query_string(query);
113+
Ok(contents)
114+
}
115+
}
116+
117+
/// Handle the HTTP request
118+
impl RPCRequestHandler for RPCGetClarityMarfValueRequestHandler {
119+
/// Reset internal state
120+
fn restart(&mut self) {
121+
self.clarity_marf_key = None;
122+
}
123+
124+
/// Make the response
125+
fn try_handle_request(
126+
&mut self,
127+
preamble: HttpRequestPreamble,
128+
contents: HttpRequestContents,
129+
node: &mut StacksNodeState,
130+
) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
131+
let clarity_marf_key = self.clarity_marf_key.take().ok_or(NetError::SendError(
132+
"`clarity_marf_key` not set".to_string(),
133+
))?;
134+
135+
let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
136+
Ok(tip) => tip,
137+
Err(error_resp) => {
138+
return error_resp.try_into_contents().map_err(NetError::from);
139+
}
140+
};
141+
142+
let with_proof = contents.get_with_proof();
143+
144+
let data_opt = node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
145+
chainstate.maybe_read_only_clarity_tx(&sortdb.index_conn(), &tip, |clarity_tx| {
146+
clarity_tx.with_clarity_db_readonly(|clarity_db| {
147+
let (value_hex, marf_proof): (String, _) = if with_proof {
148+
clarity_db
149+
.get_data_with_proof(&clarity_marf_key)
150+
.ok()
151+
.flatten()
152+
.map(|(a, b)| (a, Some(format!("0x{}", to_hex(&b)))))?
153+
} else {
154+
clarity_db
155+
.get_data(&clarity_marf_key)
156+
.ok()
157+
.flatten()
158+
.map(|a| (a, None))?
159+
};
160+
161+
let data = format!("0x{}", value_hex);
162+
Some(ClarityMarfValueResponse { data, marf_proof })
163+
})
164+
})
165+
});
166+
167+
let data_resp = match data_opt {
168+
Ok(Some(Some(data))) => data,
169+
Ok(Some(None)) => {
170+
return StacksHttpResponse::new_error(
171+
&preamble,
172+
&HttpNotFound::new("Data var not found".to_string()),
173+
)
174+
.try_into_contents()
175+
.map_err(NetError::from);
176+
}
177+
Ok(None) | Err(_) => {
178+
return StacksHttpResponse::new_error(
179+
&preamble,
180+
&HttpNotFound::new("Chain tip not found".to_string()),
181+
)
182+
.try_into_contents()
183+
.map_err(NetError::from);
184+
}
185+
};
186+
187+
let mut preamble = HttpResponsePreamble::ok_json(&preamble);
188+
preamble.set_canonical_stacks_tip_height(Some(node.canonical_stacks_tip_height()));
189+
let body = HttpResponseContents::try_from_json(&data_resp)?;
190+
Ok((preamble, body))
191+
}
192+
}
193+
194+
/// Decode the HTTP response
195+
impl HttpResponse for RPCGetClarityMarfValueRequestHandler {
196+
fn try_parse_response(
197+
&self,
198+
preamble: &HttpResponsePreamble,
199+
body: &[u8],
200+
) -> Result<HttpResponsePayload, Error> {
201+
let marf_value: ClarityMarfValueResponse = parse_json(preamble, body)?;
202+
Ok(HttpResponsePayload::try_from_json(marf_value)?)
203+
}
204+
}
205+
206+
impl StacksHttpRequest {
207+
/// Make a new request for a data var
208+
pub fn new_getclaritymarfvalue(
209+
host: PeerHost,
210+
clarity_marf_key: String,
211+
tip_req: TipRequest,
212+
with_proof: bool,
213+
) -> StacksHttpRequest {
214+
StacksHttpRequest::new_for_peer(
215+
host,
216+
"GET".into(),
217+
format!("/v2/clarity_marf_value/{}", &clarity_marf_key),
218+
HttpRequestContents::new()
219+
.for_tip(tip_req)
220+
.query_arg("proof".into(), if with_proof { "1" } else { "0" }.into()),
221+
)
222+
.expect("FATAL: failed to construct request from infallible data")
223+
}
224+
}
225+
226+
impl StacksHttpResponse {
227+
pub fn decode_clarity_marf_value_response(self) -> Result<ClarityMarfValueResponse, NetError> {
228+
let contents = self.get_http_payload_ok()?;
229+
let contents_json: serde_json::Value = contents.try_into()?;
230+
let resp: ClarityMarfValueResponse = serde_json::from_value(contents_json)
231+
.map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
232+
Ok(resp)
233+
}
234+
}

stackslib/src/net/api/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub mod getattachment;
4242
pub mod getattachmentsinv;
4343
pub mod getblock;
4444
pub mod getblock_v3;
45+
pub mod getclaritymarfvalue;
4546
pub mod getconstantval;
4647
pub mod getcontractabi;
4748
pub mod getcontractsrc;
@@ -90,6 +91,9 @@ impl StacksHttp {
9091
self.register_rpc_endpoint(getattachmentsinv::RPCGetAttachmentsInvRequestHandler::new());
9192
self.register_rpc_endpoint(getblock::RPCBlocksRequestHandler::new());
9293
self.register_rpc_endpoint(getblock_v3::RPCNakamotoBlockRequestHandler::new());
94+
self.register_rpc_endpoint(
95+
getclaritymarfvalue::RPCGetClarityMarfValueRequestHandler::new(),
96+
);
9397
self.register_rpc_endpoint(getconstantval::RPCGetConstantValRequestHandler::new());
9498
self.register_rpc_endpoint(getcontractabi::RPCGetContractAbiRequestHandler::new());
9599
self.register_rpc_endpoint(getcontractsrc::RPCGetContractSrcRequestHandler::new());

stackslib/src/net/httpcore.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,17 @@ pub mod request {
247247
Ok(txid)
248248
}
249249

250+
/// Get and parse a MARF key from a path's captures, given the name of the regex field.
251+
pub fn get_marf_key(captures: &Captures, key: &str) -> Result<String, HttpError> {
252+
let marf_key = if let Some(marf_key_str) = captures.name(key) {
253+
marf_key_str.as_str().to_string()
254+
} else {
255+
return Err(HttpError::Http(404, format!("Missing `{}`", key)));
256+
};
257+
258+
Ok(marf_key)
259+
}
260+
250261
/// Get and parse a Clarity name from a path's captures, given the name of the regex field.
251262
pub fn get_clarity_name(captures: &Captures, key: &str) -> Result<ClarityName, HttpError> {
252263
let clarity_name = if let Some(name_str) = captures.name(key) {

0 commit comments

Comments
 (0)