11// Copyright 2021-2024 Protocol Labs
22// SPDX-License-Identifier: Apache-2.0, MIT
33
4- use crate :: state:: State ;
4+ use crate :: state:: { PowerEntryValue , State } ;
55use crate :: types:: { ConstructorParams , GetStateResponse , PowerEntry , UpdateStateParams } ;
66use fil_actors_runtime:: builtin:: singletons:: SYSTEM_ACTOR_ADDR ;
77use fil_actors_runtime:: runtime:: { ActorCode , Runtime } ;
@@ -58,6 +58,38 @@ impl F3LightClient for F3LightClientActor {
5858 rt. validate_immediate_caller_is ( std:: iter:: once ( & SYSTEM_ACTOR_ADDR ) ) ?;
5959
6060 rt. transaction ( |st : & mut State , rt| {
61+ // Basic monotonicity checks to prevent accidental rewinds or no-op updates.
62+ //
63+ // Note: multiple epochs can be proven under the same certificate instance, so
64+ // `latest_instance_id` may stay the same across updates, but it must never go
65+ // backwards and must not jump by more than 1.
66+ //
67+ // Also, we allow re-applying the same update (idempotency) by permitting equality.
68+ if params. latest_finalized_height < st. light_client_state . latest_finalized_height {
69+ return Err ( actor_error ! (
70+ illegal_argument,
71+ "latest_finalized_height went backwards: {} < {}" ,
72+ params. latest_finalized_height,
73+ st. light_client_state. latest_finalized_height
74+ ) ) ;
75+ }
76+ if params. latest_instance_id < st. light_client_state . latest_instance_id {
77+ return Err ( actor_error ! (
78+ illegal_argument,
79+ "latest_instance_id went backwards: {} < {}" ,
80+ params. latest_instance_id,
81+ st. light_client_state. latest_instance_id
82+ ) ) ;
83+ }
84+ if params. latest_instance_id > st. light_client_state . latest_instance_id + 1 {
85+ return Err ( actor_error ! (
86+ illegal_argument,
87+ "latest_instance_id jumped: {} > {}" ,
88+ params. latest_instance_id,
89+ st. light_client_state. latest_instance_id + 1
90+ ) ) ;
91+ }
92+
6193 st. update_state (
6294 rt,
6395 params. latest_instance_id ,
@@ -77,15 +109,19 @@ impl F3LightClient for F3LightClientActor {
77109
78110 // Materialize the current power table for convenience.
79111 let power_table = {
80- let m = fil_actors_runtime:: Map2 :: < _ , u64 , PowerEntry > :: load (
112+ let m = fil_actors_runtime:: Map2 :: < _ , u64 , PowerEntryValue > :: load (
81113 rt. store ( ) ,
82114 & lc. power_table_root ,
83115 fil_actors_runtime:: DEFAULT_HAMT_CONFIG ,
84116 "f3_power_table" ,
85117 ) ?;
86118 let mut out = Vec :: new ( ) ;
87- m. for_each ( |_k, v| {
88- out. push ( v. clone ( ) ) ;
119+ m. for_each ( |id, v| {
120+ out. push ( PowerEntry {
121+ id,
122+ public_key : v. public_key . clone ( ) ,
123+ power_be : v. power_be . clone ( ) ,
124+ } ) ;
89125 Ok ( ( ) )
90126 } ) ?;
91127 out. sort_by_key ( |e| e. id ) ;
@@ -128,16 +164,25 @@ mod tests {
128164
129165 /// Helper function to create test power entries
130166 fn create_test_power_entries ( ) -> Vec < PowerEntry > {
167+ fn u64_to_power_be ( x : u64 ) -> Vec < u8 > {
168+ if x == 0 {
169+ return Vec :: new ( ) ;
170+ }
171+ let bytes = x. to_be_bytes ( ) ;
172+ let first = bytes. iter ( ) . position ( |b| * b != 0 ) . unwrap_or ( bytes. len ( ) ) ;
173+ bytes[ first..] . to_vec ( )
174+ }
175+
131176 vec ! [
132177 PowerEntry {
133178 id: 1 ,
134179 public_key: vec![ 1 , 2 , 3 ] ,
135- power : 100 ,
180+ power_be : u64_to_power_be ( 100 ) ,
136181 } ,
137182 PowerEntry {
138183 id: 2 ,
139184 public_key: vec![ 4 , 5 , 6 ] ,
140- power : 200 ,
185+ power_be : u64_to_power_be ( 200 ) ,
141186 } ,
142187 ]
143188 }
@@ -200,7 +245,7 @@ mod tests {
200245
201246 let update_params = UpdateStateParams {
202247 latest_instance_id : 1 ,
203- latest_finalized_height : 10 ,
248+ latest_finalized_height : 11 ,
204249 power_table : create_test_power_entries ( ) ,
205250 } ;
206251
@@ -219,12 +264,12 @@ mod tests {
219264 fn test_update_state_non_advancing_height ( ) {
220265 let rt = construct_and_verify ( 1 , create_test_power_entries ( ) , 10 ) ;
221266
222- // First update to set the finalized height to 102
267+ // First update to advance the finalized height.
223268 rt. set_caller ( * SYSTEM_ACTOR_CODE_ID , SYSTEM_ACTOR_ADDR ) ;
224269 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
225270 let initial_params = UpdateStateParams {
226271 latest_instance_id : 1 ,
227- latest_finalized_height : 10 ,
272+ latest_finalized_height : 11 ,
228273 power_table : create_test_power_entries ( ) ,
229274 } ;
230275 rt. call :: < F3LightClientActor > (
@@ -239,7 +284,7 @@ mod tests {
239284 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
240285 let update_params = UpdateStateParams {
241286 latest_instance_id : 1 ,
242- latest_finalized_height : 10 ,
287+ latest_finalized_height : 11 ,
243288 power_table : create_test_power_entries ( ) ,
244289 } ;
245290
@@ -248,10 +293,8 @@ mod tests {
248293 IpldBlock :: serialize_cbor ( & update_params) . unwrap ( ) ,
249294 ) ;
250295
251- // Should fail with illegal argument
252- assert ! ( result. is_err( ) ) ;
253- let err = result. unwrap_err ( ) ;
254- assert_eq ! ( err. exit_code( ) , ExitCode :: USR_ILLEGAL_ARGUMENT ) ;
296+ // Allowed (idempotency): equality is ok, only rewinds are rejected.
297+ assert ! ( result. is_ok( ) ) ;
255298 }
256299
257300 #[ test]
@@ -329,16 +372,24 @@ mod tests {
329372 // Update with a different power table.
330373 rt. set_caller ( * SYSTEM_ACTOR_CODE_ID , SYSTEM_ACTOR_ADDR ) ;
331374 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
375+ fn u64_to_power_be ( x : u64 ) -> Vec < u8 > {
376+ if x == 0 {
377+ return Vec :: new ( ) ;
378+ }
379+ let bytes = x. to_be_bytes ( ) ;
380+ let first = bytes. iter ( ) . position ( |b| * b != 0 ) . unwrap_or ( bytes. len ( ) ) ;
381+ bytes[ first..] . to_vec ( )
382+ }
332383 let new_power_table = vec ! [
333384 PowerEntry {
334385 id: 1 ,
335386 public_key: vec![ 1 , 2 , 3 ] ,
336- power : 999 ,
387+ power_be : u64_to_power_be ( 999 ) ,
337388 } ,
338389 PowerEntry {
339390 id: 3 ,
340391 public_key: vec![ 7 , 8 , 9 ] ,
341- power : 333 ,
392+ power_be : u64_to_power_be ( 333 ) ,
342393 } ,
343394 ] ;
344395 let update_params = UpdateStateParams {
@@ -412,7 +463,7 @@ mod tests {
412463 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
413464 let initial_params = UpdateStateParams {
414465 latest_instance_id : 100 ,
415- latest_finalized_height : 10 ,
466+ latest_finalized_height : 11 ,
416467 power_table : create_test_power_entries ( ) ,
417468 } ;
418469 rt. call :: < F3LightClientActor > (
@@ -427,7 +478,7 @@ mod tests {
427478 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
428479 let update_params = UpdateStateParams {
429480 latest_instance_id : 101 ,
430- latest_finalized_height : 10 ,
481+ latest_finalized_height : 12 ,
431482 power_table : create_test_power_entries ( ) ,
432483 } ;
433484
@@ -447,7 +498,7 @@ mod tests {
447498 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
448499 let initial_params = UpdateStateParams {
449500 latest_instance_id : 100 ,
450- latest_finalized_height : 10 ,
501+ latest_finalized_height : 11 ,
451502 power_table : create_test_power_entries ( ) ,
452503 } ;
453504 rt. call :: < F3LightClientActor > (
@@ -462,7 +513,7 @@ mod tests {
462513 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
463514 let update_params = UpdateStateParams {
464515 latest_instance_id : 102 ,
465- latest_finalized_height : 10 ,
516+ latest_finalized_height : 12 ,
466517 power_table : create_test_power_entries ( ) ,
467518 } ;
468519
@@ -476,16 +527,16 @@ mod tests {
476527 }
477528
478529 #[ test]
479- fn test_empty_epochs_rejected ( ) {
530+ fn test_height_rewind_rejected ( ) {
480531 let rt = construct_and_verify ( 1 , create_test_power_entries ( ) , 10 ) ;
481532
482533 rt. set_caller ( * SYSTEM_ACTOR_CODE_ID , SYSTEM_ACTOR_ADDR ) ;
483534 rt. expect_validate_caller_addr ( vec ! [ SYSTEM_ACTOR_ADDR ] ) ;
484535
485- // Try to update with empty finalized_epochs
536+ // Try to update with a lower finalized height (rewind).
486537 let update_params = UpdateStateParams {
487538 latest_instance_id : 1 ,
488- latest_finalized_height : 10 ,
539+ latest_finalized_height : 9 ,
489540 power_table : create_test_power_entries ( ) ,
490541 } ;
491542
0 commit comments