Documentation
¶
Overview ¶
Package deposits is the kit's deposit-scanning core (contracts/ go-client-api.md §deposits, R6): tolerant block parsing and watch-set classification into the ChainTransferLedger (parse.go), FR-023 credit-condition evaluation and the transactional block/credit write paths (state.go), and the checkpointed ascending scanner engine (scanner.go).
Index ¶
- Constants
- Variables
- func ConfirmationCount(latestHeight, blockHeight uint64) uint64
- func CreditDeposit(ctx context.Context, store storage.Store, d storage.DepositRecord, ...) error
- func DepositDedupKey(d storage.DepositRecord) string
- func TxHashHex(txBytes []byte) string
- type BlockEventEntry
- type BlockParse
- type CreditDecision
- type CreditGate
- type FeeDeduction
- type RecordOutcome
- type RecordPolicy
- type ReviewCandidate
- type Scanner
- type ScannerConfig
- type TransferCandidate
- type WatchSet
Constants ¶
const ( TypeURLMsgSend = "/cosmos.bank.v1beta1.MsgSend" TypeURLMsgMultiSend = "/cosmos.bank.v1beta1.MsgMultiSend" )
Only these two message shapes are ever unpacked (R6): the SDK TxConfig decoder's strict unknown-type rejection fails on this chain's custom-module traffic, so TxRaw → TxBody + AuthInfo are decoded with gogoproto and every other Any is kept packed.
const DefaultConfirmations = 2
DefaultConfirmations is the published recommended launch value (FR-028): CometBFT is single-block-final (1 committed block = protocol finality), so the recommended launch default of 2 is a node-operations sanity buffer for node comparison, monitoring, and incident detection — not a reorg defence. The supported range is 1..12 and the final threshold is an exchange risk decision.
const DefaultPollInterval = 2 * time.Second
DefaultPollInterval is the block poll cadence. The kit's client transports are unary-only, so a short poll interval is the accelerator-equivalent of the optional WebSocket NewBlock subscription (documented deviation — FR-027 polling remains the primary mechanism either way).
const OutboxTopicDepositCredited = "deposit.credited"
OutboxTopicDepositCredited is the transactional-outbox topic emitted exactly once per credited deposit (data model §3b invariant).
const ReviewOpIndexBase uint32 = 1 << 16
ReviewOpIndexBase offsets event-derived UNATTRIBUTED_REVIEW ledger rows above every body-decoded op index (bounded by 2 × the message's coin count), so tx-scoped review entries never collide with decoded transfers under UNIQUE(chain_id, tx_hash, message_index, op_index).
Variables ¶
var ErrCreditGateClosed = errors.New("deposits: credit gate closed inside credit transaction")
ErrCreditGateClosed aborts an in-flight credit: the gate re-check inside the crediting transaction found crediting paused, scan-without-credit engaged, or a chain-review condition open. The CREDITABLE→CREDITED flip and outbox enqueue never happen, so the record stays CREDITABLE for a later pass. It is a benign TOCTOU guard (a batch's gate load precedes each credit tx), never a failure — callers must stop the batch on it, not surface it as an error.
Functions ¶
func ConfirmationCount ¶
ConfirmationCount is latest − block_height + 1 — recomputed, never stored authority (data model §3b).
func CreditDeposit ¶
func CreditDeposit(ctx context.Context, store storage.Store, d storage.DepositRecord, now time.Time) error
CreditDeposit flips CREDITABLE → CREDITED and enqueues the credited event in one transaction (transactional outbox — the §3b exactly-once invariant). Callers must have evaluated DecisionCredit first. The credit gate is re-validated transactionally with the flip: a pause / chain-review opened after the caller's gate load must not let a batch keep crediting (ErrCreditGateClosed aborts, leaving the record CREDITABLE).
func DepositDedupKey ¶
func DepositDedupKey(d storage.DepositRecord) string
DepositDedupKey is the outbox dedup key making the credited event exactly-once per FR-024 unique deposit key.
Types ¶
type BlockEventEntry ¶
type BlockEventEntry struct {
// EventIndex is 2 × the finalize_block_events index (+1 for OUT rows)
// so one event touching two watched addresses yields distinct rows
// under UNIQUE(chain_id, block_height, event_index).
EventIndex uint32
Direction storage.LedgerDirection
Address string
CounterpartySet []string
AmountBaseUnits sdkmath.Int
Reason string
}
BlockEventEntry is a block-scoped unattributed-activity record derived from finalize_block_events, which carry no transaction association — never a tx-level candidate (R6 event scoping).
func (BlockEventEntry) LedgerEntry ¶
func (b BlockEventEntry) LedgerEntry(chainID string, blockHeight uint64, now time.Time) storage.LedgerEntry
LedgerEntry converts the record to its BLOCK_EVENT ledger row.
type BlockParse ¶
type BlockParse struct {
ChainID string
Height uint64
BlockHash string
LastBlockHash string
Time time.Time
Transfers []TransferCandidate
FeeDeductions []FeeDeduction
ReviewCandidates []ReviewCandidate
BlockEvents []BlockEventEntry
}
BlockParse is the full parse output for one block.
func ParseBlockTransfers ¶
func ParseBlockTransfers(block *client.Block, results *client.BlockResults, watch WatchSet) (*BlockParse, error)
ParseBlockTransfers performs the tolerant raw decode and watch-set classification for one block (contracts/go-client-api.md §deposits). Individual undecodable txs are never an error — they route through the event secondary-detection channel instead.
type CreditDecision ¶
type CreditDecision string
CreditDecision is the outcome of evaluating the FR-023 condition list.
const ( // DecisionCredit — every FR-023 condition holds; credit now. DecisionCredit CreditDecision = "CREDIT" // DecisionHold — a condition does not hold yet (or a pause/chain-review // gate is closed); re-evaluate later, no state change. DecisionHold CreditDecision = "HOLD" // DecisionSuspend — scan-without-credit is engaged; park as SUSPENDED. DecisionSuspend CreditDecision = "SUSPEND" // DecisionNever — a permanent condition fails (failed tx, wrong denom, // non-positive amount, already credited); the deposit must not credit. DecisionNever CreditDecision = "NEVER" )
func EvaluateCreditConditions ¶
func EvaluateCreditConditions(d storage.DepositRecord, latestHeight, confirmations uint64, gate CreditGate) (CreditDecision, string)
EvaluateCreditConditions applies the full FR-023 list to one deposit. Structural conditions (committed block, recognized transfer shape, exchange-controlled recipient, external classification) are established at derivation time — a DepositRecord only exists when they held; this function re-checks the record-level and runtime conditions.
type CreditGate ¶
type CreditGate struct {
CreditPaused bool // FR-051 deposit-crediting pause
ScanWithoutCredit bool // FR-051 scan-without-credit — parks as SUSPENDED
ChainReviewOpen bool // FR-044 open condition closes the crediting gate
}
CreditGate carries the runtime suspension state consulted on every credit decision (FR-023 tail conditions).
func LoadCreditGate ¶
LoadCreditGate reads the gate from controls + open chain-review conditions.
type FeeDeduction ¶
type FeeDeduction struct {
TxHash string
PayerAddress string
FeeBaseUnits sdkmath.Int
TxCode uint32
FeeGranterUsed bool
GranterAddress string
}
FeeDeduction is one FEE_DEDUCTION capture (data model §8a). Recorded iff the fee-deduction ante event was present and the resolved payer is watched.
type RecordOutcome ¶
type RecordOutcome struct {
LedgerAppends int
FeeOutflows int
DepositsInserted int
DepositsRevived int
Duplicates int
ReviewItemsOpened int
}
RecordOutcome reports what one block's transactional write did.
func RecordBlock ¶
func RecordBlock(ctx context.Context, store storage.Store, bp *BlockParse, pol RecordPolicy, now time.Time) (RecordOutcome, error)
RecordBlock persists one parsed block atomically: ledger appends, fee outflows, deposit derivation (exclusively from EXTERNAL_DEPOSIT entries), review-queue items, and the checkpoint advance — all in one Store.WithTx transaction (FR-026). Replays are idempotent: every unique-key hit is tolerated and counted, never re-credited (FR-024).
type RecordPolicy ¶
type RecordPolicy struct {
ChainID string
// MinimumDepositUsovr parks smaller validated deposits as BELOW_MINIMUM;
// nil or zero disables parking (threshold is exchange configuration,
// never a code constant — FR-040).
MinimumDepositUsovr sdkmath.Int
}
RecordPolicy configures the block write path.
type ReviewCandidate ¶
type ReviewCandidate struct {
TxHash string
MessageIndex uint32
EventIndex uint32
// OpIndex is ReviewOpIndexBase + 2×EventIndex (+1 for OUT rows).
OpIndex uint32
Direction storage.LedgerDirection
Address string
CounterpartySet []string
AmountBaseUnits sdkmath.Int
TxCode uint32
Reason string
}
ReviewCandidate is a tx-correlated secondary-detection hit (FR-030): a watched address appears in txs_results[i].events of a tx the body decode could not fully attribute (authz-wrapped sends, wasm transfers, module payouts). Never a crediting source.
func (ReviewCandidate) LedgerEntry ¶
func (c ReviewCandidate) LedgerEntry(chainID string, blockHeight uint64, now time.Time) storage.LedgerEntry
LedgerEntry converts the review candidate to its tx-scoped UNATTRIBUTED_REVIEW ledger row.
type Scanner ¶
type Scanner struct {
// contains filtered or unexported fields
}
Scanner is the checkpointed ascending block walker (FR-026): it advances the durable checkpoint only after a block is fully persisted (same transaction), verifies the block-hash chain (R6), and evaluates the FR-023 crediting conditions for pending deposits each cycle.
func NewScanner ¶
NewScanner builds a Scanner; cfg zero-values take documented defaults.
func (*Scanner) Cycle ¶
Cycle performs one scan pass: walk new blocks, then evaluate pending deposits. Exported for tests and drills; Run calls it on every tick.
func (*Scanner) RescanFrom ¶
RescanFrom records an explicit rescan base in the operational controls (admin POST /v1/scanner/resume-from writes the same field); the scanner picks it up on its next cycle. Replay is idempotent — unique keys make re-processing safe (FR-024).
type ScannerConfig ¶
type ScannerConfig struct {
ChainID string
// Confirmations defaults to DefaultConfirmations when zero.
Confirmations uint64
// PollInterval defaults to DefaultPollInterval when zero.
PollInterval time.Duration
// StartHeight, when non-zero, is the explicit rescan base applied on the
// first cycle; zero resumes from the durable checkpoint (FR-026).
StartHeight uint64
// MinimumDepositUsovr parks smaller deposits BELOW_MINIMUM; nil/zero
// disables parking.
MinimumDepositUsovr sdkmath.Int
// DisableHashChainVerification turns off the R6 last_block_id.hash chain
// check (on by default; disable only in tests).
DisableHashChainVerification bool
// MemoRecognizer recognizes omnibus memos (FR-016); nil ⇒ any non-empty
// memo is recognized.
MemoRecognizer func(memo string) bool
// PendingBatchLimit bounds each per-status pending sweep; default 500.
PendingBatchLimit int
Logger *slog.Logger
Metrics *metrics.Set
// Now is the clock; nil ⇒ time.Now (tests inject).
Now func() time.Time
}
ScannerConfig configures one Scanner (contract: go-client-api.md §deposits). Every economic value comes from configuration (FR-040).
type TransferCandidate ¶
type TransferCandidate struct {
TxHash string
MessageIndex uint32
// CoinIndex is the deposit-identity output-coin index (FR-024):
// outputs' coins flattened in wire order, non-usovr coins included in
// the numbering so indexes are stable regardless of filtering.
CoinIndex uint32
// OpIndex is the ledger row index: IN rows reuse CoinIndex; sender-side
// OUT rows continue the numbering after all output coins.
OpIndex uint32
Direction storage.LedgerDirection
// Address is the watched address this row is about.
Address string
// CounterpartySet is the full opposite-side address set — MsgMultiSend
// inputs have no deterministic input→output attribution.
CounterpartySet []string
// SenderAddress is the single unambiguous input for IN rows; nil when
// the input set is ambiguous.
SenderAddress *string
Denom string
AmountBaseUnits sdkmath.Int
Memo string
TxCode uint32
TxLog string
Classification storage.Classification
// ReviewReason is non-empty when the movement must route to review
// (omnibus memo policy, mixed watched/external input set).
ReviewReason string
}
TransferCandidate is one classified usovr movement decoded from a tx body — exactly one ChainTransferLedger row (data model §3). Deposit records derive only from Direction IN + Classification EXTERNAL_DEPOSIT rows.
func (TransferCandidate) LedgerEntry ¶
func (c TransferCandidate) LedgerEntry(chainID string, blockHeight uint64, now time.Time) storage.LedgerEntry
LedgerEntry converts the candidate to its ChainTransferLedger row.
type WatchSet ¶
type WatchSet struct {
// contains filtered or unexported fields
}
WatchSet is the parse-time view of the exchange-controlled address set (data model §9). Only active entries participate in classification.
func NewWatchSet ¶
func NewWatchSet(addrs []storage.WatchedAddress) WatchSet
NewWatchSet builds a WatchSet from watched-address rows; inactive entries are excluded.
func (WatchSet) Get ¶
func (w WatchSet) Get(addr string) (storage.WatchedAddress, bool)
Get returns the watch entry for addr.