chain

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: Apache-2.0 Imports: 52 Imported by: 0

Documentation

Overview

Package chain provides clients for interacting with the Manifest blockchain.

Client

The Client provides gRPC-based access to chain queries and transactions:

  • Query leases by UUID, provider, or state; query credit accounts and provider withdrawable balances
  • Submit transactions: acknowledge / reject / close leases, withdraw funds by provider
  • Wait for transaction confirmation with retry logic
  • Ping for health checks

All query operations support context cancellation and include Prometheus metrics.

EventSubscriber

The EventSubscriber connects to CometBFT's WebSocket interface to receive real-time lease events:

  • lease_created: New lease for this provider
  • lease_closed: Lease was closed by tenant
  • lease_expired: Lease expired (time limit)
  • lease_auto_closed: Lease auto-closed due to credit exhaustion

The subscriber uses a fan-out pattern where multiple consumers can subscribe independently. Each consumer gets its own buffered channel.

Signer

The Signer handles transaction signing using the Cosmos SDK keyring:

  • Loads keys from file, OS keychain, or test backend
  • Signs transactions with proper chain ID and account info
  • Provides the provider's bech32 address

Error Handling

The package defines ChainTxError for wrapping Cosmos SDK transaction errors. This enables error inspection with errors.Is() for specific error codes like insufficient funds or invalid sequence.

Reconnection

The EventSubscriber implements automatic reconnection with exponential backoff:

Initial: 1 second
Max: 60 seconds
Multiplier: 2x

The Client uses retry logic for transient gRPC errors (Unavailable, Unknown).

Index

Constants

View Source
const (
	// DefaultEventChannelCapacity is the default buffer size for subscriber channels.
	// If the channel fills up, events will be dropped with a warning log.
	// Monitor fred_events_dropped_total metric for capacity issues.
	DefaultEventChannelCapacity = 1000
)

Variables

This section is empty.

Functions

func EnsureFunding

func EnsureFunding(ctx context.Context, bankQ bankQuerier, broadcaster txBroadcaster, pool *SignerPool, minBalance, topUpAmount sdk.Coin) error

EnsureFunding tops up sub-signers that are below the minimum balance.

func EnsureGrants

func EnsureGrants(ctx context.Context, authzQ authzQuerier, broadcaster txBroadcaster, pool *SignerPool) error

EnsureGrants creates any missing authz grants from the provider (granter) to each sub-signer (grantee) for the billing message types.

func SignWithPrivKey

func SignWithPrivKey(
	ctx context.Context,
	signMode signing.SignMode,
	signerData authsigning.SignerData,
	txBuilder client.TxBuilder,
	kr keyring.Keyring,
	keyName string,
	txConfig client.TxConfig,
	sequence uint64,
) (signing.SignatureV2, error)

SignWithPrivKey signs using the keyring.

Types

type ChainTxError

type ChainTxError struct {
	Code      uint32 // ABCI error code
	Codespace string // Module that generated the error
	RawLog    string // Human-readable error message
}

ChainTxError represents an error from a failed chain transaction. It captures the ABCI error code and codespace, allowing callers to check against specific module errors using errors.Is().

func (*ChainTxError) Error

func (e *ChainTxError) Error() string

Error implements the error interface.

func (*ChainTxError) ExpectedSequence

func (e *ChainTxError) ExpectedSequence() (uint64, bool)

ExpectedSequence extracts the expected sequence number from a sequence mismatch error. Returns 0, false if the error is not a sequence mismatch or the expected sequence cannot be parsed.

func (*ChainTxError) Is

func (e *ChainTxError) Is(target error) bool

Is implements errors.Is() interface to check against cosmossdk.io/errors registered errors. This allows checking: errors.Is(err, billingtypes.ErrLeaseNotPending)

func (*ChainTxError) IsInsufficientFee

func (e *ChainTxError) IsInsufficientFee() bool

IsInsufficientFee returns true if this error is an insufficient-fee error (SDK code 13).

func (*ChainTxError) IsOutOfGas

func (e *ChainTxError) IsOutOfGas() bool

IsOutOfGas returns true if this error is an out-of-gas error (SDK code 11).

func (*ChainTxError) IsSequenceMismatch

func (e *ChainTxError) IsSequenceMismatch() bool

IsSequenceMismatch returns true if this error is a sequence mismatch (SDK code 32).

func (*ChainTxError) IsTxInMempool

func (e *ChainTxError) IsTxInMempool() bool

IsTxInMempool returns true if this error indicates the transaction is already in the mempool cache (SDK code 19). This happens when waitForTx times out on a previous attempt and the retry re-signs with the same sequence, producing identical tx bytes whose hash is still cached in the CometBFT mempool.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client provides methods to query and submit transactions to the chain.

