txm

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ErrorReasonSequenceNumber    = "sequence_number"
	ErrorReasonStoreCreate       = "store_create"
	ErrorReasonSimulation        = "simulation"
	ErrorReasonAssembly          = "assembly"
	ErrorReasonSigning           = "signing"
	ErrorReasonNoHash            = "no_hash"
	ErrorReasonStoreAdd          = "store_add"
	ErrorReasonUnknownSubmit     = "unknown_submit"
	ErrorReasonMaxRetries        = "max_retries"
	ErrorReasonRevert            = "revert"
	ErrorReasonTimedOut          = "timed_out"
	ErrorReasonBadSeq            = "bad_seq"
	ErrorReasonInsufficientBal   = "insufficient_balance"
	ErrorReasonRestoreFailed     = "restore_failed"
	ErrorReasonBadAuth           = "bad_auth"
	ErrorReasonTryAgainLater     = "try_again_later"
	ErrorReasonClientUnavailable = "client_unavailable"
	ErrorReasonInsufficientFee   = "insufficient_fee"
	ErrorReasonInternalError     = "internal_error"
	ErrorReasonNilTx             = "nil_tx"
	ErrorReasonNilTxStore        = "nil_tx_store"
	// ErrorReasonSubmitErrorUndecoded means the node returned TXStatusError but
	// ErrorResultXDR was empty or not valid transaction-result XDR.
	ErrorReasonSubmitErrorUndecoded = "submit_error_undecoded"
)

Error reason constants classify broadcast and confirmation failures.

View Source
const (
	// DropReasonChannelFullOldestEvicted: the oldest queued tx was evicted to make
	// room for a newer one. The oldest has the stalest simulation data and the
	// nearest LedgerBounds expiry, so the new tx's intent takes priority.
	DropReasonChannelFullOldestEvicted = "channel_full_oldest_evicted"

	// DropReasonChannelFullNewRejected: the incoming tx was rejected because the
	// channel was still full after an attempted oldest-evict (concurrent enqueue race).
	DropReasonChannelFullNewRejected = "channel_full_new_rejected"
)

Drop reasons classify why a pending transaction was dropped from the broadcast queue.

Variables

View Source
var DefaultConfigSet = Config{
	BroadcastChanSize:   ptr(uint(100)),
	ConfirmPollInterval: config.MustNewDuration(3 * time.Second),

	BaseInclusionFee:     ptr(int64(100)),
	MaxInclusionFee:      ptr(int64(100_000)),
	FeeBumpMultiplier:    ptr(1.5),
	ResourceFeeBuffer:    ptr(int64(15_000)),
	RestoreFeeBuffer:     ptr(int64(10_000)),
	FeeStatsPollInterval: config.MustNewDuration(5 * time.Second),

	MaxSimulateAttempts:    ptr(uint(3)),
	MaxSubmitRetryAttempts: ptr(uint(10)),
	SubmitRetryDelay:       config.MustNewDuration(3 * time.Second),
	TxTimeoutSecs:          ptr(int64(300)),
	LedgerBoundsOffset:     ptr(uint32(50)),
	MaxTxRetryAttempts:     ptr(uint64(5)),
	MaxRestoreAttempts:     ptr(uint(3)),

	PruneInterval:     config.MustNewDuration(2 * time.Hour),
	PruneTxExpiration: config.MustNewDuration(2 * time.Hour),
}

DefaultConfigSet is the default configuration for the Stellar Transaction Manager.

Functions

func GetContextedTxLogger

func GetContextedTxLogger(lgr logger.Logger, txID string, meta *commontypes.TxMeta) logger.Logger

GetContextedTxLogger returns a logger with transaction context fields attached.

func NetworkPassphrase

func NetworkPassphrase(chainID string) (string, error)

NetworkPassphrase returns the Stellar network passphrase for a given chain-selectors chain ID. Passphrases are static per network so we resolve them from chainID rather than carrying them through TOML config.

Localnet shares testnet's passphrase by convention — the network ID hash is what matters for signing, and Stellar localnet is configured against the testnet passphrase.

Types

type AccountStore

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

AccountStore holds a TxStore per Stellar account address.

func NewAccountStore

func NewAccountStore() *AccountStore

