11use crate :: executor:: TvmEmulatorAdapter ;
22use crate :: node:: { Node , NodeClockInfo , StateSource } ;
3+ use crate :: node_snapshot:: NodeStateSnapshot ;
34use crate :: storage;
45use crate :: storage:: { AccountStatus , BlockMeta , MsgMeta , TransactionInfo } ;
56use crate :: streaming:: StreamingCommitEvent ;
@@ -181,6 +182,12 @@ pub struct LocalnetMineResult {
181182 pub blocks : Vec < LocalnetBlockId > ,
182183}
183184
185+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
186+ pub struct LocalnetRecoveryPointResult {
187+ pub id : u64 ,
188+ pub block_seqno : Seqno ,
189+ }
190+
184191#[ derive( Debug , Serialize , Deserialize , Clone ) ]
185192pub struct LocalnetBlockHeader {
186193 pub id : LocalnetBlockId ,
@@ -408,6 +415,13 @@ pub(crate) enum Request {
408415 path : String ,
409416 resp : oneshot:: Sender < anyhow:: Result < ( ) > > ,
410417 } ,
418+ CreateRecoveryPoint {
419+ resp : oneshot:: Sender < anyhow:: Result < LocalnetRecoveryPointResult > > ,
420+ } ,
421+ RevertRecoveryPoint {
422+ id : u64 ,
423+ resp : oneshot:: Sender < anyhow:: Result < LocalnetRecoveryPointResult > > ,
424+ } ,
411425 MineBlocks {
412426 count : u32 ,
413427 resp : oneshot:: Sender < anyhow:: Result < LocalnetMineResult > > ,
@@ -435,6 +449,48 @@ pub struct Localnet {
435449 started_at : SystemTime ,
436450}
437451
452+ #[ derive( Default ) ]
453+ struct RecoveryPoints {
454+ next_id : u64 ,
455+ points : Vec < RecoveryPoint > ,
456+ }
457+
458+ struct RecoveryPoint {
459+ id : u64 ,
460+ snapshot : NodeStateSnapshot ,
461+ }
462+
463+ impl RecoveryPoints {
464+ fn create ( & mut self , node : & Node ) -> anyhow:: Result < LocalnetRecoveryPointResult > {
465+ let snapshot = node. build_snapshot ( ) ?;
466+ self . next_id = self
467+ . next_id
468+ . checked_add ( 1 )
469+ . context ( "Recovery point id overflow" ) ?;
470+ let id = self . next_id ;
471+ let block_seqno = snapshot. globals . head_seqno ;
472+ self . points . push ( RecoveryPoint { id, snapshot } ) ;
473+ Ok ( LocalnetRecoveryPointResult { id, block_seqno } )
474+ }
475+
476+ fn revert ( & mut self , node : & mut Node , id : u64 ) -> anyhow:: Result < LocalnetRecoveryPointResult > {
477+ let index = self
478+ . points
479+ . iter ( )
480+ . position ( |point| point. id == id)
481+ . with_context ( || format ! ( "Recovery point {id} not found" ) ) ?;
482+ let snapshot = self . points [ index] . snapshot . clone ( ) ;
483+ let block_seqno = snapshot. globals . head_seqno ;
484+ node. apply_snapshot ( snapshot) ?;
485+ self . points . truncate ( index) ;
486+ Ok ( LocalnetRecoveryPointResult { id, block_seqno } )
487+ }
488+
489+ fn clear ( & mut self ) {
490+ self . points . clear ( ) ;
491+ }
492+ }
493+
438494pub const DEFAULT_BLOCK_INTERVAL_MS : u64 = 500 ;
439495
440496impl Localnet {
@@ -1043,6 +1099,23 @@ impl Localnet {
10431099 rx. await ?
10441100 }
10451101
1102+ pub async fn create_recovery_point ( & self ) -> anyhow:: Result < LocalnetRecoveryPointResult > {
1103+ let ( resp, rx) = oneshot:: channel ( ) ;
1104+ self . tx . send ( Request :: CreateRecoveryPoint { resp } ) . await ?;
1105+ rx. await ?
1106+ }
1107+
1108+ pub async fn revert_recovery_point (
1109+ & self ,
1110+ id : u64 ,
1111+ ) -> anyhow:: Result < LocalnetRecoveryPointResult > {
1112+ let ( resp, rx) = oneshot:: channel ( ) ;
1113+ self . tx
1114+ . send ( Request :: RevertRecoveryPoint { id, resp } )
1115+ . await ?;
1116+ rx. await ?
1117+ }
1118+
10461119 pub async fn mine_blocks ( & self , count : u32 ) -> anyhow:: Result < LocalnetMineResult > {
10471120 let ( resp, rx) = oneshot:: channel ( ) ;
10481121 self . tx . send ( Request :: MineBlocks { count, resp } ) . await ?;
@@ -1097,6 +1170,7 @@ fn run_node_loop(
10971170 auto_mining : bool ,
10981171) -> anyhow:: Result < ( ) > {
10991172 let mut node = create_node ( events_tx, state_source, db_path) ?;
1173+ let mut recovery_points = RecoveryPoints :: default ( ) ;
11001174 tracing:: info!(
11011175 "TON localnet started, block interval: {}ms, auto mining: {}" ,
11021176 block_interval. as_millis( ) ,
@@ -1105,7 +1179,7 @@ fn run_node_loop(
11051179
11061180 if !auto_mining {
11071181 while let Some ( req) = rx. blocking_recv ( ) {
1108- process_loop_request ( & mut node, req) ;
1182+ process_loop_request ( & mut node, & mut recovery_points , req) ;
11091183 }
11101184 return Ok ( ( ) ) ;
11111185 }
@@ -1138,6 +1212,7 @@ async fn run_node_loop_async(
11381212 block_interval : Duration ,
11391213) -> anyhow:: Result < ( ) > {
11401214 let mut next_block_at = Instant :: now ( ) + block_interval;
1215+ let mut recovery_points = RecoveryPoints :: default ( ) ;
11411216
11421217 loop {
11431218 if Instant :: now ( ) >= next_block_at {
@@ -1154,7 +1229,7 @@ async fn run_node_loop_async(
11541229 let Some ( req) = req else {
11551230 return Ok ( ( ) ) ;
11561231 } ;
1157- process_loop_request( & mut node, req) ;
1232+ process_loop_request( & mut node, & mut recovery_points , req) ;
11581233 }
11591234 }
11601235 }
@@ -1188,7 +1263,7 @@ fn handle_mine_blocks(node: &mut Node, count: u32) -> anyhow::Result<LocalnetMin
11881263 } )
11891264}
11901265
1191- fn process_loop_request ( node : & mut Node , req : Request ) {
1266+ fn process_loop_request ( node : & mut Node , recovery_points : & mut RecoveryPoints , req : Request ) {
11921267 tracing:: debug!( "Node loop processing request: {:?}" , req) ;
11931268 match req {
11941269 Request :: SendBoc { boc, resp } => {
@@ -1456,6 +1531,17 @@ fn process_loop_request(node: &mut Node, req: Request) {
14561531 }
14571532 Request :: LoadState { path, resp } => {
14581533 let res = node. load_state_from_path ( path) ;
1534+ if res. is_ok ( ) {
1535+ recovery_points. clear ( ) ;
1536+ }
1537+ let _ = resp. send ( res) ;
1538+ }
1539+ Request :: CreateRecoveryPoint { resp } => {
1540+ let res = recovery_points. create ( node) ;
1541+ let _ = resp. send ( res) ;
1542+ }
1543+ Request :: RevertRecoveryPoint { id, resp } => {
1544+ let res = recovery_points. revert ( node, id) ;
14591545 let _ = resp. send ( res) ;
14601546 }
14611547 Request :: MineBlocks { count, resp } => {
0 commit comments