Skip to content

Commit e551df3

Browse files
committed
updated openapi.yaml
1 parent 33cad91 commit e551df3

File tree

4 files changed

+111
-28
lines changed

4 files changed

+111
-28
lines changed

docs/rpc/openapi.yaml

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,6 @@ paths:
229229
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
230230
known tip (includes unconfirmed state).
231231
required: false
232-
- name: cost_tracker
233-
in: query
234-
schema:
235-
type: string
236-
description: the cost tracker to apply ("limited" or "free").
237-
required: false
238232
requestBody:
239233
description: map of arguments and the simulated tx-sender where sender is either a Contract identifier or a normal Stacks address, and arguments is an array of hex serialized Clarity values.
240234
required: true
@@ -934,3 +928,64 @@ paths:
934928
$ref: ./api/core-node/get-health-error.schema.json
935929
example:
936930
$ref: ./api/core-node/get-health-error.example.json
931+
932+
/v2/contracts/fast-call-read/{contract_address}/{contract_name}/{function_name}:
933+
post:
934+
summary: Call read-only function in fast mode (no cost tracking)
935+
tags:
936+
- Smart Contracts
937+
operationId: fast_call_read_only_function
938+
description: |
939+
Call a read-only public function on a given smart contract without cost tracking.
940+
941+
The smart contract and function are specified using the URL path. The arguments and the simulated tx-sender are supplied via the POST body in the following JSON format:
942+
responses:
943+
"200":
944+
description: Success
945+
content:
946+
application/json:
947+
schema:
948+
$ref: ./api/contract/post-call-read-only-fn.schema.json
949+
examples:
950+
success:
951+
$ref: ./api/contract/post-call-read-only-fn-success.example.json
952+
fail:
953+
$ref: ./api/contract/post-call-read-only-fn-fail.example.json
954+
parameters:
955+
- name: contract_address
956+
in: path
957+
required: true
958+
description: Stacks address
959+
schema:
960+
type: string
961+
- name: contract_name
962+
in: path
963+
required: true
964+
description: Contract name
965+
schema:
966+
type: string
967+
- name: function_name
968+
in: path
969+
required: true
970+
description: Function name
971+
schema:
972+
type: string
973+
- name: tip
974+
in: query
975+
schema:
976+
type: string
977+
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
978+
known tip (includes unconfirmed state).
979+
required: false
980+
requestBody:
981+
description: map of arguments and the simulated tx-sender where sender is either a Contract identifier or a normal Stacks address, and arguments is an array of hex serialized Clarity values.
982+
required: true
983+
content:
984+
application/json:
985+
schema:
986+
$ref: './entities/contracts/read-only-function-args.schema.json'
987+
example:
988+
sender: 'SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0.get-info'
989+
arguments:
990+
- '0x0011...'
991+
- '0x00231...'

stackslib/src/net/api/fastcallreadonly.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ use crate::net::{Error as NetError, StacksNodeState, TipRequest};
4747
pub struct RPCFastCallReadOnlyRequestHandler {
4848
pub call_read_only_handler: RPCCallReadOnlyRequestHandler,
4949
read_only_max_execution_time: Duration,
50+
pub auth: Option<String>,
5051
}
5152

5253
impl RPCFastCallReadOnlyRequestHandler {
53-
pub fn new(maximum_call_argument_size: u32, read_only_max_execution_time: Duration) -> Self {
54+
pub fn new(
55+
maximum_call_argument_size: u32,
56+
read_only_max_execution_time: Duration,
57+
auth: Option<String>,
58+
) -> Self {
5459
Self {
5560
call_read_only_handler: RPCCallReadOnlyRequestHandler::new(
5661
maximum_call_argument_size,
@@ -63,6 +68,7 @@ impl RPCFastCallReadOnlyRequestHandler {
6368
},
6469
),
6570
read_only_max_execution_time,
71+
auth,
6672
}
6773
}
6874
}
@@ -93,6 +99,17 @@ impl HttpRequest for RPCFastCallReadOnlyRequestHandler {
9399
query: Option<&str>,
94100
body: &[u8],
95101
) -> Result<HttpRequestContents, Error> {
102+
// If no authorization is set, then the block proposal endpoint is not enabled
103+
let Some(password) = &self.auth else {
104+
return Err(Error::Http(400, "Bad Request.".into()));
105+
};
106+
let Some(auth_header) = preamble.headers.get("authorization") else {
107+
return Err(Error::Http(401, "Unauthorized".into()));
108+
};
109+
if auth_header != password {
110+
return Err(Error::Http(401, "Unauthorized".into()));
111+
}
112+
96113
let content_len = preamble.get_content_length();
97114
if !(content_len > 0
98115
&& content_len < self.call_read_only_handler.maximum_call_argument_size)
@@ -337,14 +354,4 @@ impl StacksHttpRequest {
337354
)
338355
.expect("FATAL: failed to construct request from infallible data")
339356
}
340-
}
341-
342-
impl StacksHttpResponse {
343-
pub fn decode_fast_call_readonly_response(self) -> Result<CallReadOnlyResponse, NetError> {
344-
let contents = self.get_http_payload_ok()?;
345-
let contents_json: serde_json::Value = contents.try_into()?;
346-
let resp: CallReadOnlyResponse = serde_json::from_value(contents_json)
347-
.map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
348-
Ok(resp)
349-
}
350-
}
357+
}

