@@ -5,18 +5,26 @@ use platform_distributed_storage::{
55} ;
66use platform_p2p_consensus:: { P2PCommand , P2PMessage , StorageProposal , StorageProposalMessage } ;
77use sha2:: { Digest , Sha256 } ;
8+ use std:: collections:: HashMap ;
89use std:: sync:: Arc ;
910use tokio:: sync:: mpsc;
1011use wasm_runtime_interface:: storage:: { StorageBackend , StorageHostError } ;
1112
1213/// Channel for local storage proposals (proposer adds to own state)
1314pub type LocalProposalSender = mpsc:: Sender < StorageProposal > ;
1415
16+ /// Pending write value: Some(data) = write, None = delete
17+ type PendingValue = Option < Vec < u8 > > ;
18+
1519pub struct ChallengeStorageBackend {
1620 storage : Arc < dyn DistributedStore > ,
1721 p2p_tx : Option < mpsc:: Sender < P2PCommand > > ,
1822 local_proposal_tx : Option < LocalProposalSender > ,
1923 keypair : Option < Keypair > ,
24+ /// Write-through cache for read-your-own-writes during a sync cycle.
25+ /// Key: (challenge_id, hex-encoded key). Value: pending data (None = delete).
26+ /// Cleared via `clear_pending_writes()` after each sync cycle completes.
27+ pending_writes : parking_lot:: RwLock < HashMap < ( String , String ) , PendingValue > > ,
2028}
2129
2230impl ChallengeStorageBackend {
@@ -27,6 +35,7 @@ impl ChallengeStorageBackend {
2735 p2p_tx : None ,
2836 local_proposal_tx : None ,
2937 keypair : None ,
38+ pending_writes : parking_lot:: RwLock :: new ( HashMap :: new ( ) ) ,
3039 }
3140 }
3241
@@ -41,8 +50,15 @@ impl ChallengeStorageBackend {
4150 p2p_tx : Some ( p2p_tx) ,
4251 local_proposal_tx : Some ( local_proposal_tx) ,
4352 keypair : Some ( keypair) ,
53+ pending_writes : parking_lot:: RwLock :: new ( HashMap :: new ( ) ) ,
4454 }
4555 }
56+
57+ /// Clear the pending writes cache. Call after each sync cycle completes
58+ /// so that subsequent reads go through consensus-confirmed storage.
59+ pub fn clear_pending_writes ( & self ) {
60+ self . pending_writes . write ( ) . clear ( ) ;
61+ }
4662}
4763
4864/// Build a standardized storage key for challenge data.
@@ -54,6 +70,12 @@ fn build_challenge_storage_key(challenge_id: &str, key: &[u8]) -> DStorageKey {
5470
5571impl StorageBackend for ChallengeStorageBackend {
5672 fn get ( & self , challenge_id : & str , key : & [ u8 ] ) -> Result < Option < Vec < u8 > > , StorageHostError > {
73+ // Check pending writes cache first (read-your-own-writes during sync)
74+ let cache_key = ( challenge_id. to_string ( ) , hex:: encode ( key) ) ;
75+ if let Some ( pending) = self . pending_writes . read ( ) . get ( & cache_key) {
76+ return Ok ( pending. clone ( ) ) ;
77+ }
78+
5779 let storage_key = build_challenge_storage_key ( challenge_id, key) ;
5880 let result = tokio:: task:: block_in_place ( || {
5981 tokio:: runtime:: Handle :: current ( )
@@ -76,9 +98,19 @@ impl StorageBackend for ChallengeStorageBackend {
7698 hasher. update ( value) ;
7799 let proposal_id: [ u8 ; 32 ] = hasher. finalize ( ) . into ( ) ;
78100
79- // DO NOT write locally here - data is only written after P2P consensus is reached.
80- // All validators (including the proposer) write in the StorageVote handler when
81- // 2f+1 votes approve. This ensures consistency across all nodes.
101+ // Cache the write locally so WASM can read-its-own-writes during the
102+ // current sync cycle. This cache is NOT persisted to storage and does NOT
103+ // affect other validators. Actual storage write happens after P2P consensus.
104+ // The cache is cleared after each sync cycle via clear_pending_writes().
105+ {
106+ let cache_key = ( challenge_id. to_string ( ) , hex:: encode ( key) ) ;
107+ let cache_value = if value. is_empty ( ) {
108+ None // delete
109+ } else {
110+ Some ( value. to_vec ( ) )
111+ } ;
112+ self . pending_writes . write ( ) . insert ( cache_key, cache_value) ;
113+ }
82114
83115 // Broadcast via P2P for consensus
84116 if let ( Some ( tx) , Some ( kp) ) = ( & self . p2p_tx , & self . keypair ) {
0 commit comments