1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use rialo_limits::max_rex_output_serialized_bytes;
use serde::{Deserialize, Serialize};
use crate::{
rex_info::{RexInfo, TimestampMs},
websocket_op::WebSocketOperation,
};
/// Configuration for REX duties.
///
/// This struct defines the parameters that control how REX duties are scheduled,
/// executed, and finalized within the system.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RexDutyConfig {
/// The number of validators assigned to each REX duty.
pub validators_per_duty: u32,
/// The time in milliseconds needed for the fulfillment of a REX request.
pub request_delay_ms: TimestampMs,
/// Max size of REX output in bytes.
pub max_output_size: u32,
/// Whether this duty is ASAP (execute as soon as possible) rather than scheduled.
pub is_asap: bool,
/// WebSocket operation information needed to update the WebSocketRegistry with the
/// on-chain connection state when this duty completes.
pub websocket_op: Option<WebSocketOperation>,
/// Marks a synthetic DKG partial-decrypt child duty whose updates
/// are bank-persisted in `RexDutiesMap` so the combiner can read
/// them after snapshot reload, but `result_cache` must NOT generate
/// a Report tx for it (no on-chain RexInfo to report against; the
/// user-visible result rides the *parent* rex_id).
/// `#[serde(default)]` lets snapshots written before this field
/// existed deserialize cleanly with `false`. Removing the default
/// would break forward-compat on snapshot reload.
#[serde(default)]
pub is_synthetic_partial: bool,
}
impl Default for RexDutyConfig {
fn default() -> Self {
Self {
// 3-validator committees gives us 99.996% success rate leveraging TEE guarantees
validators_per_duty: Self::DEFAULT_VALIDATORS_PER_DUTY,
// Time to execute a REX task, determined experimentally
request_delay_ms: Self::DEFAULT_REQUEST_DELAY_MS,
// Safe size for the default number of validators.
max_output_size: Self::DEFAULT_MAX_OUTPUT_SIZE,
is_asap: false,
websocket_op: None,
is_synthetic_partial: false,
}
}
}
impl RexDutyConfig {
/// Constructs an instance based on RexInfo.
pub fn from_rex_info(rex_info: &RexInfo) -> Self {
Self {
validators_per_duty: rex_info.validators_per_duty,
request_delay_ms: rex_info.request_delay_ms,
max_output_size: max_rex_output_serialized_bytes(rex_info.validators_per_duty) as u32,
is_asap: rex_info.is_asap(),
websocket_op: rex_info.websocket_op(),
is_synthetic_partial: false,
}
}
/// Safe max size limit for the default number of validators.
pub const DEFAULT_MAX_OUTPUT_SIZE: u32 = 10 * 1024;
/// A reasonably safe value to make one-off REX requests succeed with >22 bits of security.
/// This value is based on the example from the Rialo whitepaper, that is N = 130, D = 43.
pub const ONE_OFF_VALIDATORS_PER_DUTY: u32 = 13;
/// A small value for REX requests that is reasonably cheap.
/// Note that by itself it doesn't provide a response reliably enough
/// and needs retries in case of failure.
pub const DEFAULT_VALIDATORS_PER_DUTY: u32 = 5;
/// Most REX requests should complete within a few blocks.
pub const DEFAULT_REQUEST_DELAY_MS: TimestampMs = 300; // 0.3s
/// The minimum allowed delay for REX requests, in milliseconds.
pub const MIN_REX_REQUEST_DELAY: TimestampMs = 100; // 0.1s
/// The maximum allowed delay for REX requests, in milliseconds.
pub const MAX_REX_REQUEST_DELAY_MS: TimestampMs = 240_000; // 240s
/// Default maximum number of ASAP duties to process in a single consensus round
pub const DEFAULT_MAX_ASAP_DUTIES_PER_COMMIT: usize = 5;
/// Default timeout for ASAP duties in milliseconds
pub const DEFAULT_ASAP_TIMEOUT_MS: TimestampMs = 5_000; // 5s
/// Pipeline overhead budgeted for DKG-encrypted duties, on top of the HTTP
/// `request_delay_ms`. A plain ASAP duty is collected within
/// `DEFAULT_ASAP_TIMEOUT_MS` (one validator fetches directly); an encrypted
/// duty has no result until the committee posts threshold partials and the
/// combiner runs — a multi-round pipeline that overruns the plain timeout.
///
/// Sizes the encrypted duty's collection window (`DutyRequest::inner`,
/// `created + overhead + request_delay_ms`) so the combine result lands
/// inside it instead of being dropped as `OutsideCollectionWindow`. The
/// on-chain `original_target_timestamp` bound
/// (`max_asap_original_target_timestamp`) accepts that window plus a further
/// `overhead` grace for the created→scheduled gap. Independent of the DRA
/// `deadline_timestamp`, which only gates partial submission.
///
/// The realized margin is exposed as `rex_dkg_combine_window_slack_ms`.
pub const DKG_ENCRYPTED_PIPELINE_OVERHEAD_MS: TimestampMs = 30_000; // 30s
/// Sets the REX request delay (in milliseconds).
///
/// # Arguments
///
/// * `delay` - The time in milliseconds to wait before issuing a REX request.
pub fn set_rex_request_delay_ms(mut self, delay: TimestampMs) -> Self {
self.request_delay_ms = delay;
self
}
/// Sets the desired number of validators per duty.
pub fn set_validators_per_duty(mut self, validators_per_duty: u32) -> RexDutyConfig {
self.validators_per_duty = validators_per_duty;
self
}
/// Marks this duty as a synthetic DKG partial-decrypt child. See the
/// field doc on [`RexDutyConfig::is_synthetic_partial`] for why such
/// duties are bank-persisted but excluded from Report-tx emission.
pub fn set_is_synthetic_partial(mut self, is_synthetic_partial: bool) -> Self {
self.is_synthetic_partial = is_synthetic_partial;
self
}
}