txm

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildInvokeContractOperation added in v0.0.3

func BuildInvokeContractOperation(contractID string, functionName string, args []xdr.ScVal, fromAddress string) (*txnbuild.InvokeHostFunction, error)

BuildInvokeContractOperation builds an InvokeHostFunction operation for a Soroban contract call. It is shared by the TXM-backed invoker and the relayer's SubmitTransaction path.

func GetContextedTxLogger

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

GetContextedTxLogger returns a logger with transaction context fields attached.

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 DropReason added in v0.0.3

type DropReason string

DropReason is a bounded label classifying why a transaction was dropped from the broadcast queue.

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 DropReason = "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 DropReason = "channel_full_new_rejected"
)

type ErrorReason added in v0.0.3

type ErrorReason string

ErrorReason is a bounded label classifying broadcast and confirmation failures.

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

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.TxManagerConfig) 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(context.Context) (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 RestoreOutcome added in v0.0.3

type RestoreOutcome string

RestoreOutcome classifies RestoreFootprint lifecycle events recorded by TXM metrics.

const (
	RestoreOutcomeInitiated RestoreOutcome = "initiated"
	RestoreOutcomeSuccess   RestoreOutcome = "success"
	RestoreOutcomeFailed    RestoreOutcome = "failed"
)

type RetryBudget added in v0.0.3

type RetryBudget string

RetryBudget identifies which retry budget a tx exhausted before failing. This distinguishes retry-exhausted failures from first-attempt non-retryable failures in the max_attempts_reached metric.

const (
	// RetryBudgetLifecycle: the tx exhausted MaxTxRetryAttempts after repeated
	// post-submit lifecycle failures (on-chain resource exhaustion or ledger timeout).
	RetryBudgetLifecycle RetryBudget = "lifecycle"
	// RetryBudgetInfra: the tx exhausted MaxGetClientRetryAttempts after repeated
	// getClient (multinode RPC selection) failures before it could be simulated or submitted.
	RetryBudgetInfra RetryBudget = "infra"
)

type RetryReason

type RetryReason string

RetryReason classifies why a transaction is being retried (post-submit lifecycle retries).

const (
	RetryReasonResourceExhaustion RetryReason = "resource_exhaustion"
	RetryReasonTimedOut           RetryReason = "timed_out"
)

type StellarTx

type StellarTx struct {
	ID          string
	Metadata    *commontypes.TxMeta
	Timestamp   time.Time // when the tx was enqueued; zero if unset
	FromAddress string    // G... strkey: source account and signer for this TXM

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

	Attempt       atomic.Uint64
	InfraAttempts atomic.Uint64

	Status          commontypes.TransactionStatus
	TerminalTime    time.Time // when status first became Finalized or Failed; zero if not yet terminal
	BroadcastAt     time.Time // set when SendTransaction accepts the tx
	TxHash          string
	Fee             *big.Int // total fee in stroops; updated to actual FeeCharged on confirmation
	LedgerCloseTime int64    // unix seconds when tx was included in a ledger; from GetTransaction
	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   // broadcast-loop-owned; ledger bounds set during broadcast
	MinResourceFee  int64    // broadcast-loop-owned; 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. ID/Metadata/Timestamp/FromAddress/Operations/LedgerBoundsOffset are immutable after enqueue (safe to read without a lock). mu guards the mutable fields below it.

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.TxManagerConfig,
	getClient func(context.Context) (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. New calls cfg.Resolve() to fill defaults; in production this is a no-op (SetDefaults already resolved everything), but tests passing config.TxManagerConfig{} rely on it.

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
	LedgerCloseTime int64    // unix seconds when tx was included in a ledger; 0 if unknown
	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 TxmMetrics added in v0.0.3

type TxmMetrics interface {
	IncrementBroadcastedTxs(context.Context)
	IncrementSuccessTxs(context.Context)
	SetPendingTxs(context.Context, int)
	IncrementErrorTxs(context.Context, ErrorReason)
	IncrementRetryTxs(context.Context, RetryReason)
	IncrementDroppedTxs(context.Context, DropReason)
	IncrementMaxAttemptsReached(context.Context, RetryBudget)
	IncrementRestore(context.Context, RestoreOutcome)
	ObserveSimulationDuration(context.Context, int64)
	ObserveInclusionFee(context.Context, int64)
	ObserveResourceFee(context.Context, int64)
	RecordTimeUntilTxConfirmed(context.Context, float64)
}

TxmMetrics is the metrics contract for the Stellar TXM transaction lifecycle.

func NewNoopStellarTxmMetrics added in v0.0.3

func NewNoopStellarTxmMetrics() TxmMetrics

func NewStellarTxmMetrics added in v0.0.3

func NewStellarTxmMetrics(lggr logger.Logger, chainID string) TxmMetrics

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