func NewClient

func NewClient(cfg ClientConfig, pool *SignerPool) (*Client, error)

NewClient creates a new chain client connected to the given gRPC endpoint. The pool parameter is required; pass a single-signer pool for backward compatibility.

func (*Client) AcknowledgeLeases

func (c *Client) AcknowledgeLeases(ctx context.Context, leaseUUIDs []string) (uint64, []string, error)

AcknowledgeLeases acknowledges the given leases. Returns the number of leases acknowledged and tx hashes.

func (*Client) Close

func (c *Client) Close() error

Close closes the gRPC connection.

func (*Client) CloseLeases

func (c *Client) CloseLeases(ctx context.Context, leaseUUIDs []string, reason string) (uint64, []string, error)

CloseLeases closes the given leases with an optional reason. Returns the number of leases closed and tx hashes.

func (*Client) Conn

func (c *Client) Conn() *grpc.ClientConn

Conn returns the underlying gRPC connection for creating query clients.

func (*Client) GetActiveLease

func (c *Client) GetActiveLease(ctx context.Context, leaseUUID string) (*billingtypes.Lease, error)

GetActiveLease returns an active lease by UUID, or nil if not found or not active.

func (*Client) GetActiveLeasesByProvider

func (c *Client) GetActiveLeasesByProvider(ctx context.Context, providerUUID string) ([]billingtypes.Lease, error)

GetActiveLeasesByProvider returns all active leases for a provider.

func (*Client) GetCreditAccount

func (c *Client) GetCreditAccount(ctx context.Context, tenant string) (*billingtypes.CreditAccount, sdktypes.Coins, error)

GetCreditAccount returns the credit account and balances for a tenant.

func (*Client) GetLease

func (c *Client) GetLease(ctx context.Context, leaseUUID string) (*billingtypes.Lease, error)

GetLease returns a lease by UUID regardless of state. Returns nil if the lease is not found.

func (*Client) GetPendingLeases

func (c *Client) GetPendingLeases(ctx context.Context, providerUUID string) ([]billingtypes.Lease, error)

GetPendingLeases returns all pending leases for a provider.

func (*Client) GetProvider

func (c *Client) GetProvider(ctx context.Context, providerUUID string) (*skutypes.Provider, error)

GetProvider returns provider details by UUID.

func (*Client) GetProviderWithdrawable

func (c *Client) GetProviderWithdrawable(ctx context.Context, providerUUID string) (sdktypes.Coins, error)

GetProviderWithdrawable returns the total withdrawable amounts for a provider. The ProviderWithdrawable query is paginated over active leases, so we page through all results and sum the amounts for the full total.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping checks if the chain connection is healthy by querying the signer's account. Returns nil if healthy, otherwise returns the error.

func (*Client) RejectLeases

func (c *Client) RejectLeases(ctx context.Context, leaseUUIDs []string, reason string) (uint64, []string, error)

RejectLeases rejects the given pending leases with an optional reason. Returns the number of leases rejected and tx hashes. This is used when provisioning fails and the provider cannot fulfill the lease.

func (*Client) WithdrawByProvider

func (c *Client) WithdrawByProvider(ctx context.Context, providerUUID string, key []byte) (string, *billingtypes.MsgWithdrawResponse, error)

WithdrawByProvider withdraws funds from one page of a provider's active leases and returns the transaction hash and the decoded response.

Provider-wide withdrawal is paginated by an opaque cursor: pass an empty key to start from the beginning, then echo back the returned NextKey to advance to the next page. NextKey is empty once every active lease has been settled (equivalently, MsgWithdrawResponse.HasMore is false). The cursor is opaque — it is passed back verbatim, never parsed. Callers loop until NextKey is empty (see the WithdrawScheduler); the per-cycle bound lives there, not here.

Limit is the configured withdrawLimit (leases settled per page); the scheduler pages via the cursor, so this only trades settlement-tx count against per-tx gas.

type ClientConfig

type ClientConfig struct {
	Endpoint       string
	TLSEnabled     bool
	TLSCAFile      string        // Path to CA certificate file (optional, uses system CAs if empty)
	TLSSkipVerify  bool          // Skip certificate verification (for testing only)
	TxPollInterval time.Duration // Interval for polling tx status (default: 500ms)
	TxTimeout      time.Duration // Timeout for waiting for tx inclusion (default: 60s)
	QueryPageLimit int           // Page limit for paginated queries (default: 100)
	WithdrawLimit  int           // Leases settled per provider-wide MsgWithdraw page (default: 100; the chain rejects > MaxBatchLeaseSize)
}

