Next
Continue to Verification to see how a final proof is checked independently and exported as a portable bundle.
This page covers how anchoring is governed: which mode a point is anchored in, how that policy is resolved, how a proof moves to finality, and what happens when the chain reorganizes, or the backlog grows.
CORE-M supports three anchoring modes. The mode determines how a point’s hash reaches the chain.
Merkle batch (default). Point hashes accumulate into a batch, a binary SHA-256 Merkle tree is built, and only the 32-byte root is anchored in one transaction. Most cost-efficient — one transaction commits up to a full batch of points. Verification needs the per-point Merkle path. Latency is bounded by the flush window. See Hashing & Merkle Trees.
Per-event. Each point is anchored in its own transaction, with the point’s
data_hash carried directly in the OP_RETURN and data_point_count = 1. Highest
assurance and lowest latency, but it uses one transaction per point — the most
expensive mode. No Merkle path is needed; the OP_RETURN is checked directly.
Hash chain. Anchoring transactions are linked so that each new transaction spends a continuation output of the previous one, producing an ordered on-chain sequence. Useful for proving the chronological ordering of events. The first transaction starts the chain; each subsequent one is funded from the previous transaction’s continuation output.
Anchoring policy is enforced at three levels. The effective policy is resolved most-specific-first — the first level that defines a field wins:
device override -> device profile -> tenant default -> platform default (most specific) (least specific)So a device override beats its device profile, which beats the tenant default,
which beats the built-in platform default. For example, if a tenant defaults to
merkle_batch but the critical-sensors device profile sets per_event with
required=true, a device using that profile is anchored per-event. Device
profiles are described in Device Profiles.
| Field | Meaning |
|---|---|
mode | merkle_batch, per_event, or hash_chain. |
required | Whether anchoring is mandatory for the point to be accepted. |
max_pending_age_seconds | Maximum age a point may sit pending before escalation. |
finality_confirmations | Confirmations required before a proof is final (default 6). |
backlog_action | accept_pending, throttle, or reject when the backlog is high. |
max_backlog_points | Backlog threshold that triggers the backlog action. |
Every accepted point has an explicit lifecycle record (in Aerospike, namespace
telemetry, set proof_state, keyed by {tenant_id}:{device_id}:{data_hash_hex},
updated with generation-checked CAS). It moves through these states:
stateDiagram-v2 [*] --> accepted: hash + lifecycle record written accepted --> validated: written to telemetry stores validated --> pending_batch: assigned to a batch pending_batch --> anchored: anchor tx broadcast (txid known) anchored --> confirmed: block inclusion observed confirmed --> final: confirmation depth >= threshold anchored --> failed_retryable: temporary failure confirmed --> failed_retryable: reorg invalidates tx failed_retryable --> pending_batch: retry scheduled failed_retryable --> failed_terminal: permanent failure final --> [*] failed_terminal --> [*]
| State | Meaning |
|---|---|
accepted | Passed auth and parsing; lifecycle record exists. The point is not acknowledged to the caller until this write succeeds. |
validated | Device/profile validation passed; written to telemetry stores. |
pending_batch | Hash assigned to an anchoring batch. |
anchored | Anchoring transaction broadcast; txid known. |
confirmed | ARC or the SPV watcher observed block inclusion. |
final | Confirmation depth reached the finality threshold. |
failed_retryable | Temporary failure; a retry is scheduled. |
failed_terminal | Permanent failure requiring operator action. |
A confirmed transaction is not immediately treated as irreversible. The finality
watcher tracks confirmations for every anchored txid (finality records live in
Aerospike, namespace anchors, set finality, keyed by {txid}). When the
confirmation depth reaches the policy’s finality_confirmations (default 6),
the finality record is marked final and every proof-lifecycle record for that
txid moves confirmed → final, with a anchor.lifecycle.final.<tenant> event
published.
Finality survives restarts: on startup the watcher reloads finality state and will not downgrade already-final records or re-emit finality events.
If a chain reorganization removes the block that contained an anchored
transaction, the proof must not silently claim finality. The watcher detects, via
the SPV/ARC watcher, that the block holding a txid is no longer on the best
chain, and then:
txid to reorged.failed_retryable.txid, block_height, and block_hash in a
reorg_history field.corem_anchor_reorg_events_total.When pending work piles up, the policy’s backlog_action decides what happens to
new points once max_backlog_points is exceeded:
| Action | Behavior |
|---|---|
accept_pending | Keep accepting points; they queue as pending. |
throttle | Reject above the allowed throttled rate with retry-after metadata; no lifecycle record is created for rejected points. An audit event anchoring.backlog.throttled is written at most once per configured interval. |
reject | Reject new points with RESOURCE_EXHAUSTED / HTTP 429 and reason="anchoring_backlog_exceeded". |
Both throttle and reject increment their respective metrics
(corem_anchor_backlog_rejections_total{tenant} for rejections).
Anchoring spend is attributed back to tenants for cost accounting. Usage is
aggregated in Aerospike (namespace anchors, set tenant_usage, keyed by
{tenant_id}:{yyyymmdd}). When an anchoring transaction is broadcast, the usage
records for every tenant represented in that batch are updated atomically — a batch
of 900 points for tenant T1 and 100 for T2 updates both. Each record carries
point_count, batch_count, token_count, estimated_fee_sats, and the list of
txids, and the update is idempotent on retry using the batch_id as the
idempotency key.
Next
Continue to Verification to see how a final proof is checked independently and exported as a portable bundle.