helper

package
v0.5.49 Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package helper implements the share processing pipeline that receives encrypted voting shares from wallets, waits until wallet-provided submit_at times, generates ZKP 3 proofs, and submits MsgRevealShare to the chain.

This package runs inside the svoted binary, reading commitment tree leaves directly from the vote keeper's KV store.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidCommitment = fmt.Errorf("invalid vote commitment")

ErrInvalidCommitment is returned when the share's recomputed vote commitment hash does not match the on-chain leaf at the claimed tree position.

View Source
var ErrInvalidSubmitAt = errors.New("invalid submit_at")

ErrInvalidSubmitAt is returned when submit_at is after vote end time.

View Source
var ErrUnknownRound = errors.New("unknown voting round")

ErrUnknownRound is returned when a share references a round that does not exist on-chain. Callers can check for this with errors.Is to distinguish it from transient failures.

Functions

func CaptureErr added in v0.4.2

func CaptureErr(err error, tags map[string]string)

CaptureErr delegates to the shared sentry package.

func FlushSentry added in v0.4.2

func FlushSentry()

FlushSentry delegates to the shared sentry package.

func InitSentry added in v0.4.2

func InitSentry(dsn, release, serverName string, logger log.Logger) error

InitSentry delegates to the shared sentry package.

func IsDuplicateNullifier

func IsDuplicateNullifier(code uint32) bool

IsDuplicateNullifier returns true if the chain rejection code matches ErrDuplicateNullifier, meaning the share was already revealed by another helper. In quorum mode multiple helpers process the same share; the first to submit wins and the rest receive this benign rejection.

func RegisterRoutes

func RegisterRoutes(router *mux.Router, store *ShareStore, logger log.Logger)

RegisterRoutes registers helper server HTTP routes on the given mux router.

func RegisterRoutesWithGetters

func RegisterRoutesWithGetters(
	router *mux.Router,
	getStore func() *ShareStore,
	getAPIToken func() string,
	getExposeQueueStatus func() bool,
	getTree func() TreeReader,
	getVCHash func() VCHashFunc,
	getShareNullifier ShareNullifierCheckerGetter,
	logger log.Logger,
)

RegisterRoutesWithGetters registers helper routes using runtime getters for store, API token, tree reader, VC hash function, and share-nullifier checker.

func RegisterRoutesWithStoreGetter

func RegisterRoutesWithStoreGetter(router *mux.Router, getStore func() *ShareStore, logger log.Logger)

RegisterRoutesWithStoreGetter registers helper server HTTP routes on the given mux router, resolving the store at request time. This allows routes to be mounted before the helper is fully initialized.

Types

type BroadcastResult

type BroadcastResult struct {
	TxHash string `json:"tx_hash"`
	Code   uint32 `json:"code"`
	Log    string `json:"log"`
}

BroadcastResult is the chain's response to a transaction broadcast.

type ChainSubmitter

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

ChainSubmitter submits MsgRevealShare transactions to the chain's REST API.

func NewChainSubmitter

func NewChainSubmitter(baseURL string) *ChainSubmitter

NewChainSubmitter creates a submitter targeting the given base URL.

func (*ChainSubmitter) FetchVoteRound

func (c *ChainSubmitter) FetchVoteRound(roundID string) (uint64, error)

FetchVoteRound queries the chain REST API for a vote round's metadata and returns the vote_end_time (unix seconds).

func (*ChainSubmitter) SubmitRevealShare

func (c *ChainSubmitter) SubmitRevealShare(msg *MsgRevealShareJSON) (*BroadcastResult, error)

SubmitRevealShare POSTs a MsgRevealShare to the chain endpoint.

func (*ChainSubmitter) SubmitRevealShareContext added in v0.5.41

func (c *ChainSubmitter) SubmitRevealShareContext(ctx context.Context, msg *MsgRevealShareJSON) (*BroadcastResult, error)

SubmitRevealShareContext POSTs a MsgRevealShare to the chain endpoint using ctx, allowing callers to attach the outbound request to an active trace.

type Config

