Skip to content

Commit 8b7da8b

Browse files
foriequal0mergify[bot]
authored andcommitted
Implement jail
1 parent e6411ca commit 8b7da8b

File tree

2 files changed

+637
-23
lines changed

2 files changed

+637
-23
lines changed

core/src/consensus/stake/action_data.rs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4243
pub 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

318397
fn 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

Comments
 (0)