ClientConfig holds configuration for the chain client.

type EventSubscriber

type EventSubscriber struct {
	// contains filtered or unexported fields
}

EventSubscriber handles WebSocket subscription to CometBFT events. It supports multiple consumers via Subscribe() - each subscriber gets its own channel and receives all events (fan-out pattern).

func NewEventSubscriber

func NewEventSubscriber(cfg EventSubscriberConfig) (*EventSubscriber, error)

NewEventSubscriber creates a new event subscriber.

func (*EventSubscriber) Close

func (s *EventSubscriber) Close()

Close shuts down the event subscriber and closes all subscriber channels. Safe to call multiple times - subsequent calls are no-ops.

func (*EventSubscriber) Start

func (s *EventSubscriber) Start(ctx context.Context) error

Start begins listening for events. It will automatically reconnect on failure.

func (*EventSubscriber) Subscribe

func (s *EventSubscriber) Subscribe() chan LeaseEvent

Subscribe creates a new subscription and returns a channel that receives all events. Each subscriber gets its own buffered channel. Call Unsubscribe when done to avoid leaks. Returns a bidirectional channel so it can be passed to Unsubscribe for cleanup. Returns nil if the subscriber has been closed.

func (*EventSubscriber) Unsubscribe

func (s *EventSubscriber) Unsubscribe(ch chan LeaseEvent)

Unsubscribe removes a subscription and closes its channel. Safe to call multiple times or with a nil channel.

type EventSubscriberConfig

type EventSubscriberConfig struct {
	URL              string
	ProviderUUID     string
	PingInterval     time.Duration
	ReconnectInitial time.Duration
	ReconnectMax     time.Duration
	ChannelCapacity  int // Buffer size for subscriber channels (default: 1000)
}

EventSubscriberConfig holds configuration for the event subscriber.

type LeaseEvent

type LeaseEvent struct {
	Type         LeaseEventType
	LeaseUUID    string
	ProviderUUID string
	Tenant       string
}

LeaseEvent represents a lease-related event from the chain.

type LeaseEventType

type LeaseEventType string

LeaseEventType represents the type of lease event.

const (
	LeaseCreated      LeaseEventType = "lease_created"
	LeaseAcknowledged LeaseEventType = "lease_acknowledged"
	LeaseRejected     LeaseEventType = "lease_rejected"
	LeaseClosed       LeaseEventType = "lease_closed"
	LeaseExpired      LeaseEventType = "lease_expired"
	LeaseAutoClosed   LeaseEventType = "lease_auto_closed" // Credit exhaustion (any provider)
)

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer handles transaction signing using a Cosmos keyring.

func (*Signer) Address

func (s *Signer) Address() string

Address returns the signer's address.

func (*Signer) BuildSimTx added in v0.6.0

func (s *Signer) BuildSimTx(msgs []sdk.Msg, accountAny *codectypes.Any, declaredGas uint64) ([]byte, error)

BuildSimTx builds an encoded, zero-fee, dummy-signed tx suitable for the Simulate RPC. The signature is empty (Simulate skips sig verification) but uses the real pubkey + the account's on-chain sequence (Simulate DOES check the sequence). Callers pass simDeclaredGas as declaredGas (see the const).

We deliberately do NOT reuse the SDK's clienttx.CalculateGas / Factory.BuildSimTx: they require a tx.Factory + client.Context that fred's custom Signer/keyring never constructs, and CalculateGas applies the adjustment via uint64(adj*float64(GasUsed)) — the float→uint64 truncation fred deliberately replaced with deterministic cosmossdk.io/math (see adjustGas).

func (*Signer) FallbackGas added in v0.6.0

func (s *Signer) FallbackGas() (uint64, error)

FallbackGas returns the Simulate-failure fallback gas: the configured gas_limit run through the same adjustment+cap as the success path. This is the single source of truth for the degraded path (byte-for-byte the legacy effective default), used by the broadcast pre-seed when Simulate is down.

type SignerBalanceCollector

type SignerBalanceCollector struct {
	// contains filtered or unexported fields
}

SignerBalanceCollector implements prometheus.Collector and emits the `fred_signer_balance` gauge on every /metrics scrape.

