11// (c) Cartesi and individual authors (see AUTHORS)
22// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
33
4+ use alloy_primitives:: Address ;
45use serde:: { Deserialize , Serialize } ;
56use std:: fs:: { self , OpenOptions } ;
67use std:: path:: { Path , PathBuf } ;
@@ -18,10 +19,13 @@ use crate::{BenchResult, self_contained_domain};
1819pub const DEFAULT_SEQUENCER_BIN : & str = "target/release/sequencer" ;
1920pub const DEFAULT_MEMORY_SAMPLE_INTERVAL_MS : u64 = 500 ;
2021pub const DEFAULT_SEQUENCER_LOGS_DIR : & str = "benchmarks/results" ;
22+ pub const DEFAULT_ANVIL_STATE_DIR : & str = "benchmarks/.deps/rollups-contracts-2.2.0-anvil-v1.4.3" ;
2123
2224const DEFAULT_SEQUENCER_START_TIMEOUT : Duration = Duration :: from_secs ( 10 ) ;
2325const DEFAULT_SEQUENCER_SHUTDOWN_TIMEOUT : Duration = Duration :: from_secs ( 3 ) ;
2426const DEFAULT_SEQUENCER_RUST_LOG : & str = "info" ;
27+ const DEFAULT_ANVIL_START_TIMEOUT : Duration = Duration :: from_secs ( 10 ) ;
28+ const DEFAULT_ANVIL_SHUTDOWN_TIMEOUT : Duration = Duration :: from_secs ( 3 ) ;
2529
2630#[ derive( Debug , Clone ) ]
2731pub struct ManagedSequencerConfig {
@@ -30,6 +34,7 @@ pub struct ManagedSequencerConfig {
3034}
3135
3236pub struct ManagedSequencer {
37+ anvil : ManagedAnvil ,
3338 child : Child ,
3439 shutdown_timeout : Duration ,
3540 temp_dir : Option < TempDir > ,
@@ -41,6 +46,7 @@ impl ManagedSequencer {
4146 pub async fn spawn ( config : ManagedSequencerConfig ) -> BenchResult < Self > {
4247 let ( endpoint, http_addr) = build_local_endpoint ( ) ?;
4348 let domain = self_contained_domain ( ) ;
49+ let anvil = ManagedAnvil :: spawn ( config. log_prefix ) . await ?;
4450
4551 let dir = tempfile:: tempdir ( ) ?;
4652 let db_path = dir_path_join ( dir. path ( ) , "sequencer.db" ) ;
@@ -62,6 +68,10 @@ impl ManagedSequencer {
6268 . arg ( http_addr)
6369 . arg ( "--db-path" )
6470 . arg ( path_as_str ( db_path. as_path ( ) ) ?)
71+ . arg ( "--eth-rpc-url" )
72+ . arg ( anvil. endpoint . as_str ( ) )
73+ . arg ( "--input-box-address" )
74+ . arg ( anvil. input_box_address . to_string ( ) )
6575 . arg ( "--domain-chain-id" )
6676 . arg ( domain. chain_id . to_string ( ) )
6777 . arg ( "--domain-verifying-contract" )
@@ -85,6 +95,7 @@ impl ManagedSequencer {
8595 . await ?;
8696
8797 Ok ( Self {
98+ anvil,
8899 child,
89100 shutdown_timeout : DEFAULT_SEQUENCER_SHUTDOWN_TIMEOUT ,
90101 temp_dir,
@@ -103,6 +114,86 @@ impl ManagedSequencer {
103114
104115 pub async fn shutdown ( mut self ) -> BenchResult < ( ) > {
105116 let _ = self . temp_dir . take ( ) ;
117+ send_graceful_terminate ( & mut self . child ) . await ;
118+ let sequencer_result: BenchResult < ( ) > =
119+ match tokio:: time:: timeout ( self . shutdown_timeout , self . child . wait ( ) ) . await {
120+ Ok ( wait_result) => {
121+ let _ = wait_result?;
122+ Ok ( ( ) )
123+ }
124+ Err ( _) => {
125+ self . child . start_kill ( ) ?;
126+ let _ = self . child . wait ( ) . await ;
127+ Ok ( ( ) )
128+ }
129+ } ;
130+ let anvil_result = self . anvil . shutdown ( ) . await ;
131+ sequencer_result?;
132+ anvil_result
133+ }
134+ }
135+
136+ struct ManagedAnvil {
137+ child : Child ,
138+ shutdown_timeout : Duration ,
139+ endpoint : String ,
140+ input_box_address : Address ,
141+ }
142+
143+ impl ManagedAnvil {
144+ async fn spawn ( log_prefix : & str ) -> BenchResult < Self > {
145+ let state_dir = PathBuf :: from ( DEFAULT_ANVIL_STATE_DIR ) ;
146+ let state_path = dir_path_join ( state_dir. as_path ( ) , "state.json" ) ;
147+ let deployment_path = dir_path_join ( state_dir. as_path ( ) , "deployments/31337/InputBox.json" ) ;
148+
149+ ensure_exists (
150+ state_path. as_path ( ) ,
151+ format ! ( "missing {}; run `just setup` first" , state_path. display( ) ) ,
152+ ) ?;
153+ ensure_exists (
154+ deployment_path. as_path ( ) ,
155+ format ! (
156+ "missing {}; run `just setup` first" ,
157+ deployment_path. display( )
158+ ) ,
159+ ) ?;
160+
161+ let input_box_address = read_input_box_address ( deployment_path. as_path ( ) ) ?;
162+ let ( endpoint, http_addr) = build_local_endpoint ( ) ?;
163+ let log_path = default_anvil_log_path ( log_prefix) ;
164+ if let Some ( parent) = log_path. parent ( ) {
165+ fs:: create_dir_all ( parent) ?;
166+ }
167+ let stdout_log = OpenOptions :: new ( )
168+ . create ( true )
169+ . truncate ( true )
170+ . write ( true )
171+ . open ( log_path. as_path ( ) ) ?;
172+ let stderr_log = stdout_log. try_clone ( ) ?;
173+
174+ let mut child = Command :: new ( "anvil" )
175+ . arg ( "--host" )
176+ . arg ( "127.0.0.1" )
177+ . arg ( "--port" )
178+ . arg ( http_addr. rsplit ( ':' ) . next ( ) . expect ( "port" ) )
179+ . arg ( "--load-state" )
180+ . arg ( path_as_str ( state_path. as_path ( ) ) ?)
181+ . stdout ( Stdio :: from ( stdout_log) )
182+ . stderr ( Stdio :: from ( stderr_log) )
183+ . spawn ( )
184+ . map_err ( |err| io_other ( format ! ( "failed to spawn anvil: {err}" ) ) ) ?;
185+
186+ wait_for_rpc_readiness ( endpoint. as_str ( ) , & mut child, DEFAULT_ANVIL_START_TIMEOUT ) . await ?;
187+
188+ Ok ( Self {
189+ child,
190+ shutdown_timeout : DEFAULT_ANVIL_SHUTDOWN_TIMEOUT ,
191+ endpoint,
192+ input_box_address,
193+ } )
194+ }
195+
196+ async fn shutdown ( mut self ) -> BenchResult < ( ) > {
106197 send_graceful_terminate ( & mut self . child ) . await ;
107198 match tokio:: time:: timeout ( self . shutdown_timeout , self . child . wait ( ) ) . await {
108199 Ok ( wait_result) => {
@@ -237,6 +328,16 @@ pub fn default_sequencer_log_path(prefix: &str) -> PathBuf {
237328 PathBuf :: from ( format ! ( "{DEFAULT_SEQUENCER_LOGS_DIR}/{prefix}-{ts}.log" ) )
238329}
239330
331+ fn default_anvil_log_path ( prefix : & str ) -> PathBuf {
332+ let ts = std:: time:: SystemTime :: now ( )
333+ . duration_since ( std:: time:: UNIX_EPOCH )
334+ . map ( |value| value. as_millis ( ) )
335+ . unwrap_or ( 0 ) ;
336+ PathBuf :: from ( format ! (
337+ "{DEFAULT_SEQUENCER_LOGS_DIR}/{prefix}-anvil-{ts}.log"
338+ ) )
339+ }
340+
240341async fn wait_for_readiness (
241342 endpoint : & str ,
242343 child : & mut Child ,
@@ -263,6 +364,29 @@ async fn wait_for_readiness(
263364 }
264365}
265366
367+ async fn wait_for_rpc_readiness (
368+ endpoint : & str ,
369+ child : & mut Child ,
370+ timeout : Duration ,
371+ ) -> BenchResult < ( ) > {
372+ let deadline = tokio:: time:: Instant :: now ( ) + timeout;
373+ loop {
374+ if let Some ( status) = child. try_wait ( ) ? {
375+ return Err ( io_other ( format ! ( "anvil exited before readiness: status={status}" ) ) . into ( ) ) ;
376+ }
377+ if rpc_endpoint_is_ready ( endpoint) . await {
378+ return Ok ( ( ) ) ;
379+ }
380+ if tokio:: time:: Instant :: now ( ) >= deadline {
381+ return Err ( io_other ( format ! (
382+ "timed out waiting for anvil readiness at {endpoint}"
383+ ) )
384+ . into ( ) ) ;
385+ }
386+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
387+ }
388+ }
389+
266390async fn http_endpoint_is_ready ( endpoint : & str ) -> bool {
267391 let Some ( host_port) = endpoint. strip_prefix ( "http://" ) else {
268392 return false ;
@@ -288,6 +412,35 @@ async fn http_endpoint_is_ready(endpoint: &str) -> bool {
288412 }
289413}
290414
415+ async fn rpc_endpoint_is_ready ( endpoint : & str ) -> bool {
416+ let Some ( host_port) = endpoint. strip_prefix ( "http://" ) else {
417+ return false ;
418+ } ;
419+ let mut stream =
420+ match tokio:: time:: timeout ( Duration :: from_millis ( 300 ) , TcpStream :: connect ( host_port) ) . await
421+ {
422+ Ok ( Ok ( value) ) => value,
423+ _ => return false ,
424+ } ;
425+
426+ let body = r#"{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}"# ;
427+ let request = format ! (
428+ "POST / HTTP/1.1\r \n Host: {host_port}\r \n Content-Type: application/json\r \n Content-Length: {}\r \n Connection: close\r \n \r \n {body}" ,
429+ body. len( )
430+ ) ;
431+ if stream. write_all ( request. as_bytes ( ) ) . await . is_err ( ) {
432+ return false ;
433+ }
434+ let mut head = [ 0_u8 ; 128 ] ;
435+ match tokio:: time:: timeout ( Duration :: from_millis ( 300 ) , stream. read ( & mut head) ) . await {
436+ Ok ( Ok ( read) ) if read > 0 => std:: str:: from_utf8 ( & head[ ..read] )
437+ . ok ( )
438+ . map ( |text| text. contains ( "200 OK" ) )
439+ . unwrap_or ( false ) ,
440+ _ => false ,
441+ }
442+ }
443+
291444async fn send_graceful_terminate ( child : & mut Child ) {
292445 let Some ( pid) = child. id ( ) else {
293446 return ;
@@ -322,23 +475,55 @@ fn dir_path_join(base: &Path, file: &str) -> PathBuf {
322475 path
323476}
324477
478+ fn ensure_exists ( path : & Path , missing_message : String ) -> BenchResult < ( ) > {
479+ if path. exists ( ) {
480+ Ok ( ( ) )
481+ } else {
482+ Err ( io_other ( missing_message) . into ( ) )
483+ }
484+ }
485+
325486fn path_as_str ( path : & Path ) -> BenchResult < & str > {
326487 path. to_str ( )
327488 . ok_or_else ( || io_other ( format ! ( "path is not valid UTF-8: {}" , path. display( ) ) ) . into ( ) )
328489}
329490
491+ fn read_input_box_address ( path : & Path ) -> BenchResult < Address > {
492+ #[ derive( Deserialize ) ]
493+ struct DeploymentInfo {
494+ address : String ,
495+ }
496+
497+ let deployment: DeploymentInfo = serde_json:: from_str ( & fs:: read_to_string ( path) ?)
498+ . map_err ( |err| io_other ( format ! ( "failed to parse {}: {err}" , path. display( ) ) ) ) ?;
499+ deployment. address . parse ( ) . map_err ( |err| {
500+ io_other ( format ! (
501+ "invalid InputBox address in {}: {err}" ,
502+ path. display( )
503+ ) )
504+ . into ( )
505+ } )
506+ }
507+
330508fn io_other ( message : impl Into < String > ) -> std:: io:: Error {
331509 std:: io:: Error :: other ( message. into ( ) )
332510}
333511
334512#[ cfg( test) ]
335513mod tests {
336- use super :: default_sequencer_log_path;
514+ use super :: { default_anvil_log_path , default_sequencer_log_path} ;
337515
338516 #[ test]
339517 fn default_log_path_uses_results_dir ( ) {
340518 let value = default_sequencer_log_path ( "ack-latency" ) ;
341519 assert ! ( value. to_string_lossy( ) . contains( "benchmarks/results/" ) ) ;
342520 assert ! ( value. to_string_lossy( ) . contains( "ack-latency" ) ) ;
343521 }
522+
523+ #[ test]
524+ fn default_anvil_log_path_uses_results_dir ( ) {
525+ let value = default_anvil_log_path ( "ack-latency" ) ;
526+ assert ! ( value. to_string_lossy( ) . contains( "benchmarks/results/" ) ) ;
527+ assert ! ( value. to_string_lossy( ) . contains( "ack-latency-anvil" ) ) ;
528+ }
344529}
0 commit comments