func (*AccountStore) CreateTxStore

func (c *AccountStore) CreateTxStore(accountAddress string, initialSequence int64) (*TxStore, error)

CreateTxStore initializes a TxStore for a new account. Returns an error if a store already exists for this address.

func (*AccountStore) GetAllUnconfirmed

func (c *AccountStore) GetAllUnconfirmed() map[string][]*UnconfirmedTx

func (*AccountStore) GetTotalInflightCount

func (c *AccountStore) GetTotalInflightCount() int

func (*AccountStore) GetTxStore

func (c *AccountStore) GetTxStore(accountAddress string) *TxStore

type Config

type Config struct {
	BroadcastChanSize   *uint            `toml:"BroadcastChanSize"`
	ConfirmPollInterval *config.Duration `toml:"ConfirmPollInterval"`

	// Fee strategy: Stellar fees = InclusionFee + ResourceFee.
	// Only the inclusion fee is bumped on retries; the resource fee is deterministic from simulation.
	BaseInclusionFee  *int64   `toml:"BaseInclusionFee"`
	MaxInclusionFee   *int64   `toml:"MaxInclusionFee"`
	FeeBumpMultiplier *float64 `toml:"FeeBumpMultiplier"`
	ResourceFeeBuffer *int64   `toml:"ResourceFeeBuffer"`
	RestoreFeeBuffer  *int64   `toml:"RestoreFeeBuffer"`
	// FeeStatsPollInterval controls how often GetFeeStats is called to refresh
	// Soroban inclusion fee P50/P90 in the feeTracker; back-to-back broadcasts reuse values.
	// Zero disables reuse (every inclusion-fee decision calls GetFeeStats).
	FeeStatsPollInterval *config.Duration `toml:"FeeStatsPollInterval"`

	// Retry & timeout
	MaxSimulateAttempts    *uint            `toml:"MaxSimulateAttempts"`
	MaxSubmitRetryAttempts *uint            `toml:"MaxSubmitRetryAttempts"`
	SubmitRetryDelay       *config.Duration `toml:"SubmitRetryDelay"`
	TxTimeoutSecs          *int64           `toml:"TxTimeoutSecs"`
	LedgerBoundsOffset     *uint32          `toml:"LedgerBoundsOffset"`
	MaxTxRetryAttempts     *uint64          `toml:"MaxTxRetryAttempts"`
	MaxRestoreAttempts     *uint            `toml:"MaxRestoreAttempts"`

	// SimulationTerminalHints are matched case-insensitively as substrings
	// against failed SimulateTransaction errors; any match means the error is
	// treated as terminal (do not retry). Resolve merges these with built-in
	// defaults (additive); list only extra hints to add on top of defaults.
	SimulationTerminalHints []string `toml:"SimulationTerminalHints"`
	// SimulationRetryableHints: any substring match means retry simulation when
	// attempts remain. Resolve merges with built-in defaults (additive).
	SimulationRetryableHints []string `toml:"SimulationRetryableHints"`

	// Pruning
	PruneInterval     *config.Duration `toml:"PruneInterval"`
	PruneTxExpiration *config.Duration `toml:"PruneTxExpiration"`
}

Config defines the Stellar transaction manager configuration. Pointer fields are used for TOML deserialization — nil means "not set by user". After calling Resolve(), scalar pointer fields are non-nil; simulation hint slices are non-empty (built-in defaults when unset).

func (*Config) Resolve

func (c *Config) Resolve()

Resolve fills nil fields with defaults from DefaultConfigSet. After calling Resolve, scalar pointer fields are non-nil; simulation hint slices are non-empty when defaults apply.

type FeeStrategy

type FeeStrategy struct {
	BaseInclusionFee  int64
	MaxInclusionFee   int64
	BumpMultiplier    float64
	ResourceFeeBuffer int64
}

FeeStrategy calculates Stellar transaction fees.

Stellar fees have two independent components:

  • Inclusion fee: market-based bid for validator priority (bumped on retries)
  • Resource fee: deterministic cost from simulation (not negotiable)

Total fee = inclusionFee(attempt) + minResourceFee + resourceFeeBuffer

func NewFeeStrategyFromConfig

