Skip to content

Commit c7f8a6d

Browse files
authored
💥 Timeout fixes after block 440000 (#1382)
1 parent 8eb5e4b commit c7f8a6d

File tree

5 files changed

+282
-31
lines changed

5 files changed

+282
-31
lines changed

crates/hyle-model/src/contract.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
cmp::Ordering,
23
fmt::Display,
34
ops::{Add, Deref, DerefMut, Sub},
45
};
@@ -757,6 +758,23 @@ pub enum TimeoutWindow {
757758
Timeout(BlockHeight),
758759
}
759760

761+
impl PartialOrd for TimeoutWindow {
762+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
763+
Some(self.cmp(other))
764+
}
765+
}
766+
767+
impl Ord for TimeoutWindow {
768+
fn cmp(&self, other: &Self) -> Ordering {
769+
match (self, other) {
770+
(TimeoutWindow::NoTimeout, TimeoutWindow::NoTimeout) => Ordering::Equal,
771+
(TimeoutWindow::Timeout(_), TimeoutWindow::NoTimeout) => Ordering::Less,
772+
(TimeoutWindow::NoTimeout, TimeoutWindow::Timeout(_)) => Ordering::Greater,
773+
(TimeoutWindow::Timeout(a), TimeoutWindow::Timeout(b)) => a.cmp(b),
774+
}
775+
}
776+
}
777+
760778
impl Display for TimeoutWindow {
761779
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762780
match &self {
@@ -989,3 +1007,90 @@ pub mod base64_field {
9891007
BASE64_STANDARD.decode(&s).map_err(serde::de::Error::custom)
9901008
}
9911009
}
1010+
1011+
#[cfg(test)]
1012+
mod tests_timeout_order {
1013+
use super::*;
1014+
1015+
#[test]
1016+
fn test_timeout_vs_timeout() {
1017+
let a = TimeoutWindow::Timeout(BlockHeight(5));
1018+
let b = TimeoutWindow::Timeout(BlockHeight(10));
1019+
assert!(a < b);
1020+
assert!(b > a);
1021+
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
1022+
assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
1023+
}
1024+
1025+
#[test]
1026+
fn test_timeout_vs_same_timeout() {
1027+
let a = TimeoutWindow::Timeout(BlockHeight(7));
1028+
let b = TimeoutWindow::Timeout(BlockHeight(7));
1029+
assert_eq!(a, b);
1030+
assert_eq!(a.cmp(&b), Ordering::Equal);
1031+
assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
1032+
}
1033+
1034+
#[test]
1035+
fn test_timeout_vs_no_timeout() {
1036+
let a = TimeoutWindow::Timeout(BlockHeight(15));
1037+
let b = TimeoutWindow::NoTimeout;
1038+
assert!(a < b);
1039+
assert_eq!(a.cmp(&b), Ordering::Less);
1040+
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
1041+
}
1042+
1043+
#[test]
1044+
fn test_no_timeout_vs_timeout() {
1045+
let a = TimeoutWindow::NoTimeout;
1046+
let b = TimeoutWindow::Timeout(BlockHeight(1));
1047+
assert!(a > b);
1048+
assert_eq!(a.cmp(&b), Ordering::Greater);
1049+
assert_eq!(a.partial_cmp(&b), Some(Ordering::Greater));
1050+
}
1051+
1052+
#[test]
1053+
fn test_no_timeout_vs_no_timeout() {
1054+
let a = TimeoutWindow::NoTimeout;
1055+
let b = TimeoutWindow::NoTimeout;
1056+
assert_eq!(a, b);
1057+
assert_eq!(a.cmp(&b), Ordering::Equal);
1058+
assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
1059+
}
1060+
1061+
#[test]
1062+
fn test_min_timeout_is_always_selected() {
1063+
let timeout = TimeoutWindow::Timeout(BlockHeight(42));
1064+
let no_timeout = TimeoutWindow::NoTimeout;
1065+
1066+
// min(NoTimeout, Timeout(42)) == Timeout(42)
1067+
assert_eq!(std::cmp::min(no_timeout.clone(), timeout.clone()), timeout);
1068+
1069+
// min(Timeout(42), NoTimeout) == Timeout(42)
1070+
assert_eq!(std::cmp::min(timeout.clone(), no_timeout.clone()), timeout);
1071+
}
1072+
1073+
#[test]
1074+
fn test_min_among_multiple_timeout_windows() {
1075+
use TimeoutWindow::{NoTimeout, Timeout};
1076+
1077+
let timeouts = [
1078+
NoTimeout,
1079+
Timeout(BlockHeight(100)),
1080+
Timeout(BlockHeight(50)),
1081+
NoTimeout,
1082+
Timeout(BlockHeight(200)),
1083+
Timeout(BlockHeight(10)),
1084+
];
1085+
1086+
// Utiliser min avec Iterator
1087+
let actual_min = timeouts
1088+
.iter()
1089+
.min()
1090+
.expect("La liste ne doit pas être vide");
1091+
1092+
let expected_min = &Timeout(BlockHeight(10));
1093+
1094+
assert_eq!(actual_min, expected_min);
1095+
}
1096+
}

crates/hyle-modules/src/node_state.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ pub struct NodeStateStore {
147147
unsettled_transactions: OrderedTxMap,
148148
}
149149

150+
/// Make sure we register the hyle contract with the same values before genesis, and in the genesis block
151+
pub fn hyle_contract_definition() -> Contract {
152+
Contract {
153+
name: "hyle".into(),
154+
program_id: ProgramId(vec![0, 0, 0, 0]),
155+
state: StateCommitment::default(),
156+
verifier: Verifier("hyle".to_owned()),
157+
timeout_window: TimeoutWindow::NoTimeout,
158+
}
159+
}
160+
150161
// TODO: we should register the 'hyle' TLD in the genesis block.
151162
impl Default for NodeStateStore {
152163
fn default() -> Self {
@@ -156,16 +167,9 @@ impl Default for NodeStateStore {
156167
contracts: HashMap::new(),
157168
unsettled_transactions: OrderedTxMap::default(),
158169
};
159-
ret.contracts.insert(
160-
"hyle".into(),
161-
Contract {
162-
name: "hyle".into(),
163-
program_id: ProgramId(vec![]),
164-
state: StateCommitment(vec![0]),
165-
verifier: Verifier("hyle".to_owned()),
166-
timeout_window: TimeoutWindow::Timeout(BlockHeight(5)),
167-
},
168-
);
170+
let hyle_contract = hyle_contract_definition();
171+
ret.contracts
172+
.insert(hyle_contract.name.clone(), hyle_contract);
169173
ret
170174
}
171175
}
@@ -373,23 +377,35 @@ impl NodeState {
373377
&self,
374378
blobs: T,
375379
) -> TimeoutWindow {
376-
let mut timeout = TimeoutWindow::NoTimeout;
377-
for blob in blobs {
378-
if let Some(contract_timeout) = self
379-
.contracts
380-
.get(&blob.contract_name)
381-
.map(|c| c.timeout_window.clone())
382-
{
383-
timeout = match (timeout, contract_timeout) {
384-
(TimeoutWindow::NoTimeout, contract_timeout) => contract_timeout,
385-
(TimeoutWindow::Timeout(a), TimeoutWindow::Timeout(b)) => {
386-
TimeoutWindow::Timeout(a.min(b))
380+
if self.current_height.0 > 445_000 {
381+
blobs
382+
.into_iter()
383+
.filter_map(|blob| {
384+
self.contracts
385+
.get(&blob.contract_name)
386+
.map(|c| c.timeout_window.clone())
387+
})
388+
.min()
389+
.unwrap_or(TimeoutWindow::NoTimeout)
390+
} else {
391+
let mut timeout = TimeoutWindow::NoTimeout;
392+
for blob in blobs {
393+
if let Some(contract_timeout) = self
394+
.contracts
395+
.get(&blob.contract_name)
396+
.map(|c| c.timeout_window.clone())
397+
{
398+
timeout = match (timeout, contract_timeout) {
399+
(TimeoutWindow::NoTimeout, contract_timeout) => contract_timeout,
400+
(TimeoutWindow::Timeout(a), TimeoutWindow::Timeout(b)) => {
401+
TimeoutWindow::Timeout(a.min(b))
402+
}
403+
_ => TimeoutWindow::NoTimeout,
387404
}
388-
_ => TimeoutWindow::NoTimeout,
389405
}
390406
}
407+
timeout
391408
}
392-
timeout
393409
}
394410

395411
fn handle_blob_tx(

crates/hyle-modules/src/node_state/test/contract_registration_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ async fn test_register_contract_composition() {
237237

238238
check_block_is_ok(&block);
239239

240-
let block = state.craft_block_and_handle(102 + 5, vec![]);
240+
let block = state.craft_block_and_handle(102 + 100, vec![]);
241241

242242
check_block_is_ok(&block);
243243

crates/hyle-modules/src/node_state/test/node_state_tests.rs

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,10 +1235,137 @@ async fn test_tx_reset_timeout_on_tx_settlement() {
12351235
);
12361236
}
12371237

1238+
#[test_log::test(tokio::test)]
1239+
async fn test_tx_with_hyle_blob_should_have_specific_timeout_before_block445_000() {
1240+
let hyle_timeout_window = TimeoutWindow::NoTimeout;
1241+
1242+
let mut state = new_node_state().await;
1243+
1244+
let tx1 = BlobTransaction::new(
1245+
"hyle@hyle",
1246+
vec![RegisterContractAction {
1247+
verifier: "test".into(),
1248+
program_id: ProgramId(vec![]),
1249+
state_commitment: StateCommitment(vec![0, 1, 2, 3]),
1250+
contract_name: ContractName::new("a1"),
1251+
..Default::default()
1252+
}
1253+
.as_blob("hyle".into(), None, None)],
1254+
);
1255+
1256+
let tx1_hash = tx1.hashed();
1257+
1258+
let tx2 = BlobTransaction::new(
1259+
"hyle@hyle",
1260+
vec![
1261+
new_blob("a1"),
1262+
RegisterContractAction {
1263+
verifier: "test".into(),
1264+
program_id: ProgramId(vec![]),
1265+
state_commitment: StateCommitment(vec![0, 1, 2, 3]),
1266+
contract_name: ContractName::new("c1"),
1267+
..Default::default()
1268+
}
1269+
.as_blob("hyle".into(), None, None),
1270+
],
1271+
);
1272+
1273+
let tx2_hash = tx2.hashed();
1274+
1275+
let block = state.craft_block_and_handle(100, vec![tx1.into(), tx2.into()]);
1276+
1277+
// Assert no timeout
1278+
assert_eq!(block.timed_out_txs, vec![]);
1279+
1280+
// Current Time out behaviour
1281+
let block = state.craft_block_and_handle(100 + 5, vec![]);
1282+
1283+
assert_eq!(block.timed_out_txs, vec![]);
1284+
let block = state.craft_block_and_handle(100 + 100, vec![]);
1285+
assert_eq!(block.timed_out_txs, vec![]);
1286+
1287+
let block = state.craft_block_and_handle(1000, vec![]);
1288+
assert_eq!(block.timed_out_txs, vec![]);
1289+
1290+
assert!(state.unsettled_transactions.get(&tx2_hash).is_some());
1291+
}
1292+
1293+
#[test_log::test(tokio::test)]
1294+
async fn test_tx_with_hyle_blob_should_have_specific_timeout_after_block445_000() {
1295+
let hyle_timeout_window = TimeoutWindow::NoTimeout;
1296+
1297+
let mut state = new_node_state().await;
1298+
1299+
let tx1 = BlobTransaction::new(
1300+
"hyle@hyle",
1301+
vec![RegisterContractAction {
1302+
verifier: "test".into(),
1303+
program_id: ProgramId(vec![]),
1304+
state_commitment: StateCommitment(vec![0, 1, 2, 3]),
1305+
contract_name: ContractName::new("a1"),
1306+
..Default::default()
1307+
}
1308+
.as_blob("hyle".into(), None, None)],
1309+
);
1310+
1311+
let tx1_hash = tx1.hashed();
1312+
1313+
let tx2 = BlobTransaction::new(
1314+
"hyle@hyle",
1315+
vec![
1316+
new_blob("a1"),
1317+
RegisterContractAction {
1318+
verifier: "test".into(),
1319+
program_id: ProgramId(vec![]),
1320+
state_commitment: StateCommitment(vec![0, 1, 2, 3]),
1321+
contract_name: ContractName::new("c1"),
1322+
..Default::default()
1323+
}
1324+
.as_blob("hyle".into(), None, None),
1325+
],
1326+
);
1327+
1328+
let tx2_hash = tx2.hashed();
1329+
1330+
let tx3 = BlobTransaction::new(
1331+
"hyle@hyle",
1332+
vec![
1333+
new_blob("a1"),
1334+
RegisterContractAction {
1335+
verifier: "test".into(),
1336+
program_id: ProgramId(vec![]),
1337+
state_commitment: StateCommitment(vec![0, 1, 2, 3]),
1338+
contract_name: ContractName::new("c2"),
1339+
..Default::default()
1340+
}
1341+
.as_blob("hyle".into(), None, None),
1342+
],
1343+
);
1344+
1345+
let tx3_hash = tx3.hashed();
1346+
1347+
let block = state.craft_block_and_handle(445_001, vec![tx1.into(), tx2.into()]);
1348+
1349+
// Assert no timeout
1350+
assert_eq!(block.timed_out_txs, vec![]);
1351+
1352+
// Current Time out behaviour
1353+
let block = state.craft_block_and_handle(445_001 + 5, vec![]);
1354+
1355+
assert_eq!(block.timed_out_txs, vec![]);
1356+
let block = state.craft_block_and_handle(445_001 + 100, vec![]);
1357+
assert_eq!(block.timed_out_txs, vec![tx2_hash.clone()]);
1358+
1359+
let block = state.craft_block_and_handle(446_001, vec![]);
1360+
assert_eq!(block.timed_out_txs, vec![]);
1361+
1362+
assert!(state.unsettled_transactions.get(&tx2_hash).is_none());
1363+
}
1364+
12381365
// Check hyle-modules/src/node_state.rs l127 for the timeout window value
12391366
#[test_log::test(tokio::test)]
12401367
async fn test_tx_with_hyle_blob_should_have_specific_timeout() {
1241-
let hyle_timeout_window = BlockHeight(5);
1368+
let hyle_timeout_window = TimeoutWindow::NoTimeout;
12421369

12431370
let mut state = new_node_state().await;
12441371
let a1 = ContractName::new("a1");
@@ -1254,7 +1381,7 @@ async fn test_tx_with_hyle_blob_should_have_specific_timeout() {
12541381
assert_eq!(block.timed_out_txs, vec![]);
12551382

12561383
// Time out
1257-
let block = state.craft_block_and_handle(100 + hyle_timeout_window.0, vec![]);
1384+
let block = state.craft_block_and_handle(100 + 100, vec![]);
12581385

12591386
// Assert that tx has timed out
12601387
assert_eq!(block.timed_out_txs, vec![tx_hash.clone()]);

src/genesis.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use hyle_modules::{
2121
bus::{BusClientSender, SharedMessageBus},
2222
bus_client, handle_messages,
2323
modules::Module,
24+
node_state::hyle_contract_definition,
2425
};
2526
use hyllar::{client::tx_executor_handler::transfer, Hyllar, FAUCET_ID};
2627
use serde::{Deserialize, Serialize};
@@ -538,13 +539,15 @@ impl Genesis {
538539

539540
let mut register_tx = ProvableBlobTx::new("hyle@hyle".into());
540541

542+
let hyle_contract = hyle_contract_definition();
543+
541544
register_hyle_contract(
542545
&mut register_tx,
543-
"hyle".into(),
544-
"hyle".into(),
545-
ProgramId(vec![0, 0, 0, 0]),
546-
StateCommitment::default(),
547-
Some(TimeoutWindow::NoTimeout),
546+
hyle_contract.name.clone(),
547+
hyle_contract.name.0.clone().into(),
548+
hyle_contract.program_id.clone(),
549+
hyle_contract.state.clone(),
550+
Some(hyle_contract.timeout_window),
548551
None,
549552
)
550553
.expect("register hyle");

0 commit comments

Comments
 (0)