Skip to content

Commit 8e2f724

Browse files
authored
feat(coprocessor): add --seed-start-block flag to host-listener poller (backport 0.13.x) (#3124)
feat(coprocessor): add --seed-start-block flag to host-listener poller Without a persisted anchor the poller used to seed its cursor from host_chain_blocks_valid, a table owned by the WS listener. That cross-service read was a bootstrap aid from when the poller was first introduced into already-running systems; on a fresh database it is a startup race: if the poller initializes before the WS listener has written anything, it anchors at 0 and walks the chain from genesis (observed on Amoy pollers on devnet/testnet, ~2-3 months behind head). Drop the cross-service read entirely. The poller's seed sources are now its own: the persisted anchor row always wins; otherwise --seed-start-block seeds deterministically (non-negative = absolute height, 0 = genesis explicitly, negative = that many blocks behind head); otherwise the poller exits with an explicit error, so a missing flag on a new chain surfaces as a crash-loop at deploy time instead of a silent weeks-long genesis walk. The flag is deliberately initialization-only, not the listener's --start-at-block semantics (where the flag beats persisted state on every startup): once the anchor row exists the flag is inert, so a flag left in Helm values can never rewind the poller on restart, skip blocks past the anchor, or clobber a drift-revert anchor reset. Deliberate rewinds remain a manual host_listener_poller_state update. Adding a poller to an already-running listener-only deployment now requires setting --seed-start-block explicitly (the removed fallback previously covered that case). The e2e compose poller passes --seed-start-block=0: fresh local chain, genesis is the honest value.
1 parent 7a7e9ec commit 8e2f724

4 files changed

Lines changed: 77 additions & 1 deletion

File tree

coprocessor/fhevm-engine/host-listener/src/bin/poller.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ struct Args {
4040
#[arg(long, help = "PostgreSQL connection URL")]
4141
database_url: DatabaseURL,
4242

43+
#[arg(
44+
long,
45+
default_value = None,
46+
help = "Initial sync block used only when no poller state exists yet; an existing anchor always wins (can be negative from last block)",
47+
allow_hyphen_values = true
48+
)]
49+
seed_start_block: Option<i64>,
50+
4351
#[arg(
4452
long,
4553
default_value_t = 15,
@@ -180,6 +188,7 @@ async fn main() -> anyhow::Result<()> {
180188
max_http_retries: args.max_http_retries,
181189
rpc_compute_units_per_second: args.rpc_compute_units_per_second,
182190
health_port: args.health_port,
191+
seed_start_block: args.seed_start_block,
183192
dependence_cache_size: args.dependence_cache_size,
184193
dependence_by_connexity: args.dependence_by_connexity,
185194
dependence_cross_block: args.dependence_cross_block,

coprocessor/fhevm-engine/host-listener/src/poller/mod.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,58 @@ fn handle_rpc_failure<E: std::fmt::Display>(
7070
}
7171
}
7272

73+
/// Seed for `host_listener_poller_state` when no anchor row exists yet; an
74+
/// existing row always wins in the caller, so restarts never rewind or skip.
75+
/// A non-negative value is an absolute height (0 = genesis, explicitly), a
76+
/// negative value means that many blocks behind the current head. Unset is an
77+
/// error: the first start for a chain must state where to begin.
78+
#[derive(Debug, PartialEq, Eq)]
79+
enum StartAnchor {
80+
Block(i64),
81+
BehindHead(u64),
82+
}
83+
84+
fn resolve_start_anchor(seed_start_block: Option<i64>) -> Result<StartAnchor> {
85+
match seed_start_block {
86+
Some(block) if block >= 0 => Ok(StartAnchor::Block(block)),
87+
Some(delta) => Ok(StartAnchor::BehindHead(delta.unsigned_abs())),
88+
None => Err(anyhow!(
89+
"no poller state for this chain and no --seed-start-block; \
90+
set it explicitly (0 for genesis)"
91+
)),
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod start_anchor_tests {
97+
use super::{resolve_start_anchor, StartAnchor};
98+
99+
#[test]
100+
fn non_negative_flag_is_absolute() {
101+
assert_eq!(
102+
resolve_start_anchor(Some(7)).unwrap(),
103+
StartAnchor::Block(7)
104+
);
105+
assert_eq!(
106+
resolve_start_anchor(Some(0)).unwrap(),
107+
StartAnchor::Block(0)
108+
);
109+
}
110+
111+
#[test]
112+
fn negative_flag_is_behind_head() {
113+
assert_eq!(
114+
resolve_start_anchor(Some(-10_000)).unwrap(),
115+
StartAnchor::BehindHead(10_000)
116+
);
117+
}
118+
119+
#[test]
120+
fn no_flag_is_an_error() {
121+
assert!(resolve_start_anchor(None).is_err());
122+
}
123+
}
124+
73125
#[derive(Clone, Debug)]
74126
pub struct PollerConfig {
75127
pub url: String,
@@ -88,6 +140,9 @@ pub struct PollerConfig {
88140
/// Higher values = less throttling.
89141
pub rpc_compute_units_per_second: u64,
90142
pub health_port: u16,
143+
/// Initial sync anchor when no poller state exists yet
144+
/// (initialization-only; >= 0 absolute, negative from head).
145+
pub seed_start_block: Option<i64>,
91146
// Dependence chain settings
92147
pub dependence_cache_size: u16,
93148
pub dependence_by_connexity: bool,
@@ -195,7 +250,17 @@ pub async fn run_poller(config: PollerConfig) -> Result<()> {
195250
Some(block) => u64::try_from(block)
196251
.context("last_caught_up_block cannot be negative")?,
197252
None => {
198-
let initial = db.read_last_valid_block().await.unwrap_or(0);
253+
let initial = match resolve_start_anchor(config.seed_start_block)? {
254+
StartAnchor::Block(block) => block,
255+
StartAnchor::BehindHead(delta) => {
256+
let head = client.latest_block_number().await.context(
257+
"Failed to fetch head to resolve negative seed-start-block",
258+
)?;
259+
i64::try_from(head.saturating_sub(delta)).context(
260+
"start block computed from head is out of range",
261+
)?
262+
}
263+
};
199264
db.poller_set_last_caught_up_block(chain_id, initial)
200265
.await?;
201266
db.tick.update();

coprocessor/fhevm-engine/host-listener/tests/poller_integration_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ async fn poller_catches_up_to_safe_tip(
182182
max_http_retries: 0,
183183
rpc_compute_units_per_second: 1000,
184184
health_port: 18081,
185+
seed_start_block: Some(0),
185186
dependence_cache_size: 10_000,
186187
dependence_by_connexity: false,
187188
dependence_cross_block: false,

test-suite/fhevm/docker-compose/coprocessor-docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ services:
5555
command:
5656
- host_listener_poller
5757
- --database-url=${DATABASE_URL}
58+
- --seed-start-block=0
5859
- --acl-contract-address=${ACL_CONTRACT_ADDRESS}
5960
- --tfhe-contract-address=${FHEVM_EXECUTOR_CONTRACT_ADDRESS}
6061
- --kms-generation-address=${KMS_GENERATION_ADDRESS}

0 commit comments

Comments
 (0)