func NewFeeStrategyFromConfig(cfg Config) FeeStrategy

NewFeeStrategyFromConfig constructs a FeeStrategy from the resolved Config.

func (*FeeStrategy) BumpInclusionFee

func (f *FeeStrategy) BumpInclusionFee(currentInclusionFee int64, networkPercentile uint64) (fee int64, clampedToMax bool)

BumpInclusionFee returns the next inclusion fee after a submit rejection that requires a higher bid (e.g. tx_insufficient_fee): multiply the current fee, take max with networkPercentile (typically live P90 from GetFeeStats), and clamp to MaxInclusionFee.

func (*FeeStrategy) Calculate

func (f *FeeStrategy) Calculate(minResourceFee int64, attempt uint64) int64

Calculate returns the total fee (in stroops) for a transaction at the given attempt. The inclusion fee is geometrically bumped per attempt; the resource fee is passed through from simulation with a flat safety buffer.

func (*FeeStrategy) CalculateRestoreFee

func (f *FeeStrategy) CalculateRestoreFee(preambleMinResourceFee int64, restoreFeeBuffer int64) int64

CalculateRestoreFee returns the fee for a RestoreFootprint transaction. Restore fees are deterministic (no fee competition), so no geometric bumping.

func (*FeeStrategy) InclusionFee

func (f *FeeStrategy) InclusionFee(attempt uint64) int64

InclusionFee returns the inclusion fee for the given attempt number.

func (*FeeStrategy) SeedInclusionFee

func (f *FeeStrategy) SeedInclusionFee(attempt uint64, networkPercentile uint64) (fee int64, clampedToMax bool)

type InvokerAdapter

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

InvokerAdapter implements bindings.Invoker by routing contract work through the Stellar TXM: InvokeContract → EnqueueAndWait (async pipeline, signing, fees, retries), SimulateContract → Simulate, GetEvents → RPC (no sequence).

Generated binding clients (e.g. routerbindings.NewRouterClient) take a bindings.Invoker — they do not reference InvokerAdapter directly. This type is one Invoker implementation for relayer/Chainlink-style TXM usage; other stacks may use a different Invoker (e.g. deployment.Deployer in ccv/devenv, which signs and submits via RPC without the TXM).

func NewInvokerAdapter

func NewInvokerAdapter(
	txm InvokerTxManager,
	getClient func() (RPCClient, error),
	opts ...InvokerAdapterOption,
) (*InvokerAdapter, error)

NewInvokerAdapter creates a bindings.Invoker backed by the Stellar TXM.

func (*InvokerAdapter) GetEvents

func (a *InvokerAdapter) GetEvents(ctx context.Context, contractID string, startLedger uint32, topics []string) ([]protocolrpc.EventInfo, error)

GetEvents reads contract events directly from RPC. TXM is intentionally not involved because event reads do not consume sequence numbers or fees.

func (*InvokerAdapter) InvokeContract

func (a *InvokerAdapter) InvokeContract(ctx context.Context, contractID string, functionName string, args []xdr.ScVal) (*xdr.ScVal, error)

InvokeContract submits a Soroban contract invocation through the TXM and blocks until the transaction reaches a terminal state (or ctx is cancelled), then returns the Soroban return value from the confirmed transaction metadata.

func (*InvokerAdapter) SimulateContract

func (a *InvokerAdapter) SimulateContract(ctx context.Context, contractID string, functionName string, args []xdr.ScVal) (*xdr.ScVal, error)

SimulateContract performs a read-only Soroban simulation through the TXM and returns the simulated Soroban return value.

type InvokerAdapterOption

type InvokerAdapterOption func(*InvokerAdapter)

InvokerAdapterOption customizes InvokerAdapter behavior.

func WithInvokerFromAddress

func WithInvokerFromAddress(fromAddress string) InvokerAdapterOption

WithInvokerFromAddress sets the account used as the source for contract invocations. If unset, StellarTxm falls back to its first keystore account.

func WithInvokerLedgerBoundsOffset

func WithInvokerLedgerBoundsOffset(offset uint32) InvokerAdapterOption

WithInvokerLedgerBoundsOffset overrides the TXM default ledger bounds for invocations submitted through this adapter.

