@@ -116,6 +116,189 @@ async fn rpc_getinfo() {
116116 assert ! ( rpc_tx_queue_task_result. is_none( ) ) ;
117117}
118118
119+ #[ tokio:: test( flavor = "multi_thread" ) ]
120+ async fn rpc_getdeprecationinfo ( ) {
121+ let _init_guard = zebra_test:: init ( ) ;
122+
123+ let mempool: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
124+ let state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
125+ let read_state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
126+
127+ let ( _tx, rx) = tokio:: sync:: watch:: channel ( None ) ;
128+ let ( rpc, _rpc_tx_queue) = RpcImpl :: new (
129+ Mainnet ,
130+ Default :: default ( ) ,
131+ Default :: default ( ) ,
132+ "0.0.1" ,
133+ "RPC test" ,
134+ Buffer :: new ( mempool. clone ( ) , 1 ) ,
135+ Buffer :: new ( state. clone ( ) , 1 ) ,
136+ Buffer :: new ( read_state. clone ( ) , 1 ) ,
137+ MockService :: build ( ) . for_unit_tests ( ) ,
138+ MockSyncStatus :: default ( ) ,
139+ NoChainTip ,
140+ MockAddressBookPeers :: default ( ) ,
141+ rx,
142+ None ,
143+ ) ;
144+
145+ // Without a configured end of support height, `end_of_service` is not present.
146+ let deprecation_info = rpc
147+ . get_deprecation_info ( )
148+ . await
149+ . expect ( "getdeprecationinfo should succeed" ) ;
150+ assert_eq ! ( deprecation_info. end_of_service, None ) ;
151+
152+ let end_of_support_height = 3_546_440 ;
153+ let rpc = rpc. with_end_of_support_height ( Some ( Height ( end_of_support_height) ) ) ;
154+ let end_of_service = rpc
155+ . get_deprecation_info ( )
156+ . await
157+ . expect ( "getdeprecationinfo should succeed" )
158+ . end_of_service
159+ . expect ( "end_of_service should be present when an end of support height is set" ) ;
160+
161+ assert_eq ! ( end_of_service. block_height, end_of_support_height) ;
162+ // This node has no tip, so the estimate counts the blocks remaining after the highest
163+ // compiled-in checkpoint, not after genesis.
164+ let checkpoint_height = Mainnet . checkpoint_list ( ) . max_height ( ) ;
165+ let remaining_blocks = i64:: from ( end_of_support_height - checkpoint_height. 0 ) ;
166+ let expected_offset = remaining_blocks * 75 - 24 * 60 * 60 ;
167+ assert ! ( end_of_service. estimated_time > Utc :: now( ) . timestamp( ) ) ;
168+ assert ! ( end_of_service. estimated_time <= Utc :: now( ) . timestamp( ) + expected_offset) ;
169+ }
170+
171+ #[ tokio:: test( flavor = "multi_thread" ) ]
172+ async fn rpc_getdeprecationinfo_estimates_time_from_tip_with_safety_margin ( ) {
173+ let _init_guard = zebra_test:: init ( ) ;
174+
175+ let mempool: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
176+ let state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
177+ let read_state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
178+
179+ let ( latest_chain_tip, latest_chain_tip_sender) = MockChainTip :: new ( ) ;
180+ let tip_height = Height ( 3_000_000 ) ;
181+ latest_chain_tip_sender. send_best_tip_height ( tip_height) ;
182+
183+ let ( _tx, rx) = tokio:: sync:: watch:: channel ( None ) ;
184+ let ( rpc, _rpc_tx_queue) = RpcImpl :: new (
185+ Mainnet ,
186+ Default :: default ( ) ,
187+ Default :: default ( ) ,
188+ "0.0.1" ,
189+ "RPC test" ,
190+ Buffer :: new ( mempool. clone ( ) , 1 ) ,
191+ Buffer :: new ( state. clone ( ) , 1 ) ,
192+ Buffer :: new ( read_state. clone ( ) , 1 ) ,
193+ MockService :: build ( ) . for_unit_tests ( ) ,
194+ MockSyncStatus :: default ( ) ,
195+ latest_chain_tip,
196+ MockAddressBookPeers :: default ( ) ,
197+ rx,
198+ None ,
199+ ) ;
200+
201+ let end_of_support_height = Height ( 3_546_440 ) ;
202+ let rpc = rpc. with_end_of_support_height ( Some ( end_of_support_height) ) ;
203+
204+ // Both heights are after Blossom, so every remaining block is expected to take 75 seconds,
205+ // and the estimate is reported 24 hours early.
206+ let remaining_blocks = i64:: from ( end_of_support_height. 0 - tip_height. 0 ) ;
207+ let expected_offset = remaining_blocks * 75 - 24 * 60 * 60 ;
208+
209+ let before = Utc :: now ( ) . timestamp ( ) ;
210+ let end_of_service = rpc
211+ . get_deprecation_info ( )
212+ . await
213+ . expect ( "getdeprecationinfo should succeed" )
214+ . end_of_service
215+ . expect ( "end_of_service should be present when an end of support height is set" ) ;
216+ let after = Utc :: now ( ) . timestamp ( ) ;
217+
218+ assert_eq ! ( end_of_service. block_height, end_of_support_height. 0 ) ;
219+ assert ! ( end_of_service. estimated_time >= before + expected_offset) ;
220+ assert ! ( end_of_service. estimated_time <= after + expected_offset) ;
221+ }
222+
223+ #[ tokio:: test( flavor = "multi_thread" ) ]
224+ async fn rpc_getdeprecationinfo_omits_end_of_service_off_mainnet ( ) {
225+ let _init_guard = zebra_test:: init ( ) ;
226+
227+ let mempool: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
228+ let state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
229+ let read_state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
230+
231+ let ( _tx, rx) = tokio:: sync:: watch:: channel ( None ) ;
232+ let ( rpc, _rpc_tx_queue) = RpcImpl :: new (
233+ Network :: new_default_testnet ( ) ,
234+ Default :: default ( ) ,
235+ Default :: default ( ) ,
236+ "0.0.1" ,
237+ "RPC test" ,
238+ Buffer :: new ( mempool. clone ( ) , 1 ) ,
239+ Buffer :: new ( state. clone ( ) , 1 ) ,
240+ Buffer :: new ( read_state. clone ( ) , 1 ) ,
241+ MockService :: build ( ) . for_unit_tests ( ) ,
242+ MockSyncStatus :: default ( ) ,
243+ NoChainTip ,
244+ MockAddressBookPeers :: default ( ) ,
245+ rx,
246+ None ,
247+ ) ;
248+
249+ // Even if an end of support height is configured, `end_of_service` is only reported on
250+ // Mainnet, matching zcashd and the RPC documentation.
251+ let rpc = rpc. with_end_of_support_height ( Some ( Height ( 100 ) ) ) ;
252+ let deprecation_info = rpc
253+ . get_deprecation_info ( )
254+ . await
255+ . expect ( "getdeprecationinfo should succeed" ) ;
256+ assert_eq ! ( deprecation_info. end_of_service, None ) ;
257+ }
258+
259+ #[ tokio:: test( flavor = "multi_thread" ) ]
260+ async fn rpc_getdeprecationinfo_estimated_time_is_never_negative ( ) {
261+ let _init_guard = zebra_test:: init ( ) ;
262+
263+ let mempool: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
264+ let state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
265+ let read_state: MockService < _ , _ , _ , BoxError > = MockService :: build ( ) . for_unit_tests ( ) ;
266+
267+ // A tip so far past the end of support height that an unclamped estimate would be negative.
268+ let ( latest_chain_tip, latest_chain_tip_sender) = MockChainTip :: new ( ) ;
269+ latest_chain_tip_sender. send_best_tip_height ( Height :: MAX ) ;
270+
271+ let ( _tx, rx) = tokio:: sync:: watch:: channel ( None ) ;
272+ let ( rpc, _rpc_tx_queue) = RpcImpl :: new (
273+ Mainnet ,
274+ Default :: default ( ) ,
275+ Default :: default ( ) ,
276+ "0.0.1" ,
277+ "RPC test" ,
278+ Buffer :: new ( mempool. clone ( ) , 1 ) ,
279+ Buffer :: new ( state. clone ( ) , 1 ) ,
280+ Buffer :: new ( read_state. clone ( ) , 1 ) ,
281+ MockService :: build ( ) . for_unit_tests ( ) ,
282+ MockSyncStatus :: default ( ) ,
283+ latest_chain_tip,
284+ MockAddressBookPeers :: default ( ) ,
285+ rx,
286+ None ,
287+ ) ;
288+ let rpc = rpc. with_end_of_support_height ( Some ( Height ( 1 ) ) ) ;
289+
290+ let end_of_service = rpc
291+ . get_deprecation_info ( )
292+ . await
293+ . expect ( "getdeprecationinfo should succeed" )
294+ . end_of_service
295+ . expect ( "end_of_service should be present when an end of support height is set" ) ;
296+
297+ assert_eq ! ( end_of_service. block_height, 1 ) ;
298+ // The estimate is clamped to zero instead of going negative.
299+ assert_eq ! ( end_of_service. estimated_time, 0 ) ;
300+ }
301+
119302// Helper function that returns the nonce, final sapling root and
120303// block commitments of a given Block.
121304async fn get_block_data (
0 commit comments