14
14
// You should have received a copy of the GNU General Public License
15
15
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
17
+ use std:: time:: Duration ;
18
+
17
19
use clarity:: vm:: analysis:: CheckErrors ;
18
20
use clarity:: vm:: ast:: parser:: v1:: CLARITY_NAME_REGEX ;
19
21
use clarity:: vm:: clarity:: ClarityConnection ;
20
- use clarity:: vm:: costs:: { ExecutionCost , LimitedCostTracker } ;
22
+ use clarity:: vm:: costs:: { CostErrors , ExecutionCost , LimitedCostTracker } ;
23
+ use clarity:: vm:: errors:: Error as ClarityRuntimeError ;
21
24
use clarity:: vm:: errors:: Error :: Unchecked ;
22
- use clarity:: vm:: errors:: { Error as ClarityRuntimeError , InterpreterError } ;
23
25
use clarity:: vm:: representations:: { CONTRACT_NAME_REGEX_STRING , STANDARD_PRINCIPAL_REGEX_STRING } ;
24
26
use clarity:: vm:: types:: { PrincipalData , QualifiedContractIdentifier } ;
25
27
use clarity:: vm:: { ClarityName , ContractName , SymbolicExpression , Value } ;
@@ -68,10 +70,16 @@ pub struct RPCCallReadOnlyRequestHandler {
68
70
pub sender : Option < PrincipalData > ,
69
71
pub sponsor : Option < PrincipalData > ,
70
72
pub arguments : Option < Vec < Value > > ,
73
+
74
+ read_only_max_execution_time : Duration ,
71
75
}
72
76
73
77
impl RPCCallReadOnlyRequestHandler {
74
- pub fn new ( maximum_call_argument_size : u32 , read_only_call_limit : ExecutionCost ) -> Self {
78
+ pub fn new (
79
+ maximum_call_argument_size : u32 ,
80
+ read_only_call_limit : ExecutionCost ,
81
+ read_only_max_execution_time : Duration ,
82
+ ) -> Self {
75
83
Self {
76
84
maximum_call_argument_size,
77
85
read_only_call_limit,
@@ -80,6 +88,7 @@ impl RPCCallReadOnlyRequestHandler {
80
88
sender : None ,
81
89
sponsor : None ,
82
90
arguments : None ,
91
+ read_only_max_execution_time,
83
92
}
84
93
}
85
94
}
@@ -184,6 +193,12 @@ impl RPCRequestHandler for RPCCallReadOnlyRequestHandler {
184
193
}
185
194
} ;
186
195
196
+ let cost_tracker = contents
197
+ . get_query_args ( )
198
+ . get ( "cost_tracker" )
199
+ . map ( |cost_tracker| cost_tracker. as_str ( ) . into ( ) )
200
+ . unwrap_or ( CostTrackerRequest :: Limited ) ;
201
+
187
202
let contract_identifier = self
188
203
. contract_identifier
189
204
. take ( )
@@ -216,20 +231,27 @@ impl RPCRequestHandler for RPCCallReadOnlyRequestHandler {
216
231
cost_limit. write_length = 0 ;
217
232
cost_limit. write_count = 0 ;
218
233
234
+ let mut enforce_max_execution_time = false ;
235
+
219
236
chainstate. maybe_read_only_clarity_tx (
220
237
& sortdb. index_handle_at_block ( chainstate, & tip) ?,
221
238
& tip,
222
239
|clarity_tx| {
223
240
let epoch = clarity_tx. get_epoch ( ) ;
224
241
let cost_track = clarity_tx
225
- . with_clarity_db_readonly ( |clarity_db| {
226
- LimitedCostTracker :: new_mid_block (
242
+ . with_clarity_db_readonly ( |clarity_db| match cost_tracker {
243
+ CostTrackerRequest :: Limited => LimitedCostTracker :: new_mid_block (
227
244
mainnet, chain_id, cost_limit, clarity_db, epoch,
228
- )
245
+ ) ,
246
+ CostTrackerRequest :: Free => {
247
+ enforce_max_execution_time = true ;
248
+ Ok ( LimitedCostTracker :: new_free ( ) )
249
+ }
250
+ CostTrackerRequest :: Invalid => {
251
+ Err ( CostErrors :: Expect ( "Invalid cost tracker" . into ( ) ) )
252
+ }
229
253
} )
230
- . map_err ( |_| {
231
- ClarityRuntimeError :: from ( InterpreterError :: CostContractLoadFailure )
232
- } ) ?;
254
+ . map_err ( |e| ClarityRuntimeError :: from ( e) ) ?;
233
255
234
256
let clarity_version = clarity_tx
235
257
. with_analysis_db_readonly ( |analysis_db| {
@@ -250,6 +272,13 @@ impl RPCRequestHandler for RPCCallReadOnlyRequestHandler {
250
272
sponsor,
251
273
cost_track,
252
274
|env| {
275
+ // cost tracking in read only calls is meamingful mainly from a security point of view
276
+ // for this reason we enforce max_execution_time when cost tracking is disabled/free
277
+ if enforce_max_execution_time {
278
+ env. global_context
279
+ . set_max_execution_time ( self . read_only_max_execution_time ) ;
280
+ }
281
+
253
282
// we want to execute any function as long as no actual writes are made as
254
283
// opposed to be limited to purely calling `define-read-only` functions,
255
284
// so use `read_only = false`. This broadens the number of functions that
@@ -326,6 +355,38 @@ impl HttpResponse for RPCCallReadOnlyRequestHandler {
326
355
}
327
356
}
328
357
358
+ /// All representations of the `cost_tracker=` query parameter value
359
+ #[ derive( Debug , Clone , PartialEq ) ]
360
+ pub enum CostTrackerRequest {
361
+ Limited ,
362
+ Free ,
363
+ Invalid ,
364
+ }
365
+
366
+ impl CostTrackerRequest { }
367
+
368
+ impl std:: fmt:: Display for CostTrackerRequest {
369
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
370
+ match self {
371
+ Self :: Limited => write ! ( f, "limited" ) ,
372
+ Self :: Free => write ! ( f, "free" ) ,
373
+ Self :: Invalid => write ! ( f, "invalid" ) ,
374
+ }
375
+ }
376
+ }
377
+
378
+ impl From < & str > for CostTrackerRequest {
379
+ fn from ( s : & str ) -> CostTrackerRequest {
380
+ if s == "limited" || s == "" {
381
+ CostTrackerRequest :: Limited
382
+ } else if s == "free" {
383
+ CostTrackerRequest :: Free
384
+ } else {
385
+ CostTrackerRequest :: Invalid
386
+ }
387
+ }
388
+ }
389
+
329
390
impl StacksHttpRequest {
330
391
/// Make a new request to run a read-only function
331
392
pub fn new_callreadonlyfunction (
@@ -337,6 +398,7 @@ impl StacksHttpRequest {
337
398
function_name : ClarityName ,
338
399
function_args : Vec < Value > ,
339
400
tip_req : TipRequest ,
401
+ cost_tracker : CostTrackerRequest ,
340
402
) -> StacksHttpRequest {
341
403
StacksHttpRequest :: new_for_peer (
342
404
host,
@@ -345,14 +407,17 @@ impl StacksHttpRequest {
345
407
"/v2/contracts/call-read/{}/{}/{}" ,
346
408
& contract_addr, & contract_name, & function_name
347
409
) ,
348
- HttpRequestContents :: new ( ) . for_tip ( tip_req) . payload_json (
349
- serde_json:: to_value ( CallReadOnlyRequestBody {
350
- sender : sender. to_string ( ) ,
351
- sponsor : sponsor. map ( |s| s. to_string ( ) ) ,
352
- arguments : function_args. into_iter ( ) . map ( |v| v. to_string ( ) ) . collect ( ) ,
353
- } )
354
- . expect ( "FATAL: failed to encode infallible data" ) ,
355
- ) ,
410
+ HttpRequestContents :: new ( )
411
+ . for_tip ( tip_req)
412
+ . query_arg ( "cost_tracker" . to_string ( ) , cost_tracker. to_string ( ) )
413
+ . payload_json (
414
+ serde_json:: to_value ( CallReadOnlyRequestBody {
415
+ sender : sender. to_string ( ) ,
416
+ sponsor : sponsor. map ( |s| s. to_string ( ) ) ,
417
+ arguments : function_args. into_iter ( ) . map ( |v| v. to_string ( ) ) . collect ( ) ,
418
+ } )
419
+ . expect ( "FATAL: failed to encode infallible data" ) ,
420
+ ) ,
356
421
)
357
422
. expect ( "FATAL: failed to construct request from infallible data" )
358
423
}
0 commit comments