type InvokerTxManager

type InvokerTxManager interface {
	EnqueueAndWait(ctx context.Context, req TxRequest) (*TxResult, error)
	Simulate(ctx context.Context, req TxRequest) (protocolrpc.SimulateTransactionResponse, error)
}

InvokerTxManager is the subset of StellarTxm used by InvokerAdapter.

type RPCClient

RPCClient is the subset of the Stellar Soroban JSON-RPC client used by the TXM. Any value satisfying chain.RPCClient (a superset) automatically satisfies this.

type RetryReason

type RetryReason int

RetryReason classifies why a transaction is being retried (Layer 3 lifecycle retries).

const (
	RetryReasonResourceExhaustion RetryReason = iota
	RetryReasonTimedOut
	RetryReasonBadSeq
	RetryReasonTryAgainLater
	RetryReasonClientUnavailable
)

func (RetryReason) String

func (r RetryReason) String() string

type StellarTx

type StellarTx struct {
	ID          string
	Metadata    *commontypes.TxMeta
	Timestamp   uint64
	FromAddress string // G... strkey: source account and signer for this TXM

	Operations         []txnbuild.Operation
	LedgerBoundsOffset uint32 // per-tx override (0 = use config default)

	Attempt        uint64
	Status         commontypes.TransactionStatus
	TxHash         string
	Fee            *big.Int // total fee in stroops; updated to actual FeeCharged on confirmation
	ResultXDR      string   // XDR-encoded transaction result from GetTransaction
	ResultCode     string   // result code from GetTransaction (for diagnostics)
	ResultMetaXDR  string   // XDR-encoded result meta from GetTransaction SUCCESS
	MaxLedger      uint32   // ledger bounds set during broadcast
	MinResourceFee int64    // from simulation result

	// Done is closed when the transaction reaches a terminal state.
	Done chan struct{}
	// contains filtered or unexported fields
}

StellarTx represents a single transaction tracked by the TXM from enqueue to confirmation.

type StellarTxm

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

StellarTxm orchestrates the lifecycle of Stellar/Soroban transactions: enqueue → simulate → (restore) → assemble → sign → send → confirm.

func New

func New(
	lgr logger.Logger,
	keystore core.Keystore,
	cfg Config,
	getClient func() (RPCClient, error),
	chainID string,
) (*StellarTxm, error)

New creates a StellarTxm. The getClient callback should be obtained from chain.Chain.GetClient to enable multi-node rotation; in normal wiring the chain package constructs the TXM and passes its own GetClient method. The network passphrase is resolved from chainID via NetworkPassphrase. cfg is normalized with Resolve so pointer fields (e.g. TxTimeoutSecs) are non-nil for the lifetime of the TXM; do not construct StellarTxm manually with an unresolved Config.

func (*StellarTxm) Close

func (s *StellarTxm) Close() error

func (*StellarTxm) Enqueue

func (s *StellarTxm) Enqueue(ctx context.Context, req TxRequest) (string, error)

Enqueue submits a Soroban transaction request for asynchronous processing. Returns the transaction ID (auto-generated if TxRequest.ID is empty). If TxRequest.ID is already in flight or tracked, returns that same id with a nil error and does not enqueue again (idempotent, aligned with EVM TxMgr idempotency key behavior).

func (*StellarTxm) EnqueueAndWait

func (s *StellarTxm) EnqueueAndWait(ctx context.Context, req TxRequest) (*TxResult, error)

EnqueueAndWait submits a transaction and blocks until it reaches a terminal state (Finalized, Failed) or the context is cancelled.

func (*StellarTxm) GetStatus

func (s *StellarTxm) GetStatus(transactionID string) (commontypes.TransactionStatus, error)

func (*StellarTxm) GetTransactionFee

func (s *StellarTxm) GetTransactionFee(transactionID string) (*big.Int, error)

func (*StellarTxm) GetTransactionResult

func (s *StellarTxm) GetTransactionResult(transactionID string) (*TxResult, error)

func (*StellarTxm) HealthReport

func (s *StellarTxm) HealthReport() map[string]error

func (*StellarTxm) InflightCount

func (s *StellarTxm) InflightCount() (int, int)