type Config struct {
	// Disable turns off the helper server entirely.
	Disable bool `mapstructure:"disable"`

	// APIToken protects POST /shielded-vote/v1/shares when set (checked via X-Helper-Token).
	APIToken string `mapstructure:"api_token"`

	// ExposeQueueStatus enables the benchmark-only GET /shielded-vote/v1/queue-status
	// endpoint. Keep this off by default to avoid exposing per-round queue
	// counts to observers.
	ExposeQueueStatus bool `mapstructure:"expose_queue_status"`

	// DBPath is the path to the SQLite database file. Use ":memory:" for testing.
	DBPath string `mapstructure:"db_path"`

	// ChainAPIPort is the port of the chain's REST API (localhost).
	// Used for submitting MsgRevealShare via POST. Defaults to 1317 — the
	// standard Cosmos SDK [api] address port that svoted serves. Setting
	// this to a port where no server listens causes every share to fail
	// broadcast silently (`connection refused` warnings in the log while
	// the helper keeps accepting and proving new shares), which surfaces
	// to voters as zero-value tallies with no user-visible error.
	ChainAPIPort int `mapstructure:"chain_api_port"`

	// MaxConcurrentProofs limits concurrent proof generation goroutines.
	MaxConcurrentProofs int `mapstructure:"max_concurrent_proofs"`

	// SentryDSN is the Sentry DSN for error tracking. When empty, Sentry is
	// disabled. Can also be set via the SENTRY_DSN environment variable at
	// runtime (app.toml takes precedence if set).
	SentryDSN string `mapstructure:"sentry_dsn"`
}

Config holds the helper server configuration, read from app.toml helper.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default helper configuration.

type EncryptedShareWire

type EncryptedShareWire struct {
	C1         string `json:"c1"`          // base64, 32 bytes
	C2         string `json:"c2"`          // base64, 32 bytes
	ShareIndex uint32 `json:"share_index"` // 0..15
}

EncryptedShareWire is the wire format for an encrypted ElGamal share component.

type EnqueueResult

type EnqueueResult int

EnqueueResult reports how an enqueue attempt was handled.

const (
	EnqueueInserted EnqueueResult = iota
	EnqueueDuplicate
	EnqueueConflict
)

type ExpiredRoundSummary added in v0.5.35

type ExpiredRoundSummary struct {
	RoundID   string
	Total     int
	Pending   int
	Submitted int
	Failed    int
}

ExpiredRoundSummary holds queue counts for a round whose voting window has closed. Pending and Failed are both unsubmitted from the chain's perspective.

func (ExpiredRoundSummary) Unsubmitted added in v0.5.35

func (s ExpiredRoundSummary) Unsubmitted() int

type Helper

type Helper struct {
	Store                 *ShareStore
	Processor             *Processor
	APIToken              string
	ExposeQueueStatus     bool
	VCHash                VCHashFunc
	ShareNullifierChecker ShareNullifierChecker
	Logger                log.Logger
}

Helper manages the share processing pipeline lifecycle.

func New

func New(cfg Config, tree TreeReader, prover ProofGenerator, roundFetcher RoundInfoFetcher, isRoundActive RoundStatusChecker, vcHash VCHashFunc, shareNF ShareNullifierChecker, homeDir string, logger log.Logger) (*Helper, error)

New creates a new Helper from the given configuration.

Parameters:

  • cfg: helper configuration (from app.toml helper section)
  • tree: accesses the commitment tree (status + merkle paths + leaf reads) from the keeper's KV store
  • prover: generates ZKP #3 proofs (real FFI or mock)
  • roundFetcher: queries the chain for round metadata (direct keeper access)
  • isRoundActive: checks if a round is still ACTIVE (nil = skip check)
  • vcHash: computes vote commitment Poseidon hash for ingress validation
  • homeDir: the chain's home directory (for default DB path)
  • logger: module logger

func (*Helper) Close

func (h *Helper) Close() error

Close shuts down the helper and releases resources.

func (*Helper) RegisterRoutes

func (h *Helper) RegisterRoutes(router *mux.Router)

RegisterRoutes registers the helper's HTTP routes on the given router.

func (*Helper) Start

func (h *Helper) Start(ctx context.Context) error

Start launches the background processor in the given context. It blocks until the context is cancelled.

func (*Helper) Tree

func (h *Helper) Tree() TreeReader

Tree returns the tree reader used by the processor.

type MsgRevealShareJSON

type MsgRevealShareJSON struct {
	ShareNullifier           string `json:"share_nullifier"` // base64, 32 bytes
	EncShare                 string `json:"enc_share"`       // base64, 64 bytes (C1||C2)
	ProposalID               uint32 `json:"proposal_id"`
	VoteDecision             uint32 `json:"vote_decision"`
	Proof                    string `json:"proof"`         // base64
	VoteRoundID              string `json:"vote_round_id"` // base64, 32 bytes
	VoteCommTreeAnchorHeight uint64 `json:"vote_comm_tree_anchor_height"`
}

MsgRevealShareJSON is the JSON payload for submitting MsgRevealShare to the chain's REST API. Byte fields are base64-encoded (Go's default for []byte).

type Processor

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

