pub struct Engine { /* private fields */ }Expand description
The Overlay Services Engine.
Orchestrates topic managers, lookup services, storage, and advertisements. Does NOT include an HTTP server — that’s the job of overlay-cloudflare.
Implementations§
Source§impl Engine
impl Engine
Sourcepub const SYNC_RESPONSE_DEFAULT_LIMIT: u64 = 500
pub const SYNC_RESPONSE_DEFAULT_LIMIT: u64 = 500
Page size used by /requestSyncResponse when the (public) caller
omits limit. A bounded page is not lossy for a conforming
initiator: ours (crate::gasp::GASPSync) keeps requesting pages
for as long as the since score cursor advances (gate finding M1 —
it must NOT require a “full” page to continue, because this clamp
makes a full-by-the-initiator’s-limit page impossible).
Sourcepub const SYNC_RESPONSE_MAX_LIMIT: u64 = 1000
pub const SYNC_RESPONSE_MAX_LIMIT: u64 = 1000
Hard cap on a /requestSyncResponse page. Before bsv-low #291 an
absent/huge caller-supplied limit meant an unbounded scan of the
outputs table on a public route.
Sourcepub fn clamp_sync_limit(requested: Option<u64>) -> u64
pub fn clamp_sync_limit(requested: Option<u64>) -> u64
Clamp the public /requestSyncResponse page size: absent → the
default page; anything larger than the cap → the cap. The result is
ALWAYS Some-worthy — a sync response is never unbounded (bsv-low
#291). Completeness relies on the INITIATOR paging while its since
cursor advances (see SYNC_RESPONSE_DEFAULT_LIMIT’s doc) — the
responder alone cannot page for it.
Sourcepub fn new(
managers: HashMap<String, Box<dyn TopicManager>>,
lookup_services: HashMap<String, Box<dyn LookupService>>,
storage: Box<dyn Storage>,
advertiser: Option<Box<dyn Advertiser>>,
config: EngineConfig,
) -> Self
pub fn new( managers: HashMap<String, Box<dyn TopicManager>>, lookup_services: HashMap<String, Box<dyn LookupService>>, storage: Box<dyn Storage>, advertiser: Option<Box<dyn Advertiser>>, config: EngineConfig, ) -> Self
Create a new Overlay Services Engine.
Sourcepub fn with_chain_tracker(
managers: HashMap<String, Box<dyn TopicManager>>,
lookup_services: HashMap<String, Box<dyn LookupService>>,
storage: Box<dyn Storage>,
advertiser: Option<Box<dyn Advertiser>>,
broadcaster: Option<Box<dyn Broadcaster>>,
chain_tracker: Option<Box<dyn ChainTracker>>,
config: EngineConfig,
) -> Self
pub fn with_chain_tracker( managers: HashMap<String, Box<dyn TopicManager>>, lookup_services: HashMap<String, Box<dyn LookupService>>, storage: Box<dyn Storage>, advertiser: Option<Box<dyn Advertiser>>, broadcaster: Option<Box<dyn Broadcaster>>, chain_tracker: Option<Box<dyn ChainTracker>>, config: EngineConfig, ) -> Self
Create a new Engine with an optional ChainTracker for SPV verification.
Sourcepub fn with_all(
managers: HashMap<String, Box<dyn TopicManager>>,
lookup_services: HashMap<String, Box<dyn LookupService>>,
storage: Box<dyn Storage>,
advertiser: Option<Box<dyn Advertiser>>,
broadcaster: Option<Box<dyn Broadcaster>>,
arc_broadcaster: Option<Box<dyn ArcBroadcaster>>,
chain_tracker: Option<Box<dyn ChainTracker>>,
config: EngineConfig,
) -> Self
pub fn with_all( managers: HashMap<String, Box<dyn TopicManager>>, lookup_services: HashMap<String, Box<dyn LookupService>>, storage: Box<dyn Storage>, advertiser: Option<Box<dyn Advertiser>>, broadcaster: Option<Box<dyn Broadcaster>>, arc_broadcaster: Option<Box<dyn ArcBroadcaster>>, chain_tracker: Option<Box<dyn ChainTracker>>, config: EngineConfig, ) -> Self
Create a new Engine with all optional components.
Sourcepub fn set_arc_broadcaster(&mut self, arc: Box<dyn ArcBroadcaster>)
pub fn set_arc_broadcaster(&mut self, arc: Box<dyn ArcBroadcaster>)
Set the ARC broadcaster for network broadcast to miners.
When set, the Engine broadcasts transactions to ARC during Phase 2
of submit() for CurrentTx submissions.
Sourcepub fn set_gasp_remote_factory(&mut self, factory: Box<dyn GASPRemoteFactory>)
pub fn set_gasp_remote_factory(&mut self, factory: Box<dyn GASPRemoteFactory>)
Set the GASP remote factory for peer communication during sync.
Platform-specific crates (like overlay-cloudflare) provide an implementation that creates HTTP-based remotes using their native fetch API.
Sourcepub fn set_ship_lookup_client(&mut self, client: Box<dyn ShipLookupClient>)
pub fn set_ship_lookup_client(&mut self, client: Box<dyn ShipLookupClient>)
Set the platform lookup transport for network SHIP discovery.
Trackers come from EngineConfig::slap_trackers.
Sourcepub fn set_ship_bootstrap_peers(
&mut self,
topic: impl Into<String>,
peers: Vec<String>,
)
pub fn set_ship_bootstrap_peers( &mut self, topic: impl Into<String>, peers: Vec<String>, )
Keep these peers in a topic’s SHIP union even when trackers fail.
This leaves the existing SyncTarget serialization unchanged.
Such topics run ahead of tracker imports so discovery and sync can
proceed without spending the tick on importing discovery records.
Sourcepub fn set_ancestor_fetcher(&mut self, fetcher: Rc<dyn AncestorFetcher>)
pub fn set_ancestor_fetcher(&mut self, fetcher: Rc<dyn AncestorFetcher>)
Set the OPT-IN chain-backed ancestor fetcher for GASP ingest.
OFF BY DEFAULT. When set, a peer’s failure to serve a needed ancestor during GASP ingest falls back to fetching that ancestor’s raw tx from chain (via the supplied fetcher), self-healing the graph and also enabling strict-BEEF finalize so missing ancestry fails loud.
This is a deliberate one-time-migration escape hatch. Production must NOT call this — when unset, GASP ingest behavior is unchanged.
Sourcepub fn set_gasp_ancestor_fetcher(&mut self, fetcher: Rc<dyn AncestorFetcher>)
pub fn set_gasp_ancestor_fetcher(&mut self, fetcher: Rc<dyn AncestorFetcher>)
Supply a courier reserved for sync. Completion retains its own budget.
Sourcepub fn set_landing_guard(&mut self, guard: LandingGuard)
pub fn set_landing_guard(&mut self, guard: LandingGuard)
Set the admission predicate asked BEFORE the door lands a carried
predecessor (bsv-low #575, the E1D delta-2 fold, L2;
Engine::land_carried). It is asked once per body the landing
would submit, before that body’s submit (a dry one included) and
before its reads are charged; an Err (the caller’s refusal, or its
own read fault) ends the landing with nothing of that body written
and the successor’s topic answers “not now”, as on 683dffd. The
worker installs its eviction ledger here: an OPEN eviction of the
body refuses it. Its read is not one of the submit’s 256 (it is the
caller’s store, at most one per landed body). Unset (default): every
carried predecessor may land. What the predicate cannot see (a row
opened while the landing writes) stays the caller’s to guard after
the write (MutationReport::landed_predecessors).
Sourcepub fn set_peer_sync_budget(&mut self, sleep: SleepFactory, budget_ms: u64)
pub fn set_peer_sync_budget(&mut self, sleep: SleepFactory, budget_ms: u64)
Set the per-peer GASP sync budget (bsv-low#302).
sleep is the platform sleep factory (ms → future); budget_ms is
the wall-clock slice ONE peer’s sync may consume. A peer exceeding it
is dropped (loudly logged, listed in the topic’s errors) and the
loop moves on. Unset (default) = unbounded, the pre-#302 behavior.
Progress survives the deadline (bsv-low #552). With a budget set,
every graph is submitted AS IT FINALIZES, inside the raced future (the
reference submits inside finalizeGraph too), so what was finalized
before the deadline stays admitted. At the deadline the cursor is
advanced to GASPSync::completed_cursor: past the UTXOs whose graphs
were completed, never past the one in flight. That graph is lost
whole (nothing of it was finalized) and the next tick walks it again,
down to whatever is now in storage.
The deadline never lands inside a transaction’s writes (the lens
fold’s HIGH-1). It is cooperative around a finalize submit
(Engine::finalize_submit_gate): a deadline that falls due while
one transaction is being written waits for that transaction, and the
sync is dropped at the boundary. What a dropped tick leaves of the
graph being submitted is an ancestors-first prefix of WHOLE
transactions, with the cursor below its UTXO. Everywhere else (a
request to the peer, the walk, the anchor check) the deadline drops
the sync at once, as a budget against a dead peer must. A dropped tick that finalized a
graph or moved the cursor is recorded as a SUCCESSFUL attempt for the
quarantine count: a peer serving a long bootstrap is not a dead peer.
One with no progress is a failed attempt, as before.
The budget bounds the work of a TICK. Without a per-graph budget
nothing bounds a GRAPH (parity: the reference has no node cap): one
graph whose own walk outlasts the budget is dropped on every tick
(deadline_dropped_graphs) and never admitted. With one
(Engine::set_graph_budget, bsv-low #555) that walk is deferred
with its progress kept, here too, and resumed next tick.
Sourcepub fn set_graph_budget(
&mut self,
sleep: SleepFactory,
max_calls: u32,
budget_ms: u64,
)
pub fn set_graph_budget( &mut self, sleep: SleepFactory, max_calls: u32, budget_ms: u64, )
Set the per-GRAPH GASP budget and turn DEFERRAL on (bsv-low #555).
A graph whose walk makes max_calls requests (to the peer, or chain
fetches) or runs budget_ms in one pass is DEFERRED: its partial walk
(the nodes fetched with their proofs, the inputs still pending, the
calls spent, its age in passes) is saved as ONE record of the storage
(Storage::put_deferred_graph, replaced on every deferral), its
UTXO is held below the cursor as a failed one is (the gap guard) and
not walked again in the sync (#554), and the sync goes on to the next
UTXO and topic. The next sync that is served that UTXO RESUMES the
walk from the record, asking only what is pending, under the same
budget; a graph so converges over passes and is then completed as
any graph is (#551’s anchor check, the finalize submits, e1d’s
rules). A walk cut by the per-peer deadline is deferred the same way.
Bounds: a record deferred crate::gasp::DEFERRED_GRAPH_MAX_PASSES
times is dropped with its reason and the UTXO fails as before (the
gap guard asks again, from the root); a record has no byte bound
(bsv-low #585); at most
crate::gasp::DEFERRED_GRAPHS_PER_PEER_TOPIC records per (peer,
topic). Keep budget_ms below the per-peer budget so that a deep
graph leaves the pass time for the UTXOs after it. Defaults offered:
crate::gasp::DEFAULT_GRAPH_BUDGET_CALLS,
crate::gasp::DEFAULT_GRAPH_BUDGET_MS.
Every resume of one sync shares ONE such budget (the lens fold’s M1),
so the deferred UTXOs, served first, leave the pass the rest; a walk
that fetched nothing keeps no record and is a failed attempt for the
quarantine (H1); a walk that cannot be kept, or whose save faulted,
goes on under the per-peer budget alone (L4, the delta fold’s D-L2);
a FRESH walk’s error keeps no record (D-M1). With a budget, a sync the
peer served work that finalized no graph and moved no cursor is
YIELDLESS, and past crate::gasp::PEER_YIELDLESS_SYNCS_ALLOWED in a
row AND crate::gasp::PEER_YIELDLESS_SECS_ALLOWED since the streak
began each is a failed attempt (D-M2, the delta-2 fold’s D2-M1; the
streak is kept by Storage::record_peer_sync_yield, keyed on the
peer’s crate::gasp::peer_origin as its quarantine is). A FRESH
walk that faults after half its calls or half its time keeps its
record (D2-L1); a RESUMED one that faults having appended nothing
crate::gasp::DEFERRED_GRAPH_MAX_IDLE_FAULTS passes in a row is
dropped (D2-M2). Each record says whether its peer is configured
(SyncTarget::Peers), for a storage that reserves part of its
ceiling for those (the worker reserves half).
Unset (the default) nothing is deferred and the walk is the one
before #555 (parity: the reference has no budget and no deferral).
The storage must KEEP records (the four required deferred-graph
methods of Storage): over one that refuses every save, every
graph past the budget is walked from its root under the per-peer
budget alone on every pass, as before #555, so do not set a budget
there.
The budget has two more limbs (bsv-low #586), in force with it at
their defaults: the bytes one graph may be SERVED in one pass
(crate::gasp::DEFAULT_GRAPH_BUDGET_BYTES, a budget per pass only:
a record has no byte bound since bsv-low #585 door 4) and the nodes it
may APPEND (crate::gasp::DEFAULT_GRAPH_BUDGET_NODES, 64);
Engine::set_graph_budget_limbs names others.
Sourcepub fn set_graph_budget_limbs(&mut self, max_bytes_fetched: u64, max_nodes: u32)
pub fn set_graph_budget_limbs(&mut self, max_bytes_fetched: u64, max_nodes: u32)
The bytes and nodes limbs of the per-graph budget (bsv-low #586,
zanaadu-v2 #377): a graph whose walk is SERVED max_bytes_fetched
bytes in one pass (the hex of each node’s raw transaction and proof,
as the peer sends it and a record keeps it, every answer counted), or
APPENDS max_nodes nodes, is DEFERRED exactly as at the call budget
(reason bytes or nodes): its record saved, its UTXO held below
the cursor, the walk resumed by the next pass from what is pending.
Never a drop, a refusal or a discarded graph: these are budgets PER
PASS, not limits on a graph or a BEEF. A pass always makes one
request whatever a node’s size; the resumes of one sync share one
such budget, as they share its calls (M1); a walk that cannot be
KEPT goes on under the per-peer budget alone (L4), as at the calls.
They bound what one pass adds to the pending graph and to its record
while the anchor check and the finalize hold it in memory. No effect
until Engine::set_graph_budget turns deferral on; the reference
has neither. This setter is UNCAPPED, as the worker’s
GASP_GRAPH_BUDGET_BYTES var is (bsv-low #585 door 4: the engine keeps
no record cap for the limb to sit under). A consumer whose STORAGE caps
a record must keep the bytes limb under that cap, or a pass whose
record cannot be saved walks afresh (E586-L1).
Sourcepub fn set_finalize_submit_budget(
&mut self,
sleep: SleepFactory,
budget_ms: u64,
)
pub fn set_finalize_submit_budget( &mut self, sleep: SleepFactory, budget_ms: u64, )
Bound ONE transaction’s GASP finalize submit (bsv-low #559, the delta
lens’s DELTA-3). That submit is the write section no deadline drops
(Engine::finalize_submit_gate), so a storage call or lookup hook
that never answers inside it held the per-peer budget and any outer
race for as long as it hung. With this set EACH storage call, lookup
hook and topic manager call of that submit is raced against one
sleep(budget_ms) of the section (the lens fold of 2026-10-07, F2):
a call still unanswered when it falls due is dropped alone and is
that call’s FAULT, and no call is started after it. The SUBMIT is
never dropped: it runs to its end and leaves what a fault leaves
(Engine::submit_with_report: nothing of the transaction, or all
of it), the transaction did not land, the rest of its graph is not
submitted, its UTXO fails and the cursor stays below it. The undo of
a faulted submit has one more sleep(budget_ms) of its own, so a
peer costs at most its budget plus twice this one. What the backend
does with a statement whose caller stopped waiting is its own: a
write that lands after its timeout is not seen by this submit.
DEFAULT: none (a native engine has no clock). A caller that sets a per-peer budget should set this too.
Sourcepub fn finalize_submit_gate(&self) -> &SubmitGate
pub fn finalize_submit_gate(&self) -> &SubmitGate
The gate around every GASP finalize submit (bsv-low #552, the lens
fold’s HIGH-1). start_gasp_sync races each peer against its budget
with crate::gasp::race_or_deadline_guarded over this gate. A
caller that races start_gasp_sync ITSELF against a deadline (the
worker’s scheduled step) must use the same function over the same
gate: a plain race_or_deadline there can still drop a finalize
submit between its writes.
Sourcepub fn set_script_verification(&mut self, enabled: bool)
pub fn set_script_verification(&mut self, enabled: bool)
Turn reference-parity script verification on submit on or off. DEFAULT ON (set in every constructor).
On, every submit outside HistoricalTxNoSpv executes every unproven
input’s unlocking script against its source output (and checks every
merkle path against the chain tracker), exactly as the reference’s
Engine.submit → tx.verify(this.chainTracker) does. Off is an
ESCAPE HATCH for an operator who must admit a body the interpreter
refuses (a bsv-rs interpreter defect, say) while it is fixed. It is
NOT a mode: it restores the pre-2026-09-08 structural check, under
which an invalid spend that no broadcaster had yet refused was
admitted on BEEF structure alone. Prefer
crate::builder::EngineBuilder::with_script_verification.
Sourcepub fn script_verification(&self) -> bool
pub fn script_verification(&self) -> bool
Whether submits execute input scripts, see
Engine::set_script_verification. true by default.
Sourcepub fn set_walk_budget(&mut self, budget: DoorBudget)
pub fn set_walk_budget(&mut self, budget: DoorBudget)
The budget of the submit’s own script walk (bsv-low #592, the owner’s
ruling 3a of 2026-10-10). Every submit that walks (every mode but
HistoricalTxNoSpv), and the GASP anchor check, read the BEEF through
the door’s stream walk ([crate::script_door]) under this budget’s
two limbs, the work and the memory, each estimated from the bytes
before it is spent. A breach is “the walk could not run”
(WalkCouldNotRun, on MutationReport::walk_could_not_run),
never a refusal: the submit goes on as under HistoricalTxNoSpv.
A library consumer that sets nothing gets DoorBudget::DEFAULT
(64 MiB of work, 48 MiB of memory). Prefer
crate::builder::EngineBuilder::with_walk_budget.
Sourcepub fn walk_budget(&self) -> DoorBudget
pub fn walk_budget(&self) -> DoorBudget
The budget of the submit’s own script walk, see
Engine::set_walk_budget.
Sourcepub async fn verify_scripts_only(
&self,
beef_bytes: &[u8],
subject_txid: &str,
) -> Result<WalkStats, EngineError>
pub async fn verify_scripts_only( &self, beef_bytes: &[u8], subject_txid: &str, ) -> Result<WalkStats, EngineError>
The reference’s tx.verify('scripts only') over a BEEF: every unproven
transaction from subject_txid down executes every input’s unlocking
script against its source output and obeys the value rule, while a
transaction that carries a merkle path is TRUSTED AS-IS — no root is
computed and no chain tracker is consulted (the ts-sdk’s own shape when
chainTracker === 'scripts only': a proven tx is added to the verified
set and the walk stops there).
Built for an admission path whose bar is the NETWORK, not a proof
(bsv-low’s broadcast-gated, register row D1): the overlay broadcasts
and admits on network evidence, so a lagging tracker must never read as
a refused transaction — but a spend the interpreter refuses is one the
network will refuse too, and executing it at the door saves the
broadcast and names the fault. Independent of
Engine::set_script_verification (that switch governs submit; this
is an explicit ask) and of Engine::submit’s mode (HistoricalTxNoSpv
still skips, as the reference does).
Runs under the engine’s budget (Engine::set_walk_budget, the
submit’s own; DoorBudget::DEFAULT unless set: one budget per
engine, the E592 lens fold’s NOTE-2): the work is bounded statically
before anything executes, and the interpreter’s stack memory limit is
the budget’s element limit. The body is read through the streaming
reader ([crate::script_door], bsv-low #585): no bound on its size,
its transactions or a transaction’s inputs or bytes; the budget’s two
limbs are the work and the memory beside the body.
Errors: EngineError::ScriptVerificationFailed is the INTERPRETER’s
verdict and the only refusal; EngineError::ScriptWalkInconclusive
is a structural fault (a transaction or source missing from the BEEF,
a parse fault, the value rule) and EngineError::ScriptWalkOverBudget
the door’s own bound (the static budget or the memory limit) — both
name whether the SUBJECT was judged before the fault, and a caller with
a stronger bar behind it does not refuse on either.
Sourcepub async fn verify_scripts_only_under(
&self,
beef_bytes: &[u8],
subject_txid: &str,
budget: DoorBudget,
) -> Result<WalkStats, EngineError>
pub async fn verify_scripts_only_under( &self, beef_bytes: &[u8], subject_txid: &str, budget: DoorBudget, ) -> Result<WalkStats, EngineError>
Engine::verify_scripts_only under a budget the caller names (the
pins measure the door with a limb lifted; a caller with another
platform’s ceiling sets its own).
Sourcepub async fn submit_validate_only(
&self,
tagged_beef: &TaggedBEEF,
mode: SubmitMode,
) -> Result<Steak, EngineError>
pub async fn submit_validate_only( &self, tagged_beef: &TaggedBEEF, mode: SubmitMode, ) -> Result<Steak, EngineError>
Validate a tagged BEEF without applying mutations.
Runs Phase 1 (topic validation) and Phase 2 (broadcast) but NOT Phase 3
(storage mutations). Returns the Steak (admittance decisions) that
submit() would return.
Used by the onSteakReady pattern to return results to clients before
mutations are applied via a queue consumer or ctx.wait_until().
The topic managers are called with dry_run: true
(TopicAdmittanceContext::DRY_RUN): nothing is admitted on this call, so a
manager that writes on admission leaves no trace of it.
Sourcepub async fn submit(
&self,
tagged_beef: &TaggedBEEF,
mode: SubmitMode,
) -> Result<Steak, EngineError>
pub async fn submit( &self, tagged_beef: &TaggedBEEF, mode: SubmitMode, ) -> Result<Steak, EngineError>
Submit a transaction for processing by the overlay.
Three phases:
- VALIDATE — check topics, dedup, find previous coins, call topic managers
- BROADCAST — broadcast to network (if mode is CurrentTx)
- MUTATE — mark spent, delete stale, insert new outputs, notify lookup services
Returns a STEAK mapping each topic to its admittance instructions.
Phase-3 faults are reported through Engine::submit_with_report;
this convenience keeps the historical signature and only LOGS the
report’s summary. A caller that must not ack an admission whose
writes did not land (the /submit route — S2) uses the report form.
Sourcepub async fn submit_with_report(
&self,
tagged_beef: &TaggedBEEF,
mode: SubmitMode,
) -> Result<(Steak, MutationReport), EngineError>
pub async fn submit_with_report( &self, tagged_beef: &TaggedBEEF, mode: SubmitMode, ) -> Result<(Steak, MutationReport), EngineError>
Engine::submit plus the Phase-3 durability report (S2
queue-durable admission, bsv-low 2026-08-29).
The STEAK is the admission DECISION; the report says whether the decision was DURABLY WRITTEN. They are returned together because they are answered by different things: Phase 1+2 decide, Phase 3 lands, and only the caller knows whether an undurable landing may be acked (never, on the money path — re-queue it instead).
Every write failure is still logged AND still non-fatal to the
other topics (a faulted topic does not un-admit its siblings); what
changed is that the failure is also VISIBLE, and that the faulted
topic is not recorded as applied — so a replay of the same bytes is
re-validated and re-written (every backend write is idempotent:
INSERT OR IGNORE / OR REPLACE / UPDATE).
What a fault leaves (bsv-low #559 and its lens fold of 2026-10-07):
a topic’s previous coins are deleted once EVERY admitted output of
the transaction is inserted, and only then. A fault before that
(a validation read, the spent mark of a coin the manager retains, an
insert) leaves the previous coins held and nothing of the
transaction: what did get inserted is taken out again, so the replay
is the same judgement and no successor can spend half of it. A fault
after it (a lookup notification, the consumed-by update, the applied
row) leaves the transaction’s outputs and no previous coin. A fault
before any delete was started (the record of the spent coin’s
transaction, the read of the coin) is the first case if the coins
are read back still held; once a delete was started its fault never
undoes, answered or not (it may land yet): the outputs stay beside
the stale coin, marked spent. A submit that deletes a stale coin
first records that coin’s transaction as applied, so a replay of it
after its successor is a dupe. So a non-retaining chain never has
two unspent heads and never none, whatever single call faults. The
applied row is withheld on ANY fault. And a successor that was
judged without the coin an unlanded predecessor has yet to leave is
reported as a fault (predecessor_not_landed), not recorded as
applied.
A walk that could not run within the engine’s budget is “not now”
here (WalkBreachPolicy::NotNow, EngineError::WalkCouldNotRun):
never an admission. Engine::submit_with_report_under names the
policy.
Sourcepub async fn submit_with_report_under(
&self,
tagged_beef: &TaggedBEEF,
mode: SubmitMode,
policy: WalkBreachPolicy,
) -> Result<(Steak, MutationReport), EngineError>
pub async fn submit_with_report_under( &self, tagged_beef: &TaggedBEEF, mode: SubmitMode, policy: WalkBreachPolicy, ) -> Result<(Steak, MutationReport), EngineError>
Engine::submit_with_report under an explicit
WalkBreachPolicy (the E592 lens fold, H1): what a walk that could
not run within the budget does. NetworkAccepted is for a caller that
holds the network’s accept of these very bytes (the queue’s replay of
a gated submission) and for nothing else: it admits a breached body
with its scripts unrun.
Sourcepub async fn lookup(
&self,
question: &LookupQuestion,
history_selector: Option<HistorySelector>,
) -> Result<LookupAnswer, EngineError>
pub async fn lookup( &self, question: &LookupQuestion, history_selector: Option<HistorySelector>, ) -> Result<LookupAnswer, EngineError>
Answer a lookup query.
Delegates to the appropriate LookupService, then hydrates results with BEEF.
When history_selector is provided, each output is hydrated with its
ancestor spend chain (via get_utxo_history) before building the response.
This matches the TS Engine behavior when a history selector is configured.
Sourcepub async fn lookup_with_txids(
&self,
question: &LookupQuestion,
history_selector: Option<HistorySelector>,
) -> Result<(LookupAnswer, Vec<String>), EngineError>
pub async fn lookup_with_txids( &self, question: &LookupQuestion, history_selector: Option<HistorySelector>, ) -> Result<(LookupAnswer, Vec<String>), EngineError>
Same as Engine::lookup, additionally returning each hydrated
output’s txid, aligned index-for-index with the OutputList items
(empty for pre-formed Answers).
The txid IS the storage primary key the row was just fetched by — handing it to the caller lets the aggregated wire serializer write it directly instead of re-deriving it with a full BEEF parse plus a double-SHA256 per output (bsv-low #289).
Sourcepub async fn get_utxo_history(
&self,
output: &Output,
history_selector: Option<HistorySelector>,
) -> Result<Option<Output>, EngineError>
pub async fn get_utxo_history( &self, output: &Output, history_selector: Option<HistorySelector>, ) -> Result<Option<Output>, EngineError>
Traverse and return the history of a UTXO.
If no history_selector is provided, returns the output as-is.
If a depth selector is provided, includes up to N levels of ancestor
transactions by embedding them as source_transactions in the BEEF.
pub async fn list_topic_managers(&self) -> HashMap<String, ServiceMetadata>
Sourcepub async fn list_lookup_service_providers(
&self,
) -> HashMap<String, ServiceMetadata>
pub async fn list_lookup_service_providers( &self, ) -> HashMap<String, ServiceMetadata>
List all registered lookup services with their metadata.
Sourcepub async fn get_documentation_for_topic_manager(&self, name: &str) -> String
pub async fn get_documentation_for_topic_manager(&self, name: &str) -> String
Get documentation for a specific topic manager.
Sourcepub async fn get_documentation_for_lookup_service(&self, name: &str) -> String
pub async fn get_documentation_for_lookup_service(&self, name: &str) -> String
Get documentation for a specific lookup service.
Sourcepub async fn provide_foreign_sync_response(
&self,
request: &GASPInitialRequest,
topic: &str,
) -> Result<GASPInitialResponse, EngineError>
pub async fn provide_foreign_sync_response( &self, request: &GASPInitialRequest, topic: &str, ) -> Result<GASPInitialResponse, EngineError>
Respond to a GASP initial sync request.
Returns UTXOs for the given topic since the requested score.
The caller-supplied limit is CLAMPED to
Engine::SYNC_RESPONSE_MAX_LIMIT (and defaulted when absent):
/requestSyncResponse is a PUBLIC route, and an absent limit used to
mean an unbounded scan of the outputs table (bsv-low #291). Bounding
is lossless for sync correctness because the initiator
(crate::gasp::GASPSync) pages for as long as its since score
cursor advances — a truncated page is fetched by its NEXT request
within the same run (gate finding M1: pagination must not demand a
full page, which this clamp can make impossible).
Sourcepub async fn provide_foreign_gasp_node(
&self,
graph_id: &str,
txid: &str,
output_index: u32,
) -> Result<GASPNode, EngineError>
pub async fn provide_foreign_gasp_node( &self, graph_id: &str, txid: &str, output_index: u32, ) -> Result<GASPNode, EngineError>
Provide a GASPNode for a specific transaction within a graph.
Searches the BEEF tree of the root output (identified by graphID) for the requested txid. If not found in the BEEF tree, recurses through outputsConsumed in storage.
Sourcepub async fn handle_new_merkle_proof(
&self,
txid: &str,
proof_hex: &str,
block_height: Option<u32>,
) -> Result<(), EngineError>
pub async fn handle_new_merkle_proof( &self, txid: &str, proof_hex: &str, block_height: Option<u32>, ) -> Result<(), EngineError>
Handle a new merkle proof for a transaction.
When a transaction gets mined, ARC calls back with the merkle proof. This method:
- Finds all outputs for the txid in storage
- Parses the BEEF, updates the merkle path in the transaction tree
- Serializes updated BEEF back to storage
- Recursively updates the consumedBy chain (outputs that spent this one)
- Updates blockHeight on the outputs
Sourcepub async fn complete_missing_proofs(
&self,
limit: u64,
min_age_secs: u64,
) -> Result<ProofCompletionSummary, EngineError>
pub async fn complete_missing_proofs( &self, limit: u64, min_age_secs: u64, ) -> Result<ProofCompletionSummary, EngineError>
Complete missing merkle proofs for stored, confirmed transactions.
The /submit-admitted ingest path stores a proofless BEEF for each
transaction (the submitter does not yet have a merkle proof at submit
time). When such a transaction later mines, nothing in the overlay
re-fetches its proof, so its stored BEEF stays proofless forever — and a
frontend that trims inputBEEF to {source tx + its BUMP} for a P2PKH
payment-import spend has no BUMP to trim to, hanging the import (#130).
This pass closes that gap from chain (the one blessed server-side WoC use). It:
- Pulls a bounded page (
limit) of stored(txid, beef)from storage. - Parses each BEEF and keeps only those whose target tx still lacks a
merkle proof (
Beef::find_txid(txid).has_proof() == false). - For each proofless candidate, fetches its BUMP via the configured
AncestorFetcher. If a proof comes back, it callsSelf::handle_new_merkle_proof, which stitches the BUMP into the stored BEEF (D1 + R2 mirror), updates output block height, and recurses theconsumedBychain. - Skips (does NOT error) txs the fetcher can’t prove yet (still unconfirmed) — those are simply retried on a later tick.
No-op when no [AncestorFetcher] is configured — production-safe
default (the same opt-in switch as GASP ancestor hydration). Bounded by
limit (a per-tick budget, like the platform fetcher’s own budget).
min_age_secs is the PUSH-PRIMARY BACKSTOP gate (bsv-low #228 /
arcade#259): since the Arcade MINED webhook (/arc-ingest) pushes a
verified proof ~150 ms after a tx mines, this poll pass is a BACKSTOP,
not the primary source — rows stored less than min_age_secs ago are
skipped entirely (their push is still expected). Pass 0 to disable
the gate (poll everything — the pre-#228 behaviour, and the degradation
mode if pushes stop: an old-enough row is ALWAYS polled, so webhook
loss degrades to polling, never to nothing). Unknown-age rows are
always eligible (fail-safe — see the storage-trait doc).
Sourcepub async fn complete_missing_proofs_with(
&self,
fetcher: &dyn AncestorFetcher,
limit: u64,
min_age_secs: u64,
) -> Result<ProofCompletionSummary, EngineError>
pub async fn complete_missing_proofs_with( &self, fetcher: &dyn AncestorFetcher, limit: u64, min_age_secs: u64, ) -> Result<ProofCompletionSummary, EngineError>
Complete stored transactions using the caller’s dedicated allowance. The engine’s other passes retain their configured ancestor courier.
Sourcepub fn stitch_proof_into_stored_beef(
stored: &[u8],
txid: &str,
proof: &MerklePath,
) -> Option<Vec<u8>>
pub fn stitch_proof_into_stored_beef( stored: &[u8], txid: &str, proof: &MerklePath, ) -> Option<Vec<u8>>
Recursively update merkle paths in a transaction’s input tree.
If the transaction’s id matches txid, set its merkle_path.
Otherwise, recurse into sourceTransactions of each input.
Stitch a verified BUMP for txid into a STORED BEEF, preserving every
transaction and every other proof in it (#284).
The old path round-tripped the stored bytes through
Transaction::from_beef(bytes, None) -> to_beef(true), and
from_beef(_, None) picks txs.last() — it ignores the atomic
subject pointer entirely. Whenever the subject was not the last tx in
wire order, a PARENT was picked, to_beef(true) then serialized that
parent’s world, and the rewrite dropped the subject transaction from its
own stored BEEF — observed live on every pf head-spend (list / reprice /
delist / buy) the moment it mined, breaking the NEXT spend on that name
(zanaadu#284). update_input_proofs’ “already has a proof — update it”
arm could even stamp the child’s bump onto that mis-picked parent.
This is the same destroy-the-stored-BEEF class the #4 fix note above
(hydrate_utxo_history) already documents for lookup hydration.
Operating on Beef directly has none of those failure modes:
merge_bump dedupes against existing bumps by (height, root) and
assigns the bump ONLY to transactions that appear as flagged txid
leaves, so stitching tx X’s proof can never mislabel tx Y, and the tx
set is never rebuilt at all.
Returns None when the bytes do not parse or txid is not in this
BEEF (nothing to stitch — e.g. a consuming row whose ancestry got
trimmed), so callers skip the write instead of writing garbage.
bsv-low M19 R2 round 3 (review HIGH-1): a same-height DIFFERENT-root
proof (the 2026-09-07 reorg shape) does NOT combine in merge_bump
(the roots differ) — it is pushed as a SECOND bump, and
update_bump_indices assigns it only to txs whose bump_index is
None, so the reorged subject keeps its index on the STALE bump.
Left alone, has_proof/proofHeight and every find_txid(txid) .bump_index() reader then keep pointing at the orphan bump, and the
row refutes again next sweep pass forever. So after the merge, FORCE
the subject onto the bump that actually proves it, then drop any bump
no transaction references (the orphan’s, now unreferenced).
Sourcepub async fn sync_advertisements(
&self,
) -> Result<SyncAdvertisementsReport, EngineError>
pub async fn sync_advertisements( &self, ) -> Result<SyncAdvertisementsReport, EngineError>
Sync SHIP/SLAP advertisements with configured managers and services.
Creates missing advertisements and revokes stale ones. Returns a
SyncAdvertisementsReport so a create/submit failure is VISIBLE to
the caller (bsv-low #320 defect 3a): this method used to swallow both
into error! and return Ok(()), so /admin/syncAdvertisements
reported success while zero advertisements were admitted locally —
a caller could not tell a converged no-op from a silent failure, and
every cycle re-created the full ad set.
Sourcepub async fn renotify_admitted(
&self,
txid: &str,
topic: &str,
) -> Result<RenotifyReport, EngineError>
pub async fn renotify_admitted( &self, txid: &str, topic: &str, ) -> Result<RenotifyReport, EngineError>
Evict a specific outpoint from the overlay.
- Deletes the output from storage for the given topic.
- Notifies all lookup services via
output_evicted().
If no topic is specified, finds all outputs for the txid and evicts the matching outputIndex across every topic.
Matches TS OverlayExpress /admin/evictOutpoint (lines 974-1004).
bsv-low PLAN-PRE-LOOP4 §H4 (2026-09-06): RE-NOTIFY every lookup service
of the outputs the engine ALREADY admitted for (txid, topic).
Phase-1 dedup means a (txid, topic) once recorded in
applied_transactions never re-enters through /submit, so a lookup
service that missed — or mis-parsed — its admit notification could
never be told again (the loop-2 subject trap: the pot index’s admit
hook shape-checked a wire-last hop and wrote no record while the engine
had admitted the pot). This replays output_admitted_by_topic from the
engine’s OWN stored outputs (with their BEEF) and the subject-NAMED body
exactly as submit_with_report sends it. It validates nothing anew
(the topic manager judged these outputs at admit), writes none of the
engine’s tables, and is idempotent for every lookup service whose
writes are (the pot index’s store_record is insert-if-absent for the
spend fields — re-admission never regresses spend state).
outputs == 0 means the engine never admitted this txid on this topic:
nothing to re-notify — the caller re-SUBMITS instead.
Sourcepub async fn forget_phantom_applied(
&self,
txid: &str,
topic: &str,
) -> Result<bool, EngineError>
pub async fn forget_phantom_applied( &self, txid: &str, topic: &str, ) -> Result<bool, EngineError>
bsv-low PLAN-PRE-LOOP4 §H4 (2026-09-06): FORGET a PHANTOM applied row —
(txid, topic) recorded in applied_transactions while the topic holds
NO output of that txid (the engine judged the tx and admitted nothing:
the loop-2 JOIN f9e85aab… under the pre-D5 subject rule). Such a row
does nothing but block a correct re-submit through the Phase-1 dedup
(/admin/readmit on beta answered deduped: ["tm_pot_beta"] and the
pot stayed known:false). A row with ANY stored output on the topic is
a real admission and is never touched (returns false, as does a missing
row). The forgotten bytes are then re-VALIDATED by the topic manager
like any first submit — nothing is admitted here.
pub async fn evict_output( &self, txid: &str, output_index: u32, topic: Option<&str>, ) -> Result<(), EngineError>
Sourcepub async fn start_gasp_sync(&self) -> Result<GASPSyncResult, EngineError>
pub async fn start_gasp_sync(&self) -> Result<GASPSyncResult, EngineError>
Start GASP synchronization with peers for all configured topics.
For each topic in sync_configuration:
SyncTarget::Ship: unions local SHIP records, network discovery via configured SLAP trackers, and the topic’s bootstrap peers.SyncTarget::Peers(urls)— uses the provided peer URLs directly.SyncTarget::Disabled— skips the topic.
Filters out our own hosting_url to avoid self-sync.
Returns a GASPSyncResult summarizing discovered peers per topic.
Discovers peers for each configured topic and runs GASP sync with each.
When a GASPRemoteFactory is set (via set_gasp_remote_factory), this
method creates OverlayGASPStorage + GASPRemote instances per
(topic, peer) pair and runs GASPSync::sync() to exchange UTXOs.
Without a factory, peer discovery still runs but no actual sync occurs (backwards-compatible with the previous discovery-only behavior).
Sourcepub async fn rerequest_held_proofs(
&self,
candidates: &[HeldOutput],
progress: &Cell<PeerProofSummary>,
)
pub async fn rerequest_held_proofs( &self, candidates: &[HeldOutput], progress: &Cell<PeerProofSummary>, )
Union local records, tracker answers, SLAP-named providers and bootstrap. Reference: overlay-services@480d77a src/Engine.ts:839-876 and ts-stack@edf6e03 packages/sdk/src/overlay-tools/LookupResolver.ts:1976. Ask the configured topic peers for held outputs, using the same SHIP discovery as sync. Only a header-verified proof can change stored bytes.
Sourcepub fn config(&self) -> &EngineConfig
pub fn config(&self) -> &EngineConfig
Get the engine configuration.