InflightCount returns (queued broadcast work items, total unconfirmed across all accounts).

func (*StellarTxm) Name

func (s *StellarTxm) Name() string

func (*StellarTxm) Ready

func (s *StellarTxm) Ready() error

func (*StellarTxm) Simulate

Simulate performs a read-only simulation without consuming a sequence number or broadcasting. Callers receive the raw SimulateTransactionResponse so they can inspect resource usage, auth entries, and return values. This is the entry point for InvokerAdapter.SimulateContract and other read-only queries.

func (*StellarTxm) Start

func (s *StellarTxm) Start(_ context.Context) error

type TxRequest

type TxRequest struct {
	ID                 string               // idempotency key (auto-generated if empty)
	FromAddress        string               // optional; defaults to TXM's signer address
	Operations         []txnbuild.Operation // the Stellar operations to execute
	LedgerBoundsOffset uint32               // per-tx override (0 = use config default)
	Metadata           *commontypes.TxMeta  // optional; carries WorkflowExecutionID and other node-level context
}

TxRequest is the input accepted by Enqueue / EnqueueAndWait.

type TxResult

type TxResult struct {
	ID            string
	Hash          string
	Status        commontypes.TransactionStatus
	Fee           *big.Int // total fee charged in stroops
	ResultXDR     string   // XDR-encoded transaction result from GetTransaction
	ResultMetaXDR string   // XDR-encoded result meta from GetTransaction
	Error         error
}

TxResult is returned by EnqueueAndWait and Simulate with the outcome of a transaction.

type TxStore

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

TxStore tracks sequence numbers and in-flight transactions for a single Stellar account. Sequence numbers are strictly sequential: a gap blocks all subsequent transactions. The failed-sequence recycling logic ensures gaps are plugged.

func NewTxStore

func NewTxStore(initialSequence int64) *TxStore

func (*TxStore) AddUnconfirmed

func (s *TxStore) AddUnconfirmed(seq int64, hash string, maxLedger uint32, tx *StellarTx) error

AddUnconfirmed records a transaction that has been submitted to the network. The sequence must match the value returned by the preceding GetNextSequence() call.

func (*TxStore) Confirm

func (s *TxStore) Confirm(seq int64, hash string, failed bool) error

Confirm removes a transaction from the unconfirmed set. If failed is true and the sequence is still ahead of the last known on-chain sequence, it is added to failedSequences for recycling.

func (*TxStore) GetLastResyncedNonce

func (s *TxStore) GetLastResyncedNonce() int64

func (*TxStore) GetNextSequence

func (s *TxStore) GetNextSequence() int64

GetNextSequence returns the next sequence number to use. If there are failed (recycled) sequences, it returns the smallest of (nextSequence, min(failedSequences)) to plug gaps.

func (*TxStore) GetUnconfirmed

func (s *TxStore) GetUnconfirmed() []*UnconfirmedTx

GetUnconfirmed returns a sorted (by sequence) snapshot of all unconfirmed transactions.

func (*TxStore) InflightCount

func (s *TxStore) InflightCount() int

func (*TxStore) Release

func (s *TxStore) Release(seq int64)

Release returns an allocated-but-never-broadcast sequence to the failed pool for reuse. The broadcaster must call this at every early-return error path between GetNextSequence() and SendTransaction (e.g., simulation fails, assembly errors, signing errors). Without this, a pre-broadcast failure would permanently leak a sequence number.

func (*TxStore) ResyncNonce

func (s *TxStore) ResyncNonce(nextExpectedSequence int64)

ResyncNonce updates the TxStore's view of on-chain state.

IMPORTANT: On Stellar, the on-chain account.SeqNum is the LAST USED sequence. The caller must pass onchainSeq+1 (the next expected sequence number).

This must not be called between GetNextSequence() and AddUnconfirmed(), as it mutates nextSequence.

type UnconfirmedTx

type UnconfirmedTx struct {
	Sequence  int64
	Hash      string
	MaxLedger uint32 // LedgerBounds.MaxLedger — primary timeout mechanism
	Tx        *StellarTx
}

UnconfirmedTx tracks a transaction that has been sent to the network but has not yet been confirmed (or rejected) by a ledger.

Jump to

Keyboard shortcuts

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