Processor is the background share processing loop. It checks the share queue when wallet-provided submit_at times arrive, generates Merkle paths and ZKP 3 proofs, and submits MsgRevealShare to the chain.

func NewProcessor

func NewProcessor(
	store *ShareStore,
	tree TreeReader,
	prover ProofGenerator,
	submitter *ChainSubmitter,
	logger log.Logger,
	maxConcurrent int,
	isRoundActive RoundStatusChecker,
) *Processor

NewProcessor creates a new share processor.

func (*Processor) Run

func (p *Processor) Run(ctx context.Context) error

Run starts the processing loop. Blocks until ctx is cancelled. Each cycle processes ready shares and purges share data for rounds whose voting window has ended.

type ProofGenerator

type ProofGenerator interface {
	// GenerateShareRevealProof generates a share reveal proof.
	// merklePath: 772-byte serialized Merkle path (from votetree.TreeHandle.Path)
	// shareComms: 16 × 32-byte per-share Poseidon commitments
	// primaryBlind: 32-byte blind factor for the revealed share
	// encC1, encC2: 32-byte compressed Pallas points of the revealed share
	// Returns proof bytes, nullifier (32 bytes), tree root (32 bytes).
	GenerateShareRevealProof(
		merklePath []byte,
		shareComms [16][32]byte,
		primaryBlind [32]byte,
		encC1 [32]byte,
		encC2 [32]byte,
		shareIndex uint32,
		proposalID, voteDecision uint32,
		roundID [32]byte,
	) (proof []byte, nullifier [32]byte, treeRoot [32]byte, err error)
}

ProofGenerator abstracts ZKP #3 proof generation for testing.

type QueueStatus

type QueueStatus struct {
	Total     int `json:"total"`
	Pending   int `json:"pending"`
	Submitted int `json:"submitted"`
	Failed    int `json:"failed"`
}

QueueStatus holds per-round queue statistics.

type QueuedShare

type QueuedShare struct {
	Payload     SharePayload
	State       ShareState
	Attempts    int
	VoteEndTime uint64 // unix seconds; 0 if unknown
}

QueuedShare is a share payload with processing metadata.

type RoundInfoFetcher

type RoundInfoFetcher func(roundID string) (voteEndTime uint64, err error)

RoundInfoFetcher queries the chain for vote round metadata. Returns vote_end_time (unix seconds) for the given round ID (hex).

type RoundStatusChecker added in v0.3.0

type RoundStatusChecker func(roundID string) (isActive bool, err error)

RoundStatusChecker returns true if the round is still accepting shares (i.e., status == ACTIVE). Used by the processor to skip shares for rounds that have transitioned to TALLYING or beyond, avoiding wasted ZKP computation.

type ShareNullifierChecker added in v0.3.0

type ShareNullifierChecker func(roundIDHex string, shareNullifier []byte) (bool, error)

ShareNullifierChecker reports whether a share nullifier is recorded on-chain for the given voting round (hex-encoded 32-byte round id). Used by the share-status endpoint to confirm MsgRevealShare acceptance without exposing a public chain query API to wallets.

type ShareNullifierCheckerGetter added in v0.3.0

type ShareNullifierCheckerGetter func() ShareNullifierChecker

ShareNullifierCheckerGetter resolves the checker at request time (nil when helper disabled).

type SharePayload

type SharePayload struct {
	SharesHash   string             `json:"shares_hash"`   // base64, 32 bytes
	ProposalID   uint32             `json:"proposal_id"`   // proposal being voted on
	VoteDecision uint32             `json:"vote_decision"` // 0=support, 1=oppose, 2=skip
	EncShare     EncryptedShareWire `json:"enc_share"`     // the share to relay
	TreePosition uint64             `json:"tree_position"` // VC leaf index
	VoteRoundID  string             `json:"vote_round_id"` // hex, 32 bytes
	ShareComms   []string           `json:"share_comms"`   // base64, 16 × 32-byte Poseidon commitments
	PrimaryBlind string             `json:"primary_blind"` // base64, 32 bytes
	SubmitAt     uint64             `json:"submit_at"`     // unix seconds; 0 = immediate (last-moment)
}

SharePayload is the wire format sent by wallets to the helper server.

type ShareState

type ShareState int

ShareState represents the processing state of a queued share.

const (
	ShareStateReceived  ShareState = 0 // waiting for submit_at
	ShareStateWitnessed ShareState = 1 // ready for proof generation
	ShareStateSubmitted ShareState = 2 // submitted to chain
	ShareStateFailed    ShareState = 3 // permanently failed
)

type ShareStore

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

ShareStore is a SQLite-backed share queue with ephemeral in-memory scheduling. Payload data and processing state are persisted; client-provided submit_at timestamps control when each share is eligible for proof generation.

