Documentation
¶
Overview ¶
Package sequences is the kit's durable sequence manager (FR-034, data model §6). Manager.Reserve is the ONLY sequence source for transaction builders: it serializes per (chain_id, source_address) through the storage backend's account-lock primitive (Postgres SELECT … FOR UPDATE on the chain_account_locks row; SQLite single-writer + BEGIN IMMEDIATE), derives the next sequence from chain truth reconciled with open reservations, and binds exactly one reservation to one work item (withdrawal or sweep).
Reconciliation is state-dependent and never blind (§6 reconciliation rule): only RESERVED reservations — nothing signed — are ever released; a SIGNED/BROADCAST/ambiguous reservation whose transaction cannot be found is quarantined as RECONCILIATION_REQUIRED, because valid signed bytes may still redeem the sequence from a mempool, another process, or the signer system. Recovery rebroadcasts the exact persisted signed bytes (RebroadcastPersisted) — never re-signs, never auto-releases.
Index ¶
- Variables
- type Action
- type Chain
- type Manager
- func (m *Manager) RebroadcastPersisted(ctx context.Context, signedTxBytes []byte) (RebroadcastResult, error)
- func (m *Manager) ReconcileAccount(ctx context.Context, chainID, source string) (Report, error)
- func (m *Manager) Reserve(ctx context.Context, chainID, source string, ref storage.WorkRef) (storage.SequenceReservation, error)
- type Option
- type RebroadcastResult
- type Report
Constants ¶
This section is empty.
Variables ¶
var ( // ErrReleasedSlotUnusable reports that a work item's reservation was // RELEASED during reconciliation and its sequence slot has since been // consumed on chain, so the same work_ref cannot be re-bound. The work // item must be quarantined for operator review — never silently rebound // to a different sequence. ErrReleasedSlotUnusable = errors.New("sequences: released reservation slot is no longer reservable") // ErrNoAccountLock reports a storage backend that does not expose the // per-account serialization primitive (R7). ErrNoAccountLock = errors.New("sequences: storage backend does not implement AcquireAccountLock") )
Functions ¶
This section is empty.
Types ¶
type Action ¶
type Action struct {
ReservationID int64
WorkRef storage.WorkRef
Sequence uint64
From storage.SequenceReservationStatus
To storage.SequenceReservationStatus
Reason string
}
Action records one reconciliation decision for the report/audit trail.
type Chain ¶
type Chain interface {
Account(ctx context.Context, addr string) (accountNumber, sequence uint64, err error)
Tx(ctx context.Context, hash string) (*client.TxInfo, error)
Broadcast(ctx context.Context, txBytes []byte, mode client.BroadcastMode) (*client.BroadcastResult, error)
}
Chain is the subset of client.Client the manager needs. *client.Client implementations satisfy it.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager hands out durable sequence reservations. Safe for concurrent use; concurrency is serialized per account by the storage backend, and the UNIQUE(chain_id, source_address, sequence) constraint is the last-line guarantee in all cases.
func NewManager ¶
NewManager builds a Manager over one storage backend and one chain client.
func (*Manager) RebroadcastPersisted ¶
func (m *Manager) RebroadcastPersisted(ctx context.Context, signedTxBytes []byte) (RebroadcastResult, error)
RebroadcastPersisted is the ONLY recovery path for a quarantined signed transaction: it searches for the exact persisted bytes by hash first and, only when absent, rebroadcasts those identical bytes. It takes bytes, not a work item — re-signing is structurally impossible here.
func (*Manager) ReconcileAccount ¶
ReconcileAccount re-derives every unconsumed reservation for one account from chain truth. Run it at startup and after any detected mismatch.
Resolution is state-dependent (§6): a transaction found on chain by hash consumes its reservation; a RESERVED reservation with no signed bytes is released; SIGNED, BROADCAST, and every ambiguous case (signed bytes exist, work item unreadable, tx not found) is quarantined as RECONCILIATION_REQUIRED. A GetTx not-found is never sufficient to release a sequence that signed bytes may still redeem.
func (*Manager) Reserve ¶
func (m *Manager) Reserve(ctx context.Context, chainID, source string, ref storage.WorkRef) (storage.SequenceReservation, error)
Reserve returns the reservation bound to ref, creating it if absent.
Idempotent per work_ref: a second call for the same work item returns the existing reservation in whatever status it has reached (the caller inspects Status). A reservation RELEASED by reconciliation is re-bound to the same slot when that slot is still ahead of chain truth; a burned slot returns ErrReleasedSlotUnusable so the caller quarantines the work item.
The next sequence is chain truth reconciled with open reservations: max(account.sequence, highest unconsumed reservation + 1). An open reservation strictly below account.sequence is a mismatch — counted, logged, and left for ReconcileAccount; Reserve never auto-resolves it.
type Option ¶
type Option func(*Manager)
Option configures a Manager.
func WithMetrics ¶
WithMetrics attaches the adapter metric set (sequence-mismatch counter).
type RebroadcastResult ¶
type RebroadcastResult struct {
TxHash string
// AlreadyIncluded is true when the transaction was found on chain and
// nothing was broadcast.
AlreadyIncluded bool
Height int64
Code uint32
RawLog string
// Accepted is true when a broadcast was performed and CheckTx passed.
Accepted bool
}
RebroadcastResult reports what RebroadcastPersisted found or did.