Module 0x7::dead_mans_switch_tracker
Dead Man’s Switch Tracker Module
This module implements a dead man’s switch mechanism for trading orders, ensuring that orders are automatically invalidated if a trader’s session expires without periodic keep-alive updates. This security feature prevents stale orders from being executed if a trader loses connection or becomes unresponsive.
Overview
The dead man’s switch works by requiring traders to periodically send keep-alive signals. If a trader fails to update their keep-alive state within a specified timeout period, all their orders placed during that session become invalid and can be cancelled.
Key Concepts
Session Management
- Session: A time-bound period during which a trader’s orders are considered valid
- Session Start Time: The beginning of the current session (when it was started or restarted)
- Expiration Time: When the current session will expire if not renewed
- Timeout: The duration for which a keep-alive update remains valid
Order Validation
An order is considered valid if:
- The trader has no keep-alive state set (no dead man’s switch enabled), OR
- The order was created after the current session started, AND
- The current time is before the session expiration time
Session Lifecycle
First Keep-Alive Update:
- Creates a new session with
session_start_time = 0(all existing orders remain valid) - Sets
expiration_time = current_time + timeout
Subsequent Updates (Before Expiration):
- Extends the current session:
expiration_time = current_time + timeout - Keeps the same
session_start_time(existing orders remain valid)
Update After Expiration:
- Starts a new session:
session_start_time = current_time - Sets new
expiration_time = current_time + timeout - All orders placed before this time are invalidated
Events
-
KeepAliveUpdateEvent: Emitted when a trader updates their keep-alive state -
KeepAliveDisabledEvent: Emitted when a trader disables their keep-alive -
MinKeepAliveTimeUpdatedEvent: Emitted when the minimum keep-alive time is updated
use 0x1::big_ordered_map;
use 0x1::event;
use 0x1::option;
use 0x1::timestamp;
use 0x7::order_book_utils;
Enum KeepAliveUpdateEvent
#[event]
enum KeepAliveUpdateEvent has copy, drop, store
Variants
V1
Fields
-
parent: address -
market: address -
account: address -
session_start_time_secs: u64 -
expiration_time_secs: u64
Enum KeepAliveDisabledEvent
#[event]
enum KeepAliveDisabledEvent has copy, drop, store
Variants
V1
Fields
-
parent: address -
market: address -
account: address -
was_registered: bool
Enum MinKeepAliveTimeUpdatedEvent
#[event]
enum MinKeepAliveTimeUpdatedEvent has copy, drop, store
Variants
V1
Fields
-
parent: address -
market: address -
old_min_keep_alive_time_secs: u64 -
new_min_keep_alive_time_secs: u64
Struct KeepAliveState
struct KeepAliveState has store
Fields
-
session_start_time_secs: u64 -
expiration_time_secs: u64
Struct DeadMansSwitchTracker
struct DeadMansSwitchTracker has store
Fields
-
min_keep_alive_time_secs: u64 -
state: big_ordered_map::BigOrderedMap<address, dead_mans_switch_tracker::KeepAliveState>
Constants
Error code when the provided keep-alive timeout is shorter than the minimum allowed
const E_KEEP_ALIVE_TIMEOUT_TOO_SHORT: u64 = 0;
Function new_dead_mans_switch_tracker
Creates a new dead man’s switch tracker
Parameters
min_keep_alive_time_secs: Minimum timeout duration that traders must use. This prevents abuse by forcing traders to set reasonable timeout periods.
Returns
A new DeadMansSwitchTracker instance with no active sessions
Example
let tracker = new_dead_mans_switch_tracker(60); // 60 second minimum
public(friend) fun new_dead_mans_switch_tracker(min_keep_alive_time_secs: u64): dead_mans_switch_tracker::DeadMansSwitchTracker
Implementation
public(friend) fun new_dead_mans_switch_tracker(
min_keep_alive_time_secs: u64
): DeadMansSwitchTracker {
DeadMansSwitchTracker {
min_keep_alive_time_secs,
state: order_book_utils::new_default_big_ordered_map()
}
}
Function set_min_keep_alive_time_secs
public(friend) fun set_min_keep_alive_time_secs(tracker: &mut dead_mans_switch_tracker::DeadMansSwitchTracker, parent: address, market: address, min_keep_alive_time_secs: u64)
Implementation
public(friend) fun set_min_keep_alive_time_secs(
tracker: &mut DeadMansSwitchTracker,
parent: address,
market: address,
min_keep_alive_time_secs: u64
) {
let old_min_keep_alive_time_secs = tracker.min_keep_alive_time_secs;
tracker.min_keep_alive_time_secs = min_keep_alive_time_secs;
event::emit(
MinKeepAliveTimeUpdatedEvent::V1 {
parent,
market,
old_min_keep_alive_time_secs,
new_min_keep_alive_time_secs: min_keep_alive_time_secs
}
);
}
Function is_order_valid
Checks if an order is valid based on the dead man’s switch state
An order is valid if:
- No keep-alive state exists for the account (dead man’s switch not enabled), OR
- The order was created after the current session started AND the session hasn’t expired
Parameters
tracker: Reference to the dead man’s switch trackeraccount: The trader’s addressorder_creation_time_secs: When the order was created (in seconds since epoch)
Returns
true if the order is valid, false if it should be cancelled
Validation Logic
if no keep-alive state:
return true // No dead man's switch, all orders valid
if order_creation_time < session_start_time:
return false // Order from expired session
if current_time > expiration_time:
return false // Session expired (exclusive of expiration time)
return true // Order valid
Example
let order_time = 1000;
let is_valid = is_order_valid(&tracker, trader_addr, order_time);
if (!is_valid) {
// Cancel the order
}
public fun is_order_valid(tracker: &dead_mans_switch_tracker::DeadMansSwitchTracker, account: address, order_creation_time_secs: option::Option<u64>): bool
Implementation
public fun is_order_valid(
tracker: &DeadMansSwitchTracker,
account: address,
order_creation_time_secs: Option<u64>
): bool {
let itr = tracker.state.internal_find(&account);
if (itr.iter_is_end(&tracker.state)) {
// No keep-alive set, so all orders are valid
return true;
};
let current_time = aptos_std::timestamp::now_seconds();
let order_creation_time_secs =
if (order_creation_time_secs.is_some()) {
order_creation_time_secs.destroy_some()
} else {
current_time
};
let state = itr.iter_borrow(&tracker.state);
if (state.session_start_time_secs > order_creation_time_secs) {
// Order was placed before the session started, so it is invalid
return false;
};
state.expiration_time_secs >= current_time
}
Function disable_keep_alive
fun disable_keep_alive(tracker: &mut dead_mans_switch_tracker::DeadMansSwitchTracker, parent: address, market: address, account: address)
Implementation
fun disable_keep_alive(
tracker: &mut DeadMansSwitchTracker,
parent: address,
market: address,
account: address
) {
let removed = tracker.state.remove_or_none(&account);
let was_registered = removed.is_some();
if (was_registered) {
let KeepAliveState { session_start_time_secs: _, expiration_time_secs: _ } =
removed.destroy_some();
} else {
removed.destroy_none();
};
event::emit(
KeepAliveDisabledEvent::V1 { parent, market, account, was_registered }
);
}
Function keep_alive
Updates the keep-alive state for a trader
This is the core function traders call to maintain their session and prevent their orders from expiring. Behavior depends on the current state:
- First Update (No Prior State):
- Creates a new session with
session_start_time = 0 - All existing orders remain valid
- Sets
expiration_time = current_time + timeout_seconds
- Update Within Valid Session:
- Extends the current session
- Updates
expiration_time = current_time + timeout_seconds - Keeps existing
session_start_time(orders remain valid)
- Update After Session Expired:
- Starts a new session with
session_start_time = current_time - All orders placed before now are invalidated
- Sets
expiration_time = current_time + timeout_seconds
Parameters
tracker: Mutable reference to the dead man’s switch trackeraccount: The trader’s addresstimeout_seconds: Duration in seconds until the session expires. Must be >=min_keep_alive_time_secsor 0 to disable.
Special Cases
- If
timeout_seconds == 0: Disables the keep-alive (callsdisable_keep_alive)
Errors
E_KEEP_ALIVE_TIMEOUT_TOO_SHORT: If timeout is less than the minimum and not zero
Effects
- Updates or creates the trader’s keep-alive state
- Emits a
KeepAliveUpdateEvent
Example
// Update with 5 minute timeout
update_keep_alive_state(&mut tracker, trader_addr, 300);
// Disable dead man's switch
update_keep_alive_state(&mut tracker, trader_addr, 0);
public(friend) fun keep_alive(tracker: &mut dead_mans_switch_tracker::DeadMansSwitchTracker, parent: address, market: address, account: address, timeout_seconds: u64)
Implementation
public(friend) fun keep_alive(
tracker: &mut DeadMansSwitchTracker,
parent: address,
market: address,
account: address,
timeout_seconds: u64
) {
if (timeout_seconds == 0) {
disable_keep_alive(tracker, parent, market, account);
return;
};
assert!(
timeout_seconds >= tracker.min_keep_alive_time_secs,
E_KEEP_ALIVE_TIMEOUT_TOO_SHORT // ERROR_KEEP_ALIVE_TIMEOUT_TOO_SHORT
);
let current_time = aptos_std::timestamp::now_seconds();
let expiration_time = current_time + timeout_seconds;
let itr = tracker.state.internal_find(&account);
if (!itr.iter_is_end(&tracker.state)) {
let state = itr.iter_borrow_mut(&mut tracker.state);
if (current_time > state.expiration_time_secs) {
// Start a new session - this means any order placed before this time is invalidated
state.session_start_time_secs = current_time;
};
// Update existing session
state.expiration_time_secs = expiration_time;
event::emit(
KeepAliveUpdateEvent::V1 {
parent,
market,
account,
session_start_time_secs: state.session_start_time_secs,
expiration_time_secs: state.expiration_time_secs
}
);
} else {
let new_state = KeepAliveState {
session_start_time_secs: 0, // this means that all existing orders are valid
expiration_time_secs: expiration_time
};
tracker.state.add(account, new_state);
event::emit(
KeepAliveUpdateEvent::V1 {
parent,
market,
account,
session_start_time_secs: 0,
expiration_time_secs: expiration_time
}
);
}
}