func NewShareStore

func NewShareStore(dbPath string, fetcher RoundInfoFetcher) (*ShareStore, error)

NewShareStore opens (or creates) a SQLite database and runs migrations.

func (*ShareStore) Close

func (s *ShareStore) Close() error

Close closes the database connection.

func (*ShareStore) Enqueue

func (s *ShareStore) Enqueue(payload SharePayload) (EnqueueResult, error)

Enqueue adds a share payload using the wallet-provided submit_at time.

Returns:

  • EnqueueInserted when a new row was inserted and scheduled.
  • EnqueueDuplicate when an identical payload already exists.
  • EnqueueConflict when an entry exists for (round_id, share_index) but with different payload content.

func (*ShareStore) ExpiredRoundSummaries added in v0.5.35

func (s *ShareStore) ExpiredRoundSummaries(now time.Time) ([]ExpiredRoundSummary, error)

ExpiredRoundSummaries returns per-round queue counts for rounds whose voting window has ended. Call this before PurgeExpiredRounds so unsubmitted share alerts can be emitted without retaining witness data.

func (*ShareStore) MarkFailed

func (s *ShareStore) MarkFailed(roundID string, shareIndex, proposalID uint32, treePosition uint64)

MarkFailed marks a share processing attempt as failed, with retry or permanent failure after max attempts.

func (*ShareStore) MarkSubmitted

func (s *ShareStore) MarkSubmitted(roundID string, shareIndex, proposalID uint32, treePosition uint64)

MarkSubmitted marks a share as successfully submitted to the chain.

func (*ShareStore) NextScheduledTime added in v0.5.45

func (s *ShareStore) NextScheduledTime() (time.Time, bool)

NextScheduledTime returns the earliest scheduled share time.

func (*ShareStore) PurgeExpiredRounds

func (s *ShareStore) PurgeExpiredRounds() int64

PurgeExpiredRounds deletes all share data for rounds whose vote_end_time has passed, and removes the corresponding entries from the in-memory schedule and round cache. Returns the number of rows deleted.

func (*ShareStore) ScheduleChanged added in v0.5.45

func (s *ShareStore) ScheduleChanged() <-chan struct{}

ScheduleChanged returns a buffered notification channel that receives a signal when enqueue or retry scheduling changes. Multiple changes may coalesce.

func (*ShareStore) Status

func (s *ShareStore) Status() map[string]QueueStatus

Status returns per-round queue statistics.

func (*ShareStore) TakeReady

func (s *ShareStore) TakeReady() []QueuedShare

TakeReady returns all shares past their scheduled submission time that are in Received state, transitioning them to Witnessed.

type TraceSpan added in v0.5.36

type TraceSpan = sentry.TraceSpan

TraceSpan wraps a Sentry performance span.

func StartTrace added in v0.5.36

func StartTrace(ctx context.Context, operation, description string, tags map[string]string, data map[string]interface{}) (context.Context, *TraceSpan)

StartTrace starts a Sentry performance span.

type TreeReader

type TreeReader interface {
	// ForRound returns an isolated reader for the given voting round.
	ForRound(roundID []byte) TreeReader

	// GetTreeStatus returns lightweight tree statistics (leaf count + anchor height).
	GetTreeStatus() (TreeStatus, error)

	// MerklePath returns the 772-byte serialized Poseidon Merkle authentication
	// path for the leaf at position, anchored to the checkpoint at anchorHeight.
	// anchorHeight must correspond to a checkpoint that exists in the persistent
	// tree (i.e. a block height at which Checkpoint was called by EndBlocker).
	MerklePath(position uint64, anchorHeight uint32) ([]byte, error)

	// LeafAt returns the raw 32-byte vote commitment stored at the given tree
	// position, or nil if no leaf exists at that index.
	LeafAt(position uint64) ([]byte, error)
}

TreeReader abstracts commitment tree access from the keeper. Commitment trees are scoped per voting round; callers must use ForRound before round-specific tree lookups so concurrent shares cannot overwrite shared round state.

type TreeStatus

type TreeStatus struct {
	LeafCount    uint64 `json:"leaf_count"`
	AnchorHeight uint64 `json:"anchor_height"`
}

TreeStatus holds lightweight commitment tree statistics.

type VCHashFunc

type VCHashFunc func(roundID, sharesHash [32]byte, proposalID, voteDecision uint32) ([32]byte, error)

VCHashFunc computes the vote commitment Poseidon hash. The implementation is provided by the votecommitment CGo package but abstracted here so the helper package doesn't depend on the Rust FFI library directly.

Jump to

Keyboard shortcuts

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