pub struct PersistentPoolStateStore { /* private fields */ }Expand description
redb-backed persistent implementation of PoolStateStore.
Construct via PersistentPoolStateStore::open. One redb::Database per
write family is owned for the lifetime of this value; drop closes every
handle. The lane table is buffered in memory: record/forget mutate the
working set only, and a caller must call PoolStateStore::flush to commit
it to disk. The store is thread-safe — each redb file serialises its own
writes via single-writer transactions, families commit on independent writer
slots, and reads are MVCC.
The working set is a DashMap rather than one mutex-guarded map, so a
record on the paid-delivery path locks only its own lane’s shard — no lane
serialises on another, and settlement’s load_all/get do not contend
with record (issue #1792 item 1). Each entry’s shard lock is the per-lane
critical section that keeps a record/forget race decidable.
Implementations§
Source§impl PersistentPoolStateStore
impl PersistentPoolStateStore
Sourcepub fn open(data_dir: &Path, deployment: Deployment) -> Result<Self, StoreError>
pub fn open(data_dir: &Path, deployment: Deployment) -> Result<Self, StoreError>
Open (or create) the lane-state store under data_dir.
data_dir is validated through
decdn_common::identity::ensure_data_dir before any redb operation,
which enforces 0o700 on the directory and rejects insecure modes.
The store file is then chmod’d to 0o600 after creation as
defense-in-depth.
§Errors
StoreError::Backendwhen thedata_dirsecurity check fails.StoreError::Backendwhenredb::Database::createrefuses the file.StoreError::Corruptwhen the file exists but is zero-length —redbwould otherwise treat that as “create a fresh database” and silently reopen the issue #527 replay window.StoreError::PermissionTightenwhen the post-create chmod fails.StoreError::Iofor any other filesystem error while stat-ing.
When this returns Err, the caller MUST abort node bring-up —
starting with a clean store silently forfeits the issue #527 guarantee.
deployment is the PaymentPool the node is configured against. The
store binds to it before it hydrates any lane: a store stamped with
another deployment drops its lane rows, pending settles and watcher
checkpoints, and a store with no stamp takes this one and keeps its rows.
Trait Implementations§
Source§impl Debug for PersistentPoolStateStore
impl Debug for PersistentPoolStateStore
Source§impl KeyedCheckpointStore for PersistentPoolStateStore
impl KeyedCheckpointStore for PersistentPoolStateStore
Source§fn load_checkpoint(&self, key: CheckpointKey) -> Result<Option<u64>, StoreError>
fn load_checkpoint(&self, key: CheckpointKey) -> Result<Option<u64>, StoreError>
key, or None on a never-written key
(first-ever boot for that watcher — the caller applies its configured
none-fallback floor). Read moreSource§fn record_checkpoint(
&self,
key: CheckpointKey,
block: u64,
) -> Result<(), StoreError>
fn record_checkpoint( &self, key: CheckpointKey, block: u64, ) -> Result<(), StoreError>
key. Overwrites the prior value.
Called as each backfill window and live tick advances, so the next boot
resumes from here. Read moreSource§fn flush_checkpoint(&self, key: CheckpointKey) -> Result<(), StoreError>
fn flush_checkpoint(&self, key: CheckpointKey) -> Result<(), StoreError>
key to durable storage. The default
is a no-op: implementations that already commit durably inside
record_checkpoint have nothing buffered. A
debouncing decorator overrides this to fsync the latest deferred block,
and the runtime calls it on graceful shutdown so the most recent scan
progress is not lost to the next boot’s rescan. Read moreSource§impl PendingSettleStore for PersistentPoolStateStore
impl PendingSettleStore for PersistentPoolStateStore
Source§fn record_pending(&self, entry: &PendingSettle) -> Result<(), StoreError>
fn record_pending(&self, entry: &PendingSettle) -> Result<(), StoreError>
Source§fn load_pending(&self) -> Result<Vec<PendingSettle>, StoreError>
fn load_pending(&self) -> Result<Vec<PendingSettle>, StoreError>
Source§fn forget_pending(&self, pool_id: B256) -> Result<(), StoreError>
fn forget_pending(&self, pool_id: B256) -> Result<(), StoreError>
Source§impl PoolStateStore for PersistentPoolStateStore
impl PoolStateStore for PersistentPoolStateStore
Source§fn load_all(&self) -> Result<Vec<LaneState>, StoreError>
fn load_all(&self) -> Result<Vec<LaneState>, StoreError>
Every persisted lane, from the in-memory working set hydrated at open().
Tombstoned slots (a forget-ten lane not yet flushed) read as absent.
Source§fn record(&self, state: &LaneState) -> Result<(), StoreError>
fn record(&self, state: &LaneState) -> Result<(), StoreError>
Advance the in-memory lane state and mark it dirty. Durability is the
background flush’s job (ADR 003 §Off-chain voucher state persistence).
Locks only this lane’s DashMap shard; concurrent records on other
lanes proceed in parallel.
Source§fn set_registered_until(
&self,
key: LaneKey,
registered_until: u64,
) -> Result<(), StoreError>
fn set_registered_until( &self, key: LaneKey, registered_until: u64, ) -> Result<(), StoreError>
Raise this lane’s observed on-chain registration expiry, monotonically —
touches ONLY registered_until, never the replay-critical last_*
tuple, so it cannot race a concurrent voucher record into a lost
update. A no-op for a lane with no live record. Buffered like record;
durability is the next flush’s job.
Source§fn set_paid_cumulative(
&self,
key: LaneKey,
paid_cumulative: U256,
) -> Result<(), StoreError>
fn set_paid_cumulative( &self, key: LaneKey, paid_cumulative: U256, ) -> Result<(), StoreError>
Raise this lane’s paid cumulative, monotonically — touches ONLY
paid_cumulative, never the replay-critical last_* tuple, so a
PoolRedeemed write cannot race a concurrent voucher record into a lost
update. A no-op for a lane with no live record (a redemption observed after
the lane was forgotten has nothing to advance). Buffered like record;
durability is the next flush’s job (#2052).
Source§fn forget(&self, key: LaneKey) -> Result<(), StoreError>
fn forget(&self, key: LaneKey) -> Result<(), StoreError>
Tombstone the lane so reads treat it as absent and the next flush deletes
its on-disk row. Idempotent. The slot stays in the map (as a tombstone)
so a concurrent record resurrecting the lane is decided under the same
shard lock rather than racing a separate set. The lane’s owner-signed
capability material (owner_sig) is part of the record, so it goes with
the lane in the same delete.
Source§fn flush(&self) -> Result<(), StoreError>
fn flush(&self) -> Result<(), StoreError>
Write every dirty lane and apply every tombstone in ONE fsynced redb transaction. Each lane record carries its own owner-signed capability material, so nothing is written separately. Idempotent — a no-op when clean.
Double-buffered: the dirty work-list is drained and each lane’s slot is
cloned out (a cheap memcpy — LaneState holds no heap fields), then
encoded, all off the fsync’s critical path. The ~1–10 ms fsynced commit
holds no lane shard lock, so a concurrent record (a single-shard map
insert) never waits on disk I/O.
A record that lands after its key is drained pushes the key afresh and
is captured by the next flush; the store only needs a monotonic floor,
and the frontier is documented “safe to lose” on a crash (ADR 003
§Off-chain voucher state persistence), so a slightly older value now and a
newer one one cadence later is correct.
Source§fn get(&self, key: LaneKey) -> Result<Option<LaneState>, StoreError>
fn get(&self, key: LaneKey) -> Result<Option<LaneState>, StoreError>
None if no record
exists. Used by the on-chain seller redemption path to read the latest
accepted voucher (last_amount / last_bytes_delivered /
last_signature) when deciding whether an accrued claim crosses the
redemption threshold — always the freshest committed voucher. Read moreAuto Trait Implementations§
impl !Freeze for PersistentPoolStateStore
impl !RefUnwindSafe for PersistentPoolStateStore
impl !UnwindSafe for PersistentPoolStateStore
impl Send for PersistentPoolStateStore
impl Sync for PersistentPoolStateStore
impl Unpin for PersistentPoolStateStore
impl UnsafeUnpin for PersistentPoolStateStore
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request