@@ -37,6 +37,7 @@ lazy_static! {
3737 ActionDataKeyBuilder :: new( CUSTOM_ACTION_HANDLER_ID , 1 ) . append( & "StakeholderAddresses" ) . into_key( ) ;
3838 pub static ref CANDIDATES_KEY : H256 =
3939 ActionDataKeyBuilder :: new( CUSTOM_ACTION_HANDLER_ID , 1 ) . append( & "Candidates" ) . into_key( ) ;
40+ pub static ref JAIL_KEY : H256 = ActionDataKeyBuilder :: new( CUSTOM_ACTION_HANDLER_ID , 1 ) . append( & "Jail" ) . into_key( ) ;
4041}
4142
4243pub fn get_delegation_key ( address : & Address ) -> H256 {
@@ -313,6 +314,84 @@ impl Candidates {
313314 self . 0 = retained. into_iter ( ) . map ( |c| ( c. address , c) ) . collect ( ) ;
314315 expired
315316 }
317+
318+ pub fn remove ( & mut self , address : & Address ) -> Option < Candidate > {
319+ self . 0 . remove ( address)
320+ }
321+ }
322+
323+ pub struct Jail ( BTreeMap < Address , Prisoner > ) ;
324+ #[ derive( Clone , Debug , Eq , PartialEq , RlpEncodable , RlpDecodable ) ]
325+ pub struct Prisoner {
326+ pub address : Address ,
327+ pub deposit : Deposit ,
328+ pub custody_until : u64 ,
329+ pub kicked_at : u64 ,
330+ }
331+
332+ #[ derive( Debug , Eq , PartialEq ) ]
333+ pub enum ReleaseResult {
334+ NotExists ,
335+ InCustody ,
336+ Released ( Prisoner ) ,
337+ }
338+
339+ impl Jail {
340+ pub fn load_from_state ( state : & TopLevelState ) -> StateResult < Jail > {
341+ let key = * JAIL_KEY ;
342+ let prisoner = state. action_data ( & key) ?. map ( |data| decode_list :: < Prisoner > ( & data) ) . unwrap_or_default ( ) ;
343+ let indexed = prisoner. into_iter ( ) . map ( |c| ( c. address , c) ) . collect ( ) ;
344+ Ok ( Jail ( indexed) )
345+ }
346+
347+ pub fn save_to_state ( & self , state : & mut TopLevelState ) -> StateResult < ( ) > {
348+ let key = * JAIL_KEY ;
349+ if !self . 0 . is_empty ( ) {
350+ let encoded = encode_iter ( self . 0 . values ( ) ) ;
351+ state. update_action_data ( & key, encoded) ?;
352+ } else {
353+ state. remove_action_data ( & key) ;
354+ }
355+ Ok ( ( ) )
356+ }
357+
358+ pub fn get_prisoner ( & self , address : & Address ) -> Option < & Prisoner > {
359+ self . 0 . get ( address)
360+ }
361+
362+ #[ cfg( test) ]
363+ pub fn len ( & self ) -> usize {
364+ self . 0 . len ( )
365+ }
366+
367+ pub fn add ( & mut self , candidate : Candidate , custody_until : u64 , kicked_at : u64 ) {
368+ assert ! ( custody_until <= kicked_at) ;
369+ self . 0 . insert ( candidate. address , Prisoner {
370+ address : candidate. address ,
371+ deposit : candidate. deposit ,
372+ custody_until,
373+ kicked_at,
374+ } ) ;
375+ }
376+
377+ pub fn try_release ( & mut self , address : & Address , term_index : u64 ) -> ReleaseResult {
378+ match self . 0 . entry ( * address) {
379+ Entry :: Occupied ( entry) => {
380+ if entry. get ( ) . custody_until < term_index {
381+ ReleaseResult :: Released ( entry. remove ( ) )
382+ } else {
383+ ReleaseResult :: InCustody
384+ }
385+ }
386+ _ => ReleaseResult :: NotExists ,
387+ }
388+ }
389+
390+ pub fn kick_prisoners ( & mut self , term_index : u64 ) -> Vec < Prisoner > {
391+ let ( kicked, retained) : ( Vec < _ > , Vec < _ > ) = self . 0 . values ( ) . cloned ( ) . partition ( |c| c. kicked_at <= term_index) ;
392+ self . 0 = retained. into_iter ( ) . map ( |c| ( c. address , c) ) . collect ( ) ;
393+ kicked
394+ }
316395}
317396
318397fn decode_set < V > ( data : Option < & ActionData > ) -> BTreeSet < V >
@@ -956,4 +1035,228 @@ mod tests {
9561035 let result = state. action_data ( & * CANDIDATES_KEY ) . unwrap ( ) ;
9571036 assert_eq ! ( result, None ) ;
9581037 }
1038+
1039+ #[ test]
1040+ fn jail_try_free_not_existing ( ) {
1041+ let mut state = helpers:: get_temp_state ( ) ;
1042+
1043+ // Prepare
1044+ let address = Address :: from ( 1 ) ;
1045+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1046+ jail. add (
1047+ Candidate {
1048+ address,
1049+ deposit : 100 ,
1050+ nomination_ends_at : 0 ,
1051+ } ,
1052+ 10 ,
1053+ 20 ,
1054+ ) ;
1055+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1056+
1057+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1058+ let freed = jail. try_release ( & Address :: from ( 1000 ) , 5 ) ;
1059+ assert_eq ! ( freed, ReleaseResult :: NotExists ) ;
1060+ assert_eq ! ( jail. len( ) , 1 ) ;
1061+ assert_ne ! ( jail. get_prisoner( & address) , None ) ;
1062+ }
1063+
1064+ #[ test]
1065+ fn jail_try_release_none_until_custody ( ) {
1066+ let mut state = helpers:: get_temp_state ( ) ;
1067+
1068+ // Prepare
1069+ let address = Address :: from ( 1 ) ;
1070+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1071+ jail. add (
1072+ Candidate {
1073+ address,
1074+ deposit : 100 ,
1075+ nomination_ends_at : 0 ,
1076+ } ,
1077+ 10 ,
1078+ 20 ,
1079+ ) ;
1080+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1081+
1082+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1083+ let released = jail. try_release ( & address, 10 ) ;
1084+ assert_eq ! ( released, ReleaseResult :: InCustody ) ;
1085+ assert_eq ! ( jail. len( ) , 1 ) ;
1086+ assert_ne ! ( jail. get_prisoner( & address) , None ) ;
1087+ }
1088+
1089+ #[ test]
1090+ fn jail_try_release_prisoner_after_custody ( ) {
1091+ let mut state = helpers:: get_temp_state ( ) ;
1092+
1093+ // Prepare
1094+ let address = Address :: from ( 1 ) ;
1095+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1096+ jail. add (
1097+ Candidate {
1098+ address,
1099+ deposit : 100 ,
1100+ nomination_ends_at : 0 ,
1101+ } ,
1102+ 10 ,
1103+ 20 ,
1104+ ) ;
1105+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1106+
1107+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1108+ let released = jail. try_release ( & address, 11 ) ;
1109+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1110+
1111+ // Assert
1112+ assert_eq ! (
1113+ released,
1114+ ReleaseResult :: Released ( Prisoner {
1115+ address,
1116+ deposit: 100 ,
1117+ custody_until: 10 ,
1118+ kicked_at: 20 ,
1119+ } )
1120+ ) ;
1121+ assert_eq ! ( jail. len( ) , 0 ) ;
1122+ assert_eq ! ( jail. get_prisoner( & address) , None ) ;
1123+
1124+ let result = state. action_data ( & * JAIL_KEY ) . unwrap ( ) ;
1125+ assert_eq ! ( result, None , "Should clean the state if all prisoners are released" ) ;
1126+ }
1127+
1128+ #[ test]
1129+ fn jail_keep_prisoners_until_kick_at ( ) {
1130+ let mut state = helpers:: get_temp_state ( ) ;
1131+
1132+ // Prepare
1133+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1134+ jail. add (
1135+ Candidate {
1136+ address : Address :: from ( 1 ) ,
1137+ deposit : 100 ,
1138+ nomination_ends_at : 0 ,
1139+ } ,
1140+ 10 ,
1141+ 20 ,
1142+ ) ;
1143+ jail. add (
1144+ Candidate {
1145+ address : Address :: from ( 2 ) ,
1146+ deposit : 200 ,
1147+ nomination_ends_at : 0 ,
1148+ } ,
1149+ 15 ,
1150+ 25 ,
1151+ ) ;
1152+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1153+
1154+ // Kick
1155+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1156+ let kicked = jail. kick_prisoners ( 19 ) ;
1157+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1158+
1159+ // Assert
1160+ assert_eq ! ( kicked, Vec :: new( ) ) ;
1161+ assert_eq ! ( jail. len( ) , 2 ) ;
1162+ assert_ne ! ( jail. get_prisoner( & Address :: from( 1 ) ) , None ) ;
1163+ assert_ne ! ( jail. get_prisoner( & Address :: from( 2 ) ) , None ) ;
1164+ }
1165+
1166+ #[ test]
1167+ fn jail_partially_kick_prisoners ( ) {
1168+ let mut state = helpers:: get_temp_state ( ) ;
1169+
1170+ // Prepare
1171+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1172+ jail. add (
1173+ Candidate {
1174+ address : Address :: from ( 1 ) ,
1175+ deposit : 100 ,
1176+ nomination_ends_at : 0 ,
1177+ } ,
1178+ 10 ,
1179+ 20 ,
1180+ ) ;
1181+ jail. add (
1182+ Candidate {
1183+ address : Address :: from ( 2 ) ,
1184+ deposit : 200 ,
1185+ nomination_ends_at : 0 ,
1186+ } ,
1187+ 15 ,
1188+ 25 ,
1189+ ) ;
1190+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1191+
1192+ // Kick
1193+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1194+ let kicked = jail. kick_prisoners ( 20 ) ;
1195+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1196+
1197+ // Assert
1198+ assert_eq ! ( kicked, vec![ Prisoner {
1199+ address: Address :: from( 1 ) ,
1200+ deposit: 100 ,
1201+ custody_until: 10 ,
1202+ kicked_at: 20 ,
1203+ } ] ) ;
1204+ assert_eq ! ( jail. len( ) , 1 ) ;
1205+ assert_eq ! ( jail. get_prisoner( & Address :: from( 1 ) ) , None ) ;
1206+ assert_ne ! ( jail. get_prisoner( & Address :: from( 2 ) ) , None ) ;
1207+ }
1208+
1209+ #[ test]
1210+ fn jail_kick_all_prisoners ( ) {
1211+ let mut state = helpers:: get_temp_state ( ) ;
1212+
1213+ // Prepare
1214+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1215+ jail. add (
1216+ Candidate {
1217+ address : Address :: from ( 1 ) ,
1218+ deposit : 100 ,
1219+ nomination_ends_at : 0 ,
1220+ } ,
1221+ 10 ,
1222+ 20 ,
1223+ ) ;
1224+ jail. add (
1225+ Candidate {
1226+ address : Address :: from ( 2 ) ,
1227+ deposit : 200 ,
1228+ nomination_ends_at : 0 ,
1229+ } ,
1230+ 15 ,
1231+ 25 ,
1232+ ) ;
1233+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1234+
1235+ // Kick
1236+ let mut jail = Jail :: load_from_state ( & state) . unwrap ( ) ;
1237+ let kicked = jail. kick_prisoners ( 25 ) ;
1238+ jail. save_to_state ( & mut state) . unwrap ( ) ;
1239+
1240+ // Assert
1241+ assert_eq ! ( kicked, vec![
1242+ Prisoner {
1243+ address: Address :: from( 1 ) ,
1244+ deposit: 100 ,
1245+ custody_until: 10 ,
1246+ kicked_at: 20 ,
1247+ } ,
1248+ Prisoner {
1249+ address: Address :: from( 2 ) ,
1250+ deposit: 200 ,
1251+ custody_until: 15 ,
1252+ kicked_at: 25 ,
1253+ }
1254+ ] ) ;
1255+ assert_eq ! ( jail. len( ) , 0 ) ;
1256+ assert_eq ! ( jail. get_prisoner( & Address :: from( 1 ) ) , None ) ;
1257+ assert_eq ! ( jail. get_prisoner( & Address :: from( 2 ) ) , None ) ;
1258+
1259+ let result = state. action_data ( & * JAIL_KEY ) . unwrap ( ) ;
1260+ assert_eq ! ( result, None , "Should clean the state if all prisoners are kicked" ) ;
1261+ }
9591262}
0 commit comments