stackslib/src/net/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl StacksHttp {
7878
self.register_rpc_endpoint(fastcallreadonly::RPCFastCallReadOnlyRequestHandler::new(
7979
self.maximum_call_argument_size,
8080
self.read_only_max_execution_time,
81+
self.auth_token.clone(),
8182
));
8283
self.register_rpc_endpoint(getaccount::RPCGetAccountRequestHandler::new());
8384
self.register_rpc_endpoint(getattachment::RPCGetAttachmentRequestHandler::new());

stackslib/src/net/api/tests/fastcallreadonly.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn test_try_parse_request() {
3636
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
3737
let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default());
3838

39-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
39+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
4040
addr.into(),
4141
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
4242
"hello-world-unconfirmed".try_into().unwrap(),
@@ -48,6 +48,10 @@ fn test_try_parse_request() {
4848
vec![],
4949
TipRequest::SpecificTip(StacksBlockId([0x22; 32])),
5050
);
51+
52+
// add the authorization header
53+
request.add_header("authorization".into(), "password".into());
54+
5155
assert_eq!(
5256
request.contents().tip_request(),
5357
TipRequest::SpecificTip(StacksBlockId([0x22; 32]))
@@ -58,8 +62,11 @@ fn test_try_parse_request() {
5862
debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap());
5963

6064
let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap();
61-
let mut handler =
62-
fastcallreadonly::RPCFastCallReadOnlyRequestHandler::new(4096, Duration::from_secs(30));
65+
let mut handler = fastcallreadonly::RPCFastCallReadOnlyRequestHandler::new(
66+
4096,
67+
Duration::from_secs(30),
68+
Some("password".into()),
69+
);
6370
let mut parsed_request = http
6471
.handle_try_parse_request(
6572
&mut handler,
@@ -89,8 +96,9 @@ fn test_try_parse_request() {
8996
assert_eq!(handler.call_read_only_handler.sponsor, None);
9097
assert_eq!(handler.call_read_only_handler.arguments, Some(vec![]));
9198

92-
// parsed request consumes headers that would not be in a constructed reqeuest
99+
// parsed request consumes headers that would not be in a constructed request
93100
parsed_request.clear_headers();
101+
parsed_request.add_header("authorization".into(), "password".into());
94102
let (preamble, contents) = parsed_request.destruct();
95103

96104
assert_eq!(&preamble, request.preamble());
@@ -111,7 +119,7 @@ fn test_try_make_response() {
111119
let mut requests = vec![];
112120

113121
// query confirmed tip
114-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
122+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
115123
addr.into(),
116124
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
117125
"hello-world".try_into().unwrap(),
@@ -123,10 +131,12 @@ fn test_try_make_response() {
123131
vec![],
124132
TipRequest::UseLatestAnchoredTip,
125133
);
134+
request.add_header("authorization".into(), "password".into());
135+
126136
requests.push(request);
127137

128138
// query unconfirmed tip
129-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
139+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
130140
addr.into(),
131141
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
132142
"hello-world-unconfirmed".try_into().unwrap(),
@@ -138,10 +148,12 @@ fn test_try_make_response() {
138148
vec![],
139149
TipRequest::UseLatestUnconfirmedTip,
140150
);
151+
request.add_header("authorization".into(), "password".into());
152+
141153
requests.push(request);
142154

143155
// query non-existent function
144-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
156+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
145157
addr.into(),
146158
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
147159
"hello-world-unconfirmed".try_into().unwrap(),
@@ -153,10 +165,12 @@ fn test_try_make_response() {
153165
vec![],
154166
TipRequest::UseLatestUnconfirmedTip,
155167
);
168+
request.add_header("authorization".into(), "password".into());
169+
156170
requests.push(request);
157171

158172
// query non-existent contract
159-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
173+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
160174
addr.into(),
161175
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
162176
"does-not-exist".try_into().unwrap(),
@@ -168,10 +182,12 @@ fn test_try_make_response() {
168182
vec![],
169183
TipRequest::UseLatestUnconfirmedTip,
170184
);
185+
request.add_header("authorization".into(), "password".into());
186+
171187
requests.push(request);
172188

173189
// query non-existent tip
174-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
190+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
175191
addr.into(),
176192
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
177193
"hello-world".try_into().unwrap(),
@@ -183,6 +199,8 @@ fn test_try_make_response() {
183199
vec![],
184200
TipRequest::SpecificTip(StacksBlockId([0x11; 32])),
185201
);
202+
request.add_header("authorization".into(), "password".into());
203+
186204
requests.push(request);
187205

188206
let mut responses = test_rpc(function_name!(), requests);
@@ -280,7 +298,7 @@ fn test_try_make_response_free_cost_tracker() {
280298
let mut requests = vec![];
281299

282300
// query confirmed tip
283-
let request = StacksHttpRequest::new_fastcallreadonlyfunction(
301+
let mut request = StacksHttpRequest::new_fastcallreadonlyfunction(
284302
addr.into(),
285303
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
286304
"hello-world".try_into().unwrap(),
@@ -292,6 +310,8 @@ fn test_try_make_response_free_cost_tracker() {
292310
vec![],
293311
TipRequest::UseLatestAnchoredTip,
294312
);
313+
request.add_header("authorization".into(), "password".into());
314+
295315
requests.push(request);
296316

297317
let mut responses = test_rpc_with_config(

0 commit comments

Comments
 (0)