On Collect, the pool is snapshotted (live reads of ProviderAddress and SubSignerAddresses), one balance query is fanned out per address against the bank module, and each successful response yields one gauge series. The balance (a math.Int) is emitted as a float64 via a big.Float intermediate with no int64 round-trip, so balances above math.MaxInt64 (e.g. the ~1e29 umfx dev provider) produce a healthy series instead of being misclassified as a failure; float64 is exact for integers <= 2^53, with negligible relative rounding above that for threshold alerting. The configured denom is both queried from bank and emitted as the `denom` label so the gauge is accurate on any deployment (not just umfx-denominated networks). Per-address query failures — a bank RPC error or a nil/empty response, never a value-representation condition — drop only that address's series and bump metrics.SignerBalanceQueryFailures (no per-index counter cardinality).

The collector is intentionally stateless across scrapes — it does not cache balances, does not run a background sampler, and does not allocate goroutines outside of Collect. This means the gauge naturally tracks the live pool (e.g. after DemoteToSingleSigner) without any extra wiring.

func NewSignerBalanceCollector

func NewSignerBalanceCollector(bankQ bankQuerier, pool *SignerPool, denom string, scrapeTimeout time.Duration) *SignerBalanceCollector

NewSignerBalanceCollector constructs a per-scrape signer balance collector. denom is the bank module denom to query (e.g. "umfx") and is also emitted as the `denom` label on each series so the metric stays accurate when the network uses a non-umfx fee denom. scrapeTimeout bounds the wall time of a single Collect call across all fanned-out queries.

func (*SignerBalanceCollector) Collect

func (c *SignerBalanceCollector) Collect(ch chan<- prometheus.Metric)

Collect implements prometheus.Collector. Safe for concurrent calls.

func (*SignerBalanceCollector) Describe

func (c *SignerBalanceCollector) Describe(ch chan<- *prometheus.Desc)

Describe implements prometheus.Collector.

type SignerConfig

type SignerConfig struct {
	KeyringBackend string
	KeyringDir     string
	KeyName        string
	ChainID        string
	GasLimit       uint64
	MaxGasLimit    uint64  // 0 = no cap; if set, caps the gas limit during out-of-gas retries
	GasAdjustment  float64 // multiplier applied to GasLimit at sign time (Cosmos CLI convention); 0 or 1.0 = no adjustment
	GasPrice       int64
	FeeDenom       string
	Passphrase     string // Keyring passphrase for "file" backend (read from FRED_KEYRING_PASSPHRASE env)
}

SignerConfig holds configuration for the transaction signer.

type SignerPool

type SignerPool struct {
	// contains filtered or unexported fields
}

SignerPool manages a primary signer and optional sub-signers for parallel transaction broadcasting via CosmosSDK authz.

func NewSignerPool

func NewSignerPool(cfg SignerPoolConfig) (*SignerPool, error)

NewSignerPool creates a signer pool with a primary signer and optional sub-signers. Sub-signer keys are derived from the mnemonic on first boot, and loaded from the keyring on subsequent boots.

func (*SignerPool) Acquire

func (p *SignerPool) Acquire() (*Signer, bool, func())

Acquire returns the next available sub-signer with exclusive access. If no sub-signers exist, returns the primary signer (no locking needed). The caller MUST call the returned release function when done; failing to do so will deadlock the pool.

func (*SignerPool) DemoteToSingleSigner

func (p *SignerPool) DemoteToSingleSigner()

DemoteToSingleSigner removes all sub-signers, making the pool primary-only. Thread-safe, but must be called before the AckBatcher is constructed since it captures lane count at construction time.

func (*SignerPool) HasSubSigners

func (p *SignerPool) HasSubSigners() bool

HasSubSigners returns true if the pool has any sub-signers.

func (*SignerPool) LaneCount

func (p *SignerPool) LaneCount() int

LaneCount returns the number of parallel lanes for the batcher. With 0 sub-signers → 1 lane (primary). With N sub-signers → N lanes.

func (*SignerPool) Primary

func (p *SignerPool) Primary() *Signer

Primary returns the primary signer. Used for operations that must use the provider key (withdrawals, grants, funding).

func (*SignerPool) ProviderAddress

func (p *SignerPool) ProviderAddress() string

ProviderAddress returns the primary signer's address.

func (*SignerPool) Size

func (p *SignerPool) Size() int

Size returns the total number of signers (1 primary + N sub-signers).

func (*SignerPool) SubSignerAddresses

func (p *SignerPool) SubSignerAddresses() []string

SubSignerAddresses returns the addresses of all sub-signers.

type SignerPoolConfig

type SignerPoolConfig struct {
	SignerConfig
	SubSignerCount int    // 0 = single signer (default)
	Mnemonic       string // only used on first boot to derive sub-signer keys
}

SignerPoolConfig extends SignerConfig with sub-signer options.

Directories

Path Synopsis
Package chaintest provides test doubles for the chain package.
Package chaintest provides test